mirror of
https://github.com/famedly/ansible-collection-matrix
synced 2024-12-04 00:29:12 +00:00
feat: Add ansible-test and refactor plugins
This commit is contained in:
parent
1a302fb74b
commit
52785ab768
19 changed files with 650 additions and 261 deletions
|
@ -1,7 +1,31 @@
|
|||
include:
|
||||
- project: 'famedly/company/devops/templates/ci-cd'
|
||||
ref: 'ansible-v1'
|
||||
file: '/ansible.yml'
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- publish
|
||||
|
||||
# Debian Stable
|
||||
sanity 3.9:
|
||||
variables: { PYTHON_VERSION: "3.9" }
|
||||
extends: .ansible-test-sanity
|
||||
|
||||
# Latest stable Python
|
||||
sanity 3.10:
|
||||
variables: { PYTHON_VERSION: "3.10" }
|
||||
extends: .ansible-test-sanity
|
||||
|
||||
unit:
|
||||
variables: { PYTHON_VERSION: "3.10" }
|
||||
extends: .ansible-test-units
|
||||
|
||||
integration:
|
||||
variables: { PYTHON_VERSION: "3.10" }
|
||||
extends: .ansible-test-integration
|
||||
|
||||
build:
|
||||
image: docker.io/alpine
|
||||
stage: build
|
||||
|
|
|
@ -1,93 +1,121 @@
|
|||
#!/usr/bin/python
|
||||
# coding: utf-8
|
||||
|
||||
# (c) 2021-2022, Famedly GmbH
|
||||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
import traceback
|
||||
import asyncio
|
||||
|
||||
#Check if all required libs can loded
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
# Check if all required libs can load
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from nio import *
|
||||
HAS_LIB = True
|
||||
from nio import AsyncClient, AsyncClientConfig, \
|
||||
Api, \
|
||||
LoginResponse, LoginError, \
|
||||
LogoutResponse, LogoutError, \
|
||||
RoomGetStateResponse, RoomGetStateError, \
|
||||
RoomBanResponse, RoomBanError, \
|
||||
RoomUnbanResponse, RoomUnbanError, \
|
||||
RoomKickResponse, RoomKickError, \
|
||||
RoomInviteResponse, RoomInviteError, \
|
||||
RoomResolveAliasResponse, RoomResolveAliasError, \
|
||||
JoinedRoomsResponse, JoinedRoomsError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
class AnsibleNioModule():
|
||||
def __init__(self,
|
||||
custom_spec={},
|
||||
|
||||
class AnsibleNioModule:
|
||||
def __init__(self,
|
||||
custom_spec=None,
|
||||
bypass_checks=False,
|
||||
no_log=False,
|
||||
mutually_exclusive=[['password', 'token']],
|
||||
mutually_exclusive=None,
|
||||
required_together=None,
|
||||
required_one_of=[['password', 'token', 'key']],
|
||||
required_by={'password': 'user_id', 'key': 'user_id'},
|
||||
required_one_of=None,
|
||||
required_by=None,
|
||||
add_file_common_args=False,
|
||||
supports_check_mode=True,
|
||||
required_if=None,
|
||||
user_logout=True):
|
||||
|
||||
#If a user/password login is provided, should we logout when exiting?
|
||||
self.user_logout=user_logout
|
||||
|
||||
#Create the Ansible module
|
||||
if required_by is None:
|
||||
required_by = {'password': 'user_id'}
|
||||
|
||||
if required_one_of is None:
|
||||
required_one_of = [['password', 'token']]
|
||||
|
||||
if mutually_exclusive is None:
|
||||
mutually_exclusive = [['password', 'token']]
|
||||
|
||||
if custom_spec is None:
|
||||
custom_spec = {}
|
||||
|
||||
# If a user/password login is provided, should we logout when exiting?
|
||||
self.user_logout = user_logout
|
||||
|
||||
# Create the Ansible module
|
||||
self.module = AnsibleModule(
|
||||
argument_spec = AnsibleNioModule.__common_argument_spec(custom_spec),
|
||||
argument_spec=AnsibleNioModule.__common_argument_spec(custom_spec),
|
||||
bypass_checks=bypass_checks,
|
||||
no_log=no_log,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
required_together=required_together,
|
||||
required_one_of=required_one_of,
|
||||
add_file_common_args=add_file_common_args,
|
||||
supports_check_mode = supports_check_mode,
|
||||
supports_check_mode=supports_check_mode,
|
||||
required_if=required_if,
|
||||
required_by=required_by
|
||||
)
|
||||
|
||||
#Make some values from the module easly accessible
|
||||
self.check_mode = self.module.check_mode
|
||||
self.params = self.module.params
|
||||
|
||||
#Fail when matix-nio is not installed
|
||||
#WARNING: We don't perform a version check!
|
||||
# Make some values from the module easly accessible
|
||||
self.check_mode = self.module.check_mode
|
||||
self.params = self.module.params
|
||||
|
||||
# Fail when matix-nio is not installed
|
||||
# WARNING: We don't perform a version check!
|
||||
if not HAS_LIB:
|
||||
self.module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
async def matrix_login(self):
|
||||
#Login with token or supplied user account
|
||||
# Login with token or supplied user account
|
||||
if self.module.params['token'] is None:
|
||||
self.client = AsyncClient(self.module.params['hs_url'], self.module.params['user_id'])
|
||||
login_response = await self.client.login(self.module.params['password'])
|
||||
if isinstance(login_response, LoginResponse):
|
||||
self.access_token = login_response.access_token
|
||||
self.device_id = login_response.device_id
|
||||
else:
|
||||
result['msg'] = login_response.message
|
||||
result['http_status_code'] = login_response.status_code
|
||||
module.fail_json(**result)
|
||||
login_response = await self.client.login(password=self.module.params['password'])
|
||||
else:
|
||||
self.client = AsyncClient(self.module.params['hs_url'])
|
||||
self.client.access_token = self.module.params['token']
|
||||
login_response = await self.client.login(token=self.module.params['token'])
|
||||
if isinstance(login_response, LoginResponse):
|
||||
self.access_token = login_response.access_token
|
||||
self.device_id = login_response.device_id
|
||||
else:
|
||||
result = {
|
||||
'msg': login_response.message,
|
||||
'http_status_code': login_response.status_code
|
||||
}
|
||||
self.module.fail_json(**result)
|
||||
|
||||
async def matrix_logout(self):
|
||||
if self.client.logged_in:
|
||||
await self.client.logout()
|
||||
request = await self.client.logout()
|
||||
if isinstance(request, LogoutError):
|
||||
result = {'msg': request.message}
|
||||
self.module.fail_json(**result)
|
||||
|
||||
async def exit_json(self, **result):
|
||||
if self.module.params['token'] is None and self.user_logout == True:
|
||||
if self.module.params['token'] is None and self.user_logout is True:
|
||||
await self.matrix_logout()
|
||||
await self.client.close()
|
||||
self.module.exit_json(**result)
|
||||
|
||||
async def fail_json(self, **result):
|
||||
if self.module.params['token'] is None and self.user_logout == True:
|
||||
if self.module.params['token'] is None and self.user_logout is True:
|
||||
await self.matrix_logout()
|
||||
await self.client.close()
|
||||
self.module.fail_json(**result)
|
||||
|
@ -101,4 +129,3 @@ class AnsibleNioModule():
|
|||
token=dict(type='str', required=False, no_log=True)
|
||||
)
|
||||
return {**argument_spec, **custom_spec}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#!/usr/bin/python3
|
||||
# coding: utf-8
|
||||
|
||||
# (c) 2021, Famedly GmbH
|
||||
|
@ -8,9 +7,19 @@ from __future__ import (absolute_import, division, print_function)
|
|||
|
||||
__metaclass__ = type
|
||||
|
||||
from typing import Union, Type
|
||||
import requests
|
||||
import traceback
|
||||
import urllib.parse
|
||||
from types import SimpleNamespace
|
||||
|
||||
# Check if all required libs can load
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
REQUESTS_IMPORT_ERROR = traceback.format_exc()
|
||||
HAS_REQUESTS = False
|
||||
requests = SimpleNamespace(Response=None)
|
||||
|
||||
|
||||
class AdminApi:
|
||||
|
@ -31,39 +40,39 @@ class AdminApi:
|
|||
# Make API request
|
||||
def get(self, path: str) -> requests.Response:
|
||||
response = requests.get(url=urllib.parse.urljoin(self.api_url, path),
|
||||
headers={"Authorization": "Bearer {}".format(self.access_token)})
|
||||
headers={"Authorization": f"Bearer {self.access_token}"})
|
||||
if response.status_code == 200:
|
||||
return response
|
||||
if response.status_code == 500:
|
||||
raise Exceptions.MatrixException(
|
||||
"Matrix Error\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Matrix Error\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
else:
|
||||
raise Exceptions.HTTPException(
|
||||
"Unexpected return code\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Unexpected return code\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
|
||||
def post(self, path: str, **kwargs) -> requests.Response:
|
||||
response = requests.post(url=urllib.parse.urljoin(self.api_url, path),
|
||||
headers={"Authorization": "Bearer {}".format(self.access_token)}, **kwargs)
|
||||
headers={"Authorization": f"Bearer {self.access_token}"}, **kwargs)
|
||||
if response.status_code == 200:
|
||||
return response
|
||||
if response.status_code == 500:
|
||||
raise Exceptions.MatrixException(
|
||||
"Matrix Error\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Matrix Error\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
else:
|
||||
raise Exceptions.HTTPException(
|
||||
"Unexpected return code\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Unexpected return code\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
|
||||
def delete(self, path: str) -> requests.Response:
|
||||
response = requests.delete(url=urllib.parse.urljoin(self.api_url, path),
|
||||
headers={"Authorization": "Bearer {}".format(self.access_token)})
|
||||
headers={"Authorization": f"Bearer {self.access_token}"})
|
||||
if response.status_code == 200:
|
||||
return response
|
||||
if response.status_code == 500:
|
||||
raise Exceptions.MatrixException(
|
||||
"Matrix Error\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Matrix Error\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
else:
|
||||
raise Exceptions.HTTPException(
|
||||
"Unexpected return code\nHTTP-Code: {}\n Response: {}".format(response.status_code, response.text))
|
||||
f"Unexpected return code\nHTTP-Code: {response.status_code}\n Response: {response.text}")
|
||||
|
||||
@staticmethod
|
||||
def url_encode(string: str) -> str:
|
||||
|
@ -83,8 +92,8 @@ class AdminApi:
|
|||
def set(self, user_id: str, messages_per_second: int = 0, burst_count: int = 0) -> dict:
|
||||
user_id = AdminApi.url_encode(user_id)
|
||||
return self.__parent.post(
|
||||
self.API_PATH.format(user_id=user_id), json={"messages_per_second": messages_per_second,
|
||||
"burst_count": burst_count}).json()
|
||||
self.API_PATH.format(user_id=user_id), json={"messages_per_second": messages_per_second,
|
||||
"burst_count": burst_count}).json()
|
||||
|
||||
def delete(self, user_id: str) -> dict:
|
||||
user_id = AdminApi.url_encode(user_id)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -26,14 +27,22 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
@ -56,10 +65,10 @@ device_id:
|
|||
returned: When login was successful
|
||||
type: str
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
|
||||
|
||||
async def run_module():
|
||||
result = dict(
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -26,10 +27,22 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
@ -43,10 +56,11 @@ EXAMPLES = '''
|
|||
|
||||
RETURN = '''
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
|
||||
|
||||
async def run_module():
|
||||
result = dict(
|
||||
changed=False,
|
||||
|
@ -61,5 +75,6 @@ async def run_module():
|
|||
def main():
|
||||
asyncio.run(run_module())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
|
@ -20,32 +17,53 @@ author: "Johanna Dorothea Reichmann (@transcaffeine)"
|
|||
module: matrix_member
|
||||
short_description: Manage matrix room membership
|
||||
description:
|
||||
- Manage the membership status of a given set of matrix users. Invitations (`state=member`), kicks and bans can be issued. With the `exclusive=True` flag, all other members in the room can be auto-kicked.
|
||||
- Manage the membership status of a given set of matrix users. Invitations (`state=member`),
|
||||
kicks and bans can be issued. With the `exclusive=True` flag, all other members in the room can be auto-kicked.
|
||||
options:
|
||||
room_id:
|
||||
hs_url:
|
||||
description:
|
||||
- ID of the room to manage
|
||||
user_ids:
|
||||
description:
|
||||
- List of matrix IDs to set their state
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- In which state all listed members should be: member|kicked|banned
|
||||
required: true
|
||||
exclusive:
|
||||
description:
|
||||
- If state=member, the module ensure only the specified user_ids are in the room by kicking every other user present in the room.
|
||||
required: false
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call. If provided, user_id and password are not required
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
room_id:
|
||||
description:
|
||||
- ID of the room to edit
|
||||
required: true
|
||||
type: str
|
||||
user_ids:
|
||||
description:
|
||||
- List of matrix IDs to set their state
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- In which state all listed members should be member|kicked|banned
|
||||
choices: ['member', 'kicked', 'banned']
|
||||
type: str
|
||||
required: true
|
||||
exclusive:
|
||||
description:
|
||||
- If state=member, the module ensure only the specified user_ids are in the room by
|
||||
kicking every other user present in the room.
|
||||
default: false
|
||||
type: bool
|
||||
required: false
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
@ -58,83 +76,97 @@ EXAMPLES = '''
|
|||
room_id: "{{ matrix_room_id }}"
|
||||
state: member
|
||||
user_ids:
|
||||
- @user1:matrix.org
|
||||
- @user2:homeserver.tld
|
||||
- "@user1:matrix.org"
|
||||
- "@user2:homeserver.tld"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
members:
|
||||
description: Dictionary of all members in the given room who are either invited or joined
|
||||
returned: When auth_token is valid
|
||||
type: dict[str]
|
||||
type: dict
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
import traceback
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import RoomGetStateError, \
|
||||
RoomBanError, RoomUnbanError, \
|
||||
RoomKickError, RoomInviteError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
class NioOperationError(Exception):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
|
||||
async def get_room_members(client, room_id, res):
|
||||
member_resp = await client.room_get_state(room_id)
|
||||
|
||||
if isinstance(member_resp, RoomGetStateError):
|
||||
res['msg'] = "Could not get room state for roomId={0}".format(room_id)
|
||||
raise Exception()
|
||||
res['msg'] = f"Could not get room state for roomId={room_id}"
|
||||
raise NioOperationError(res['msg'])
|
||||
else:
|
||||
return dict(list(map(lambda m: (m['state_key'],m['content']['membership']), filter(lambda e: e['type'] == 'm.room.member' and
|
||||
e['content']['membership'] in ['invite', 'join', 'leave', 'ban'], member_resp.events))))
|
||||
return dict(list(map(lambda m: (m['state_key'], m['content']['membership']), filter(
|
||||
lambda e: e['type'] == 'm.room.member' and e['content']['membership'] in ['invite', 'join', 'leave', 'ban'],
|
||||
member_resp.events))))
|
||||
|
||||
|
||||
async def ban_from_room(client, room_id, user_id, res):
|
||||
ban_resp = await client.room_ban(room_id, user_id)
|
||||
if isinstance(ban_resp, RoomBanError):
|
||||
res['msg'] = "Could not ban user={0} from roomId={1}".format(user_id, room_id)
|
||||
raise Exception()
|
||||
res['msg'] = f"Could not ban user={user_id} from roomId={room_id}"
|
||||
raise NioOperationError(res['msg'])
|
||||
res['changed'] = True
|
||||
res['banned'].append(user_id)
|
||||
|
||||
|
||||
async def unban_from_room(client, room_id, user_id, res):
|
||||
ban_resp = await client.room_unban(room_id, user_id)
|
||||
if isinstance(ban_resp, RoomUnbanError):
|
||||
res['msg'] = "Could not unban user={0} from roomId={1}".format(user_id, room_id)
|
||||
raise Exception()
|
||||
res['msg'] = f"Could not unban user={user_id} from roomId={room_id}"
|
||||
raise NioOperationError(res['msg'])
|
||||
res['changed'] = True
|
||||
res['unbanned'].append(user_id)
|
||||
|
||||
|
||||
async def kick_from_room(client, room_id, user_id, res):
|
||||
kick_resp = await client.room_kick(room_id, user_id)
|
||||
if isinstance(kick_resp, RoomKickError):
|
||||
res['msg'] = "Could not kick user={0} from roomId={1}".format(user_id, room_id)
|
||||
raise Exception()
|
||||
res['msg'] = f"Could not kick user={user_id} from roomId={room_id}"
|
||||
raise NioOperationError(res['msg'])
|
||||
res['changed'] = True
|
||||
res['kicked'].append(user_id)
|
||||
|
||||
|
||||
async def invite_to_room(client, room_id, user_id, res):
|
||||
invite_resp = await client.room_invite(room_id, user_id)
|
||||
if isinstance(invite_resp, RoomInviteError):
|
||||
res['msg'] = "Could not invite user={0} to roomId={1}".format(user_id, room_id)
|
||||
raise Exception()
|
||||
res['msg'] = f"Could not invite user={user_id} to roomId={room_id}"
|
||||
raise NioOperationError(res['msg'])
|
||||
res['changed'] = True
|
||||
res['invited'].append(user_id)
|
||||
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
state=dict(choices=['member', 'kicked', 'banned'], required=True),
|
||||
room_id=dict(type='str', required=True),
|
||||
user_ids=dict(type='list', required=True, elements='str'),
|
||||
exclusive=dict(type='bool', required=False, default=False),
|
||||
)
|
||||
module_args = dict(state=dict(choices=['member', 'kicked', 'banned'], required=True),
|
||||
room_id=dict(type='str', required=True),
|
||||
user_ids=dict(type='list', required=True, elements='str'),
|
||||
exclusive=dict(type='bool', required=False, default=False))
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
banned=[],
|
||||
unbanned=[],
|
||||
kicked=[],
|
||||
invited=[],
|
||||
members=[],
|
||||
msg="",
|
||||
)
|
||||
result = dict(changed=False, banned=[], unbanned=[], kicked=[], invited=[], members=[], msg="", )
|
||||
|
||||
module = AnsibleNioModule(module_args)
|
||||
if not HAS_LIB:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
await module.matrix_login()
|
||||
|
||||
if module.check_mode:
|
||||
|
@ -151,10 +183,13 @@ async def run_module():
|
|||
# Create client object
|
||||
client = module.client
|
||||
|
||||
# Query all room members (invited users count as member, as they _can_ be in the room)
|
||||
room_members = await get_room_members(client, room_id, result)
|
||||
present_members = {m: s for m, s in room_members.items() if s in ['join', 'invite']}.keys()
|
||||
banned_members = {m: s for m, s in room_members.items() if s == 'ban'}.keys()
|
||||
try:
|
||||
# Query all room members (invited users count as member, as they _can_ be in the room)
|
||||
room_members = await get_room_members(client, room_id, result)
|
||||
present_members = {m: s for m, s in room_members.items() if s in ['join', 'invite']}.keys()
|
||||
banned_members = {m: s for m, s in room_members.items() if s == 'ban'}.keys()
|
||||
except NioOperationError:
|
||||
await module.fail_json(**result)
|
||||
|
||||
if not module.params['exclusive']:
|
||||
# Handle non-exclusive invite|kick|ban
|
||||
|
@ -170,7 +205,7 @@ async def run_module():
|
|||
await unban_from_room(client, room_id, user_id, result)
|
||||
elif action == 'banned' and user_id not in banned_members:
|
||||
await ban_from_room(client, room_id, user_id, result)
|
||||
except:
|
||||
except NioOperationError:
|
||||
await module.fail_json(**result)
|
||||
else:
|
||||
# Handle exclusive mode: get state and make lists of users to be kicked or invited
|
||||
|
@ -182,18 +217,19 @@ async def run_module():
|
|||
await invite_to_room(client, room_id, user_id, result)
|
||||
for user_id in to_kick:
|
||||
await kick_from_room(client, room_id, user_id, result)
|
||||
except:
|
||||
except NioOperationError:
|
||||
await module.fail_json(**result)
|
||||
|
||||
# Get all current members from the room
|
||||
try:
|
||||
room_members_after = await get_room_members(client, room_id, result)
|
||||
result['members'] = {m: s for m, s in room_members_after.items() if s in ['join', 'invite']}.keys()
|
||||
except:
|
||||
except NioOperationError:
|
||||
pass
|
||||
|
||||
await module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_module())
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -21,33 +22,43 @@ module: matrix_notification
|
|||
short_description: Send notifications to matrix
|
||||
description:
|
||||
- This module sends html formatted notifications to matrix rooms.
|
||||
version_added: "2.8"
|
||||
version_added: "2.8.0"
|
||||
options:
|
||||
msg_plain:
|
||||
description:
|
||||
- Plain text form of the message to send to matrix, usually markdown
|
||||
required: true
|
||||
msg_html:
|
||||
description:
|
||||
- HTML form of the message to send to matrix
|
||||
required: true
|
||||
room_id:
|
||||
description:
|
||||
- ID of the room to send the notification to
|
||||
required: true
|
||||
hs_url:
|
||||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call. If provided, user_id and password are not required
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
msg_plain:
|
||||
description:
|
||||
- Plain text form of the message to send to matrix, usually markdown
|
||||
required: true
|
||||
type: str
|
||||
msg_html:
|
||||
description:
|
||||
- HTML form of the message to send to matrix
|
||||
required: true
|
||||
type: str
|
||||
room_id:
|
||||
description:
|
||||
- ID of the room to send the notification to
|
||||
required: true
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
@ -73,16 +84,27 @@ EXAMPLES = '''
|
|||
|
||||
RETURN = '''
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import RoomSendResponse, RoomSendError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
msg_plain=dict(type='str', required=True),
|
||||
msg_html=dict(type='str', required=True),
|
||||
room_id=dict(type='str', required=True),
|
||||
)
|
||||
)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
|
@ -90,15 +112,17 @@ async def run_module():
|
|||
)
|
||||
|
||||
module = AnsibleNioModule(module_args)
|
||||
if not HAS_LIB:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
if module.check_mode:
|
||||
return result
|
||||
|
||||
|
||||
await module.matrix_login()
|
||||
client = module.client
|
||||
|
||||
# send message
|
||||
await client.room_send(
|
||||
response = await client.room_send(
|
||||
room_id=module.params['room_id'],
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
|
@ -108,9 +132,12 @@ async def run_module():
|
|||
"formatted_body": module.params['msg_html'],
|
||||
}
|
||||
)
|
||||
if isinstance(response, RoomSendError):
|
||||
await module.fail_json(**result)
|
||||
|
||||
await module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_module())
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -20,26 +21,37 @@ author: "Jan Christian Grünhage (@jcgruenhage)"
|
|||
module: matrix_room
|
||||
short_description: Join/Create matrix room
|
||||
description:
|
||||
- This module takes a room alias and makes sure that the user identified by the access token is in such a room. If that room does not exist, it is created, if it does exist but the user is not in it, it tries to join. If the alias is taken and the user can't join the room, the module will fail. Remote aliases are not supported for creating, but work for joining.
|
||||
- This module takes a room alias and makes sure that the user identified by the access token is in such a room.
|
||||
If that room does not exist, it is created, if it does exist but the user is not in it, it tries to join.
|
||||
If the alias is taken and the user can't join the room, the module will fail.
|
||||
Remote aliases are not supported for creating, but work for joining.
|
||||
options:
|
||||
alias:
|
||||
description:
|
||||
- Alias of the room to join/create
|
||||
required: true
|
||||
hs_url:
|
||||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call. If provided, user_id and password are not required
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
equirements:
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
alias:
|
||||
description:
|
||||
- Alias of the room to join/create
|
||||
required: true
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
||||
|
@ -55,12 +67,28 @@ RETURN = '''
|
|||
room_id:
|
||||
description: ID of the room
|
||||
type: str
|
||||
sample: !asdfbuiarbk213e479asf:server.tld
|
||||
returned: success
|
||||
sample: "!asdfbuiarbk213e479asf:server.tld"
|
||||
'''
|
||||
import traceback
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import RoomCreateResponse, RoomCreateError, \
|
||||
JoinedRoomsResponse, JoinedRoomsError, \
|
||||
JoinResponse, JoinError, \
|
||||
RoomResolveAliasResponse, RoomResolveAliasError
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
|
@ -73,6 +101,9 @@ async def run_module():
|
|||
)
|
||||
|
||||
module = AnsibleNioModule(module_args)
|
||||
if not HAS_LIB:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
await module.matrix_login()
|
||||
client = module.client
|
||||
|
||||
|
@ -87,7 +118,7 @@ async def run_module():
|
|||
rooms_resp = await client.joined_rooms()
|
||||
if isinstance(rooms_resp, JoinedRoomsError):
|
||||
failed = True
|
||||
result = {"msg":"Couldn't get joined rooms."}
|
||||
result = {"msg": "Couldn't get joined rooms."}
|
||||
elif room_id_resp.room_id in rooms_resp.rooms:
|
||||
result = {"room_id": room_id_resp.room_id, "changed": False}
|
||||
else:
|
||||
|
@ -99,7 +130,7 @@ async def run_module():
|
|||
result = {"room_id": join_resp.room_id, "changed": True}
|
||||
else:
|
||||
failed = True
|
||||
result = {"msg": "Room exists, but couldn't join: {1}".format(join_resp)}
|
||||
result = {"msg": f"Room exists, but couldn't join: {join_resp}"}
|
||||
else:
|
||||
# Get local part of alias
|
||||
local_part_regex = re.search("#([^:]*):(.*)", module.params['alias'])
|
||||
|
@ -113,13 +144,14 @@ async def run_module():
|
|||
result = {"room_id": create_room_resp.room_id, "changed": True}
|
||||
else:
|
||||
failed = True
|
||||
result = {"msg": "Room does not exist but couldn't be created either: {0}".format(create_room_resp)}
|
||||
result = {"msg": f"Room does not exist but couldn't be created either: {create_room_resp}"}
|
||||
|
||||
if failed:
|
||||
await module.fail_json(**result)
|
||||
else:
|
||||
await module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_module())
|
||||
|
||||
|
|
|
@ -1,18 +1,65 @@
|
|||
#!/bin/python3
|
||||
# Copyright: (c) 2018, Emmanouil Kampitakis <info@kampitakis.de>
|
||||
#!/usr/bin/python
|
||||
# Copyright: (c) 2018
|
||||
# Apache 2.0
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: "Emmanouil Kampitakis (@madonius)"
|
||||
module: matrix_signing_key
|
||||
short_description: Create a signing key file if not exists
|
||||
description:
|
||||
- Create a signing key file if not exists
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path to the signing key file
|
||||
required: true
|
||||
type: str
|
||||
requirements:
|
||||
- signedjson (Python library)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create signing key file
|
||||
matrix_signing_key:
|
||||
changed: "/path/to/file"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
import os
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from signedjson import key
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
# Check if all required libs can load
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from signedjson import key
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
def write_signing_key(path):
|
||||
with open(path,'w') as file:
|
||||
with open(path, 'w') as file:
|
||||
key.write_signing_keys(
|
||||
file,
|
||||
[key.generate_signing_key('first')]
|
||||
file,
|
||||
[key.generate_signing_key('first')]
|
||||
)
|
||||
|
||||
|
||||
def run_module():
|
||||
module_args = dict(
|
||||
path=dict(type='str', required=True),
|
||||
|
@ -29,6 +76,9 @@ def run_module():
|
|||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_LIB:
|
||||
module.fail_json(msg=missing_required_lib("signedjson"))
|
||||
|
||||
signing_key_path = module.params['path']
|
||||
|
||||
signing_key_exists = os.path.isfile(signing_key_path)
|
||||
|
@ -37,12 +87,17 @@ def run_module():
|
|||
result['changed'] = True
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
write_signing_key(signing_key_path)
|
||||
try:
|
||||
write_signing_key(signing_key_path)
|
||||
except OSError as e:
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -22,36 +23,47 @@ short_description: Set matrix room state
|
|||
description:
|
||||
- This module sets matrix room state idempotently
|
||||
options:
|
||||
event_type:
|
||||
description:
|
||||
- Event type of the state to be set
|
||||
required: true
|
||||
state_key:
|
||||
description:
|
||||
- State key for the state event to be set
|
||||
required: true
|
||||
content:
|
||||
description:
|
||||
- The content to set the state to
|
||||
required: true
|
||||
room_id:
|
||||
description:
|
||||
- ID of the room to set the state for
|
||||
required: true
|
||||
hs_url:
|
||||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call. If provided, user_id and password are not required
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
equirements:
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
event_type:
|
||||
description:
|
||||
- Event type of the state to be set
|
||||
required: true
|
||||
type: str
|
||||
state_key:
|
||||
description:
|
||||
- State key for the state event to be set
|
||||
required: true
|
||||
type: str
|
||||
content:
|
||||
description:
|
||||
- The content to set the state to
|
||||
required: true
|
||||
type: dict
|
||||
room_id:
|
||||
description:
|
||||
- ID of the room to set the state for
|
||||
required: true
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-client (Python library)
|
||||
'''
|
||||
|
||||
|
@ -79,14 +91,27 @@ event_id:
|
|||
type: str
|
||||
sample: $Het2Dv7EEDFNJNgY-ehLSUrdqMo8JOxZDCMnuQPSNfo
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import JoinedRoomsResponse, JoinedRoomsError, \
|
||||
RoomGetStateEventResponse, RoomPutStateResponse
|
||||
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
event_type=dict(type='str', required=True),
|
||||
state_key=dict(type='str', required=True),
|
||||
state_key=dict(type='str', required=True, no_log=False),
|
||||
content=dict(type='dict', required=True),
|
||||
room_id=dict(type='str', required=True)
|
||||
)
|
||||
|
@ -97,6 +122,8 @@ async def run_module():
|
|||
)
|
||||
|
||||
module = AnsibleNioModule(module_args)
|
||||
if not HAS_LIB:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
await module.matrix_login()
|
||||
client = module.client
|
||||
|
||||
|
@ -110,7 +137,7 @@ async def run_module():
|
|||
rooms_resp = await client.joined_rooms()
|
||||
if isinstance(rooms_resp, JoinedRoomsError):
|
||||
failed = True
|
||||
result = {"msg":"Couldn't get joined rooms."}
|
||||
result = {"msg": "Couldn't get joined rooms."}
|
||||
elif module.params['room_id'] not in rooms_resp.rooms:
|
||||
failed = True
|
||||
result = {"msg": "Not in the room you're trying to set state for."}
|
||||
|
@ -138,13 +165,14 @@ async def run_module():
|
|||
# Else, fail
|
||||
else:
|
||||
failed = True
|
||||
result = {"msg": "Couldn't set state: {error}".format(error=send_resp)}
|
||||
result = {"msg": f"Couldn't set state: {send_resp}"}
|
||||
|
||||
if failed:
|
||||
await module.fail_json(**result)
|
||||
else:
|
||||
await module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
asyncio.run(run_module())
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -17,7 +18,7 @@ ANSIBLE_METADATA = {
|
|||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: "Jan Christian Grünhage"
|
||||
author: "Jan Christian Grünhage (@jcgruenhage)"
|
||||
module: matrix_token_login
|
||||
short_description: Use com.famedly.token based logins to obtain an access token
|
||||
description:
|
||||
|
@ -27,17 +28,30 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: true
|
||||
key:
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The key to sign the log in token with
|
||||
required: true
|
||||
- The password to log in with
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
admin:
|
||||
description:
|
||||
- Whether to set the user as admin during login
|
||||
type: bool
|
||||
key:
|
||||
description: Login key to use
|
||||
type: str
|
||||
required: true
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
- jwcrypto (Python library)
|
||||
|
@ -62,18 +76,38 @@ device_id:
|
|||
returned: When login was successful
|
||||
type: str
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
import json
|
||||
import base64
|
||||
from jwcrypto import jwt, jwk
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
# Check if all required libs can load
|
||||
JWCRYPTO_IMP_ERR = None
|
||||
try:
|
||||
from jwcrypto import jwt, jwk
|
||||
|
||||
HAS_JWCRYPTO = True
|
||||
except ImportError:
|
||||
JWCRYPTO_IMP_ERR = traceback.format_exc()
|
||||
HAS_JWCRYPTO = False
|
||||
|
||||
NIO_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import AsyncClient, Api
|
||||
|
||||
HAS_NIO = True
|
||||
except ImportError:
|
||||
NIO_IMP_ERR = traceback.format_exc()
|
||||
HAS_NIO = False
|
||||
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
key=dict(type='str', required=True),
|
||||
key=dict(type='str', required=True, no_log=True),
|
||||
admin=dict(type='bool', required=False),
|
||||
)
|
||||
|
||||
|
@ -83,6 +117,10 @@ async def run_module():
|
|||
)
|
||||
|
||||
module = AnsibleNioModule(module_args, user_logout=False)
|
||||
if not HAS_JWCRYPTO:
|
||||
await module.fail_json(msg=missing_required_lib("jwcrypto"))
|
||||
if not HAS_NIO:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
if module.check_mode:
|
||||
return result
|
||||
|
@ -112,9 +150,9 @@ async def run_module():
|
|||
}
|
||||
key = jwk.JWK(**key)
|
||||
claims = {
|
||||
"iss": "Matrix UIA Login Ansible Module",
|
||||
"sub": client.user,
|
||||
"exp": int(time.time()) + 60 * 30,
|
||||
"iss": "Matrix UIA Login Ansible Module",
|
||||
"sub": client.user,
|
||||
"exp": int(time.time()) + 60 * 30,
|
||||
}
|
||||
|
||||
if admin is not None:
|
||||
|
@ -126,12 +164,12 @@ async def run_module():
|
|||
token.make_signed_token(key)
|
||||
|
||||
auth = {
|
||||
"type": "com.famedly.login.token",
|
||||
"identifier" : {
|
||||
"type": "m.id.user",
|
||||
"user": client.user
|
||||
},
|
||||
"token": token.serialize()
|
||||
"type": "com.famedly.login.token",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": client.user
|
||||
},
|
||||
"token": token.serialize()
|
||||
}
|
||||
|
||||
payload = json.dumps(auth)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -26,14 +27,22 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to log in with
|
||||
required: true
|
||||
required: false
|
||||
type: str
|
||||
token:
|
||||
description:
|
||||
- Authentication token for the API call
|
||||
required: false
|
||||
type: str
|
||||
requirements:
|
||||
- matrix-nio (Python library)
|
||||
'''
|
||||
|
@ -56,14 +65,24 @@ device_id:
|
|||
returned: When login was successful
|
||||
type: str
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
from functools import reduce
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import AnsibleNioModule
|
||||
from nio import AsyncClient, Api
|
||||
HAS_LIB = True
|
||||
except ImportError:
|
||||
LIB_IMP_ERR = traceback.format_exc()
|
||||
HAS_LIB = False
|
||||
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.matrix import *
|
||||
log = []
|
||||
|
||||
|
||||
def get_payload(data):
|
||||
payload = json.dumps(data)
|
||||
payload_length = len(payload)
|
||||
|
@ -73,55 +92,62 @@ def get_payload(data):
|
|||
}
|
||||
return payload, headers
|
||||
|
||||
|
||||
async def do_password_stage(client, session, method, path, password):
|
||||
auth = {
|
||||
"type": "m.login.password",
|
||||
"identifier" : {
|
||||
"type": "m.id.user",
|
||||
"user": client.user
|
||||
},
|
||||
"session": session,
|
||||
"password": password
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": client.user
|
||||
},
|
||||
"session": session,
|
||||
"password": password
|
||||
}
|
||||
log.append("DEBUG: attempt stage=" + auth['type'] + " for session="+ auth['session'] + " with password="+auth['password'] + ", method=" + method)
|
||||
payload, headers = get_payload({"auth" : auth })
|
||||
raw_response = await client.send(method, path, payload, headers)
|
||||
res = await client.parse_body(raw_response)
|
||||
log.append("DEBUG: stage=" + auth['type'] + " resulted in status=" + str(raw_response.status))
|
||||
return raw_response.status, res
|
||||
|
||||
async def do_dummy_stage(client, session, method, path, password):
|
||||
auth = {
|
||||
"type": "m.login.dummy",
|
||||
"session": session
|
||||
}
|
||||
log.append("DEBUG: attempt stage=" + auth['type'] + " for session="+ auth['session'])
|
||||
log.append(f"DEBUG: attempt stage={auth['type']} for session={auth['session']} with password={auth['password']}, method={method}")
|
||||
payload, headers = get_payload({"auth": auth})
|
||||
raw_response = await client.send(method, path, payload, headers)
|
||||
res = await client.parse_body(raw_response)
|
||||
log.append("DEBUG: stage=" + auth['type'] + " resulted in status=" + str(raw_response.status))
|
||||
log.append(f"DEBUG: stage={auth['type']} resulted in status={raw_response.status}")
|
||||
return raw_response.status, res
|
||||
|
||||
|
||||
async def do_dummy_stage(client, session, method, path, password):
|
||||
auth = {
|
||||
"type": "m.login.dummy",
|
||||
"session": session
|
||||
}
|
||||
log.append(f"DEBUG: attempt stage={auth['type']} for session={auth['session']}")
|
||||
payload, headers = get_payload({"auth": auth})
|
||||
raw_response = await client.send(method, path, payload, headers)
|
||||
res = await client.parse_body(raw_response)
|
||||
log.append(f"DEBUG: stage{auth['type']} resulted in status={raw_response.status}")
|
||||
return raw_response.status, res
|
||||
|
||||
|
||||
uia_stages = {
|
||||
"m.login.password": do_password_stage,
|
||||
"m.login.dummy": do_dummy_stage
|
||||
}
|
||||
|
||||
|
||||
# Picks the best compatible flow out of an array of flows
|
||||
def pick_flow(flows):
|
||||
supported_stages = uia_stages.keys()
|
||||
# reduces each flow to a boolean telling filter if the flow consists only out of compatible stages
|
||||
compatible_flows = [flow for flow in flows if all([stage in supported_stages for stage in flow['stages']])]
|
||||
compatible_flows = [flow for flow in flows if all(stage in supported_stages for stage in flow['stages'])]
|
||||
# the best flow is the one with the fewest stages, key= takes a function telling min() the weight of an entry
|
||||
best = min(compatible_flows, key=(lambda flow: len(flow['stages'])))
|
||||
return best
|
||||
|
||||
|
||||
async def run_module():
|
||||
result = dict(
|
||||
changed=False,
|
||||
)
|
||||
|
||||
module = AnsibleNioModule(user_logout=False)
|
||||
if not HAS_LIB:
|
||||
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||
|
||||
if module.check_mode:
|
||||
return result
|
||||
|
@ -134,6 +160,7 @@ async def run_module():
|
|||
|
||||
# Collect and check login information
|
||||
password = module.params['password']
|
||||
token = module.params['token']
|
||||
if password is None and token is None:
|
||||
await module.fail_json(msg="A PASSWORD has to be provided")
|
||||
|
||||
|
@ -149,21 +176,21 @@ async def run_module():
|
|||
raw_response = await client.send(method, path, {})
|
||||
res = await client.parse_body(raw_response)
|
||||
uia_session = res['session']
|
||||
log.append("DEBUG: begin UIA for session=" + uia_session)
|
||||
log.append(f"DEBUG: begin UIA for session={uia_session}")
|
||||
|
||||
# Figure out best compatible UIA login flow
|
||||
log.append("INFO: available flows: " + str(res['flows']))
|
||||
log.append(f"INFO: available flows: {res['flows']}")
|
||||
flow_to_attempt = pick_flow(res['flows'])
|
||||
log.append("INFO: picking flow: " + (" -> ".join(flow_to_attempt['stages'])))
|
||||
log.append(f"INFO: picking flow: {' -> '.join(flow_to_attempt['stages'])}")
|
||||
|
||||
# Attempt each stage in the flow
|
||||
for stage in flow_to_attempt['stages']:
|
||||
stage_status, stage_result = await uia_stages[stage](client, uia_session, method, path, password)
|
||||
if int(stage_status) == 401 and stage != (flow_to_attempt['stages'])[-1]:
|
||||
log.append("INFO: completed stage " + stage)
|
||||
log.append(f"INFO: completed stage {stage}")
|
||||
completed_stages = stage_result['completed']
|
||||
elif int(stage_status) == 200 and stage == (flow_to_attempt['stages'])[-1]:
|
||||
log.append("INFO: final stage completed " + stage)
|
||||
log.append(f"INFO: final stage completed {stage}")
|
||||
result['token'] = stage_result['access_token']
|
||||
result['device_id'] = stage_result['device_id']
|
||||
failed = False
|
||||
|
|
|
@ -16,7 +16,7 @@ ANSIBLE_METADATA = {
|
|||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
author: "Jadyn Emma Jäger (@jadyn.dev)"
|
||||
author: "Jadyn Emma Jäger (@jadyndev)"
|
||||
module: synapse_ratelimit
|
||||
short_description: Change a users rate-limits
|
||||
description:
|
||||
|
@ -26,31 +26,34 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
access_token:
|
||||
description:
|
||||
- Shared secret to authenticate registration request
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The fully qualified MXID of a __local__ user
|
||||
required: true
|
||||
type: str
|
||||
action:
|
||||
description:
|
||||
- Which (http) operation should be executed
|
||||
required: True
|
||||
required: false
|
||||
type: str
|
||||
choices: 'get', 'set', 'delete'
|
||||
choices: ['get', 'set', 'delete']
|
||||
default: 'get'
|
||||
messages_per_second:
|
||||
description:
|
||||
- Set the maximum messages per second (0 = disabled)
|
||||
required: False
|
||||
required: false
|
||||
type: int
|
||||
default: 0
|
||||
burst_count:
|
||||
description:
|
||||
- Set the maximum message burst (0 = disabled)
|
||||
required: False
|
||||
required: false
|
||||
type: int
|
||||
default: 0
|
||||
requirements: []
|
||||
|
@ -69,13 +72,21 @@ EXAMPLES = '''
|
|||
|
||||
RETURN = '''
|
||||
ratelimit:
|
||||
- burst_count: 5
|
||||
- messages_per_second: 10
|
||||
if a ratelimit is set, otherwise `ratelimit` is empty
|
||||
description: if a ratelimit is set, otherwise `ratelimit` is empty
|
||||
type: dict
|
||||
returned: success
|
||||
sample:
|
||||
burst_count: 5
|
||||
messages_per_second: 10
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.synapse import *
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from ansible_collections.famedly.matrix.plugins.module_utils.synapse import AdminApi, Exceptions, HAS_REQUESTS
|
||||
except ImportError:
|
||||
REQUESTS_IMPORT_ERROR = traceback.format_exc()
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -97,6 +108,9 @@ def main():
|
|||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_REQUESTS:
|
||||
module.fail_json(msg=missing_required_lib("requests"))
|
||||
|
||||
if module.check_mode:
|
||||
return result
|
||||
|
||||
|
@ -117,7 +131,7 @@ def main():
|
|||
result['ratelimit'] = synapse.ratelimit.delete(module.params['user_id'])
|
||||
result['changed'] = ratelimit != result['ratelimit']
|
||||
module.exit_json(**result)
|
||||
raise NotImplementedError("action {} is not implemented".format(action))
|
||||
raise NotImplementedError(f"action {action} is not implemented")
|
||||
except (Exceptions.HTTPException, Exceptions.MatrixException) as e:
|
||||
result['msg'] = str(e)
|
||||
module.fail_json(**result)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
|
@ -26,14 +27,17 @@ options:
|
|||
description:
|
||||
- URL of the homeserver, where the CS-API is reachable
|
||||
required: true
|
||||
type: str
|
||||
user_id:
|
||||
description:
|
||||
- The user id of the user
|
||||
required: true
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- The password to register with
|
||||
required: true
|
||||
type: str
|
||||
admin:
|
||||
description:
|
||||
- Whether or not the new user should be an admin
|
||||
|
@ -44,6 +48,7 @@ options:
|
|||
description:
|
||||
- Shared secret to authenticate registration request
|
||||
required: true
|
||||
type: str
|
||||
requirements: []
|
||||
'''
|
||||
|
||||
|
@ -57,21 +62,28 @@ EXAMPLES = '''
|
|||
shared_secret: "long secret string"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
import traceback
|
||||
import asyncio
|
||||
import hmac
|
||||
import hashlib
|
||||
import requests
|
||||
import traceback
|
||||
|
||||
# Check if all required libs can load
|
||||
LIB_IMP_ERR = None
|
||||
try:
|
||||
import requests
|
||||
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
REQUESTS_IMPORT_ERROR = traceback.format_exc()
|
||||
HAS_REQUESTS = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
def generate_mac(nonce, shared_secret, user, password, admin=False, user_type=None):
|
||||
|
||||
def generate_mac(nonce, shared_secret, user, password, admin=False, user_type=None):
|
||||
mac = hmac.new(
|
||||
key=shared_secret.encode('utf8'),
|
||||
digestmod=hashlib.sha1,
|
||||
key=shared_secret.encode('utf8'),
|
||||
digestmod=hashlib.sha1,
|
||||
)
|
||||
|
||||
mac.update(nonce.encode('utf8'))
|
||||
|
@ -87,6 +99,7 @@ def generate_mac(nonce, shared_secret, user, password, admin=False, user_type=No
|
|||
|
||||
return mac.hexdigest()
|
||||
|
||||
|
||||
async def run_module():
|
||||
module_args = dict(
|
||||
hs_url=dict(type='str', required=True),
|
||||
|
@ -105,18 +118,22 @@ async def run_module():
|
|||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_REQUESTS:
|
||||
module.fail_json(msg=missing_required_lib("requests"))
|
||||
|
||||
if module.check_mode:
|
||||
return result
|
||||
|
||||
failed = False
|
||||
|
||||
url = "{}/_synapse/admin/v1/register".format(module.params["hs_url"])
|
||||
url = f"{module.params['hs_url']}/_synapse/admin/v1/register"
|
||||
response = requests.get(url)
|
||||
if response.status_code != 200:
|
||||
result["msg"] = response.json()["error"]
|
||||
module.exit_json(**result)
|
||||
nonce = response.json()["nonce"]
|
||||
mac = generate_mac(nonce, module.params["shared_secret"], module.params["user_id"], module.params["password"], module.params["admin"])
|
||||
mac = generate_mac(nonce, module.params["shared_secret"], module.params["user_id"], module.params["password"],
|
||||
module.params["admin"])
|
||||
|
||||
data = {
|
||||
"nonce": nonce,
|
||||
|
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
ansible~=5.2.0
|
||||
matrix-nio~=0.19.0
|
||||
signedjson~=1.1.4
|
5
tests/sanity/generate-ignore.sh
Normal file
5
tests/sanity/generate-ignore.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
cd ../../
|
||||
while read -r line; do
|
||||
find plugins/modules -name "*.py" | xargs -I {} -n 1 printf "{} $line\n"
|
||||
done <"tests/sanity/ignore.template"
|
11
tests/sanity/ignore-2.12.txt
Normal file
11
tests/sanity/ignore-2.12.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
plugins/modules/matrix_signing_key.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/synapse_register.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_notification.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_state.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_room.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_uia_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/synapse_ratelimit.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_token_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_logout.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_member.py validate-modules:missing-gplv3-license # ignore license check
|
11
tests/sanity/ignore-2.13.txt
Normal file
11
tests/sanity/ignore-2.13.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
plugins/modules/matrix_signing_key.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/synapse_register.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_notification.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_state.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_room.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_uia_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/synapse_ratelimit.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_token_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_login.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_logout.py validate-modules:missing-gplv3-license # ignore license check
|
||||
plugins/modules/matrix_member.py validate-modules:missing-gplv3-license # ignore license check
|
1
tests/sanity/ignore.template
Normal file
1
tests/sanity/ignore.template
Normal file
|
@ -0,0 +1 @@
|
|||
validate-modules:missing-gplv3-license # ignore license check
|
Loading…
Reference in a new issue