mirror of
https://github.com/famedly/ansible-collection-matrix
synced 2024-11-10 05:34:16 +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:
|
stages:
|
||||||
|
- test
|
||||||
- build
|
- build
|
||||||
- publish
|
- 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:
|
build:
|
||||||
image: docker.io/alpine
|
image: docker.io/alpine
|
||||||
stage: build
|
stage: build
|
||||||
|
|
|
@ -1,93 +1,121 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
# (c) 2021-2022, Famedly GmbH
|
# (c) 2021-2022, Famedly GmbH
|
||||||
# GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl-3.0.txt)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
|
||||||
import traceback
|
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
|
LIB_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
from nio import *
|
from nio import AsyncClient, AsyncClientConfig, \
|
||||||
HAS_LIB = True
|
Api, \
|
||||||
|
LoginResponse, LoginError, \
|
||||||
|
LogoutResponse, LogoutError, \
|
||||||
|
RoomGetStateResponse, RoomGetStateError, \
|
||||||
|
RoomBanResponse, RoomBanError, \
|
||||||
|
RoomUnbanResponse, RoomUnbanError, \
|
||||||
|
RoomKickResponse, RoomKickError, \
|
||||||
|
RoomInviteResponse, RoomInviteError, \
|
||||||
|
RoomResolveAliasResponse, RoomResolveAliasError, \
|
||||||
|
JoinedRoomsResponse, JoinedRoomsError
|
||||||
|
HAS_LIB = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
LIB_IMP_ERR = traceback.format_exc()
|
LIB_IMP_ERR = traceback.format_exc()
|
||||||
HAS_LIB = False
|
HAS_LIB = False
|
||||||
|
|
||||||
class AnsibleNioModule():
|
|
||||||
def __init__(self,
|
class AnsibleNioModule:
|
||||||
custom_spec={},
|
def __init__(self,
|
||||||
|
custom_spec=None,
|
||||||
bypass_checks=False,
|
bypass_checks=False,
|
||||||
no_log=False,
|
no_log=False,
|
||||||
mutually_exclusive=[['password', 'token']],
|
mutually_exclusive=None,
|
||||||
required_together=None,
|
required_together=None,
|
||||||
required_one_of=[['password', 'token', 'key']],
|
required_one_of=None,
|
||||||
required_by={'password': 'user_id', 'key': 'user_id'},
|
required_by=None,
|
||||||
add_file_common_args=False,
|
add_file_common_args=False,
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
required_if=None,
|
required_if=None,
|
||||||
user_logout=True):
|
user_logout=True):
|
||||||
|
|
||||||
#If a user/password login is provided, should we logout when exiting?
|
if required_by is None:
|
||||||
self.user_logout=user_logout
|
required_by = {'password': 'user_id'}
|
||||||
|
|
||||||
#Create the Ansible module
|
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(
|
self.module = AnsibleModule(
|
||||||
argument_spec = AnsibleNioModule.__common_argument_spec(custom_spec),
|
argument_spec=AnsibleNioModule.__common_argument_spec(custom_spec),
|
||||||
bypass_checks=bypass_checks,
|
bypass_checks=bypass_checks,
|
||||||
no_log=no_log,
|
no_log=no_log,
|
||||||
mutually_exclusive=mutually_exclusive,
|
mutually_exclusive=mutually_exclusive,
|
||||||
required_together=required_together,
|
required_together=required_together,
|
||||||
required_one_of=required_one_of,
|
required_one_of=required_one_of,
|
||||||
add_file_common_args=add_file_common_args,
|
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_if=required_if,
|
||||||
required_by=required_by
|
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
|
# Make some values from the module easly accessible
|
||||||
#WARNING: We don't perform a version check!
|
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:
|
if not HAS_LIB:
|
||||||
self.module.fail_json(msg=missing_required_lib("matrix-nio"))
|
self.module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
|
|
||||||
async def matrix_login(self):
|
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:
|
if self.module.params['token'] is None:
|
||||||
self.client = AsyncClient(self.module.params['hs_url'], self.module.params['user_id'])
|
self.client = AsyncClient(self.module.params['hs_url'], self.module.params['user_id'])
|
||||||
login_response = await self.client.login(self.module.params['password'])
|
login_response = await self.client.login(password=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)
|
|
||||||
else:
|
else:
|
||||||
self.client = AsyncClient(self.module.params['hs_url'])
|
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):
|
async def matrix_logout(self):
|
||||||
if self.client.logged_in:
|
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):
|
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.matrix_logout()
|
||||||
await self.client.close()
|
await self.client.close()
|
||||||
self.module.exit_json(**result)
|
self.module.exit_json(**result)
|
||||||
|
|
||||||
async def fail_json(self, **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.matrix_logout()
|
||||||
await self.client.close()
|
await self.client.close()
|
||||||
self.module.fail_json(**result)
|
self.module.fail_json(**result)
|
||||||
|
@ -101,4 +129,3 @@ class AnsibleNioModule():
|
||||||
token=dict(type='str', required=False, no_log=True)
|
token=dict(type='str', required=False, no_log=True)
|
||||||
)
|
)
|
||||||
return {**argument_spec, **custom_spec}
|
return {**argument_spec, **custom_spec}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
# (c) 2021, Famedly GmbH
|
# (c) 2021, Famedly GmbH
|
||||||
|
@ -8,9 +7,19 @@ from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from typing import Union, Type
|
import traceback
|
||||||
import requests
|
|
||||||
import urllib.parse
|
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:
|
class AdminApi:
|
||||||
|
@ -31,39 +40,39 @@ class AdminApi:
|
||||||
# Make API request
|
# Make API request
|
||||||
def get(self, path: str) -> requests.Response:
|
def get(self, path: str) -> requests.Response:
|
||||||
response = requests.get(url=urllib.parse.urljoin(self.api_url, path),
|
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:
|
if response.status_code == 200:
|
||||||
return response
|
return response
|
||||||
if response.status_code == 500:
|
if response.status_code == 500:
|
||||||
raise Exceptions.MatrixException(
|
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:
|
else:
|
||||||
raise Exceptions.HTTPException(
|
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:
|
def post(self, path: str, **kwargs) -> requests.Response:
|
||||||
response = requests.post(url=urllib.parse.urljoin(self.api_url, path),
|
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:
|
if response.status_code == 200:
|
||||||
return response
|
return response
|
||||||
if response.status_code == 500:
|
if response.status_code == 500:
|
||||||
raise Exceptions.MatrixException(
|
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:
|
else:
|
||||||
raise Exceptions.HTTPException(
|
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:
|
def delete(self, path: str) -> requests.Response:
|
||||||
response = requests.delete(url=urllib.parse.urljoin(self.api_url, path),
|
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:
|
if response.status_code == 200:
|
||||||
return response
|
return response
|
||||||
if response.status_code == 500:
|
if response.status_code == 500:
|
||||||
raise Exceptions.MatrixException(
|
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:
|
else:
|
||||||
raise Exceptions.HTTPException(
|
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
|
@staticmethod
|
||||||
def url_encode(string: str) -> str:
|
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:
|
def set(self, user_id: str, messages_per_second: int = 0, burst_count: int = 0) -> dict:
|
||||||
user_id = AdminApi.url_encode(user_id)
|
user_id = AdminApi.url_encode(user_id)
|
||||||
return self.__parent.post(
|
return self.__parent.post(
|
||||||
self.API_PATH.format(user_id=user_id), json={"messages_per_second": messages_per_second,
|
self.API_PATH.format(user_id=user_id), json={"messages_per_second": messages_per_second,
|
||||||
"burst_count": burst_count}).json()
|
"burst_count": burst_count}).json()
|
||||||
|
|
||||||
def delete(self, user_id: str) -> dict:
|
def delete(self, user_id: str) -> dict:
|
||||||
user_id = AdminApi.url_encode(user_id)
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -26,14 +27,22 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
required: true
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
@ -56,10 +65,10 @@ device_id:
|
||||||
returned: When login was successful
|
returned: When login was successful
|
||||||
type: str
|
type: str
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
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():
|
async def run_module():
|
||||||
result = dict(
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -26,10 +27,22 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
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:
|
token:
|
||||||
description:
|
description:
|
||||||
- Authentication token for the API call
|
- Authentication token for the API call
|
||||||
required: true
|
required: false
|
||||||
|
type: str
|
||||||
requirements:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
@ -43,10 +56,11 @@ EXAMPLES = '''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
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():
|
async def run_module():
|
||||||
result = dict(
|
result = dict(
|
||||||
changed=False,
|
changed=False,
|
||||||
|
@ -61,5 +75,6 @@ async def run_module():
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(run_module())
|
asyncio.run(run_module())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'}
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
---
|
---
|
||||||
|
@ -20,32 +17,53 @@ author: "Johanna Dorothea Reichmann (@transcaffeine)"
|
||||||
module: matrix_member
|
module: matrix_member
|
||||||
short_description: Manage matrix room membership
|
short_description: Manage matrix room membership
|
||||||
description:
|
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:
|
options:
|
||||||
room_id:
|
hs_url:
|
||||||
description:
|
description:
|
||||||
- ID of the room to manage
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
user_ids:
|
|
||||||
description:
|
|
||||||
- List of matrix IDs to set their state
|
|
||||||
required: true
|
required: true
|
||||||
state:
|
type: str
|
||||||
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
|
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
@ -58,83 +76,97 @@ EXAMPLES = '''
|
||||||
room_id: "{{ matrix_room_id }}"
|
room_id: "{{ matrix_room_id }}"
|
||||||
state: member
|
state: member
|
||||||
user_ids:
|
user_ids:
|
||||||
- @user1:matrix.org
|
- "@user1:matrix.org"
|
||||||
- @user2:homeserver.tld
|
- "@user2:homeserver.tld"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
members:
|
members:
|
||||||
description: Dictionary of all members in the given room who are either invited or joined
|
description: Dictionary of all members in the given room who are either invited or joined
|
||||||
returned: When auth_token is valid
|
returned: When auth_token is valid
|
||||||
type: dict[str]
|
type: dict
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
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):
|
async def get_room_members(client, room_id, res):
|
||||||
member_resp = await client.room_get_state(room_id)
|
member_resp = await client.room_get_state(room_id)
|
||||||
|
|
||||||
if isinstance(member_resp, RoomGetStateError):
|
if isinstance(member_resp, RoomGetStateError):
|
||||||
res['msg'] = "Could not get room state for roomId={0}".format(room_id)
|
res['msg'] = f"Could not get room state for roomId={room_id}"
|
||||||
raise Exception()
|
raise NioOperationError(res['msg'])
|
||||||
else:
|
else:
|
||||||
return dict(list(map(lambda m: (m['state_key'],m['content']['membership']), filter(lambda e: e['type'] == 'm.room.member' and
|
return dict(list(map(lambda m: (m['state_key'], m['content']['membership']), filter(
|
||||||
e['content']['membership'] in ['invite', 'join', 'leave', 'ban'], member_resp.events))))
|
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):
|
async def ban_from_room(client, room_id, user_id, res):
|
||||||
ban_resp = await client.room_ban(room_id, user_id)
|
ban_resp = await client.room_ban(room_id, user_id)
|
||||||
if isinstance(ban_resp, RoomBanError):
|
if isinstance(ban_resp, RoomBanError):
|
||||||
res['msg'] = "Could not ban user={0} from roomId={1}".format(user_id, room_id)
|
res['msg'] = f"Could not ban user={user_id} from roomId={room_id}"
|
||||||
raise Exception()
|
raise NioOperationError(res['msg'])
|
||||||
res['changed'] = True
|
res['changed'] = True
|
||||||
res['banned'].append(user_id)
|
res['banned'].append(user_id)
|
||||||
|
|
||||||
|
|
||||||
async def unban_from_room(client, room_id, user_id, res):
|
async def unban_from_room(client, room_id, user_id, res):
|
||||||
ban_resp = await client.room_unban(room_id, user_id)
|
ban_resp = await client.room_unban(room_id, user_id)
|
||||||
if isinstance(ban_resp, RoomUnbanError):
|
if isinstance(ban_resp, RoomUnbanError):
|
||||||
res['msg'] = "Could not unban user={0} from roomId={1}".format(user_id, room_id)
|
res['msg'] = f"Could not unban user={user_id} from roomId={room_id}"
|
||||||
raise Exception()
|
raise NioOperationError(res['msg'])
|
||||||
res['changed'] = True
|
res['changed'] = True
|
||||||
res['unbanned'].append(user_id)
|
res['unbanned'].append(user_id)
|
||||||
|
|
||||||
|
|
||||||
async def kick_from_room(client, room_id, user_id, res):
|
async def kick_from_room(client, room_id, user_id, res):
|
||||||
kick_resp = await client.room_kick(room_id, user_id)
|
kick_resp = await client.room_kick(room_id, user_id)
|
||||||
if isinstance(kick_resp, RoomKickError):
|
if isinstance(kick_resp, RoomKickError):
|
||||||
res['msg'] = "Could not kick user={0} from roomId={1}".format(user_id, room_id)
|
res['msg'] = f"Could not kick user={user_id} from roomId={room_id}"
|
||||||
raise Exception()
|
raise NioOperationError(res['msg'])
|
||||||
res['changed'] = True
|
res['changed'] = True
|
||||||
res['kicked'].append(user_id)
|
res['kicked'].append(user_id)
|
||||||
|
|
||||||
|
|
||||||
async def invite_to_room(client, room_id, user_id, res):
|
async def invite_to_room(client, room_id, user_id, res):
|
||||||
invite_resp = await client.room_invite(room_id, user_id)
|
invite_resp = await client.room_invite(room_id, user_id)
|
||||||
if isinstance(invite_resp, RoomInviteError):
|
if isinstance(invite_resp, RoomInviteError):
|
||||||
res['msg'] = "Could not invite user={0} to roomId={1}".format(user_id, room_id)
|
res['msg'] = f"Could not invite user={user_id} to roomId={room_id}"
|
||||||
raise Exception()
|
raise NioOperationError(res['msg'])
|
||||||
res['changed'] = True
|
res['changed'] = True
|
||||||
res['invited'].append(user_id)
|
res['invited'].append(user_id)
|
||||||
|
|
||||||
|
|
||||||
async def run_module():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(state=dict(choices=['member', 'kicked', 'banned'], required=True),
|
||||||
state=dict(choices=['member', 'kicked', 'banned'], required=True),
|
room_id=dict(type='str', required=True),
|
||||||
room_id=dict(type='str', required=True),
|
user_ids=dict(type='list', required=True, elements='str'),
|
||||||
user_ids=dict(type='list', required=True, elements='str'),
|
exclusive=dict(type='bool', required=False, default=False))
|
||||||
exclusive=dict(type='bool', required=False, default=False),
|
|
||||||
)
|
|
||||||
|
|
||||||
result = dict(
|
result = dict(changed=False, banned=[], unbanned=[], kicked=[], invited=[], members=[], msg="", )
|
||||||
changed=False,
|
|
||||||
banned=[],
|
|
||||||
unbanned=[],
|
|
||||||
kicked=[],
|
|
||||||
invited=[],
|
|
||||||
members=[],
|
|
||||||
msg="",
|
|
||||||
)
|
|
||||||
|
|
||||||
module = AnsibleNioModule(module_args)
|
module = AnsibleNioModule(module_args)
|
||||||
|
if not HAS_LIB:
|
||||||
|
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
|
|
||||||
await module.matrix_login()
|
await module.matrix_login()
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
|
@ -151,10 +183,13 @@ async def run_module():
|
||||||
# Create client object
|
# Create client object
|
||||||
client = module.client
|
client = module.client
|
||||||
|
|
||||||
# Query all room members (invited users count as member, as they _can_ be in the room)
|
try:
|
||||||
room_members = await get_room_members(client, room_id, result)
|
# Query all room members (invited users count as member, as they _can_ be in the room)
|
||||||
present_members = {m: s for m, s in room_members.items() if s in ['join', 'invite']}.keys()
|
room_members = await get_room_members(client, room_id, result)
|
||||||
banned_members = {m: s for m, s in room_members.items() if s == 'ban'}.keys()
|
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']:
|
if not module.params['exclusive']:
|
||||||
# Handle non-exclusive invite|kick|ban
|
# Handle non-exclusive invite|kick|ban
|
||||||
|
@ -170,7 +205,7 @@ async def run_module():
|
||||||
await unban_from_room(client, room_id, user_id, result)
|
await unban_from_room(client, room_id, user_id, result)
|
||||||
elif action == 'banned' and user_id not in banned_members:
|
elif action == 'banned' and user_id not in banned_members:
|
||||||
await ban_from_room(client, room_id, user_id, result)
|
await ban_from_room(client, room_id, user_id, result)
|
||||||
except:
|
except NioOperationError:
|
||||||
await module.fail_json(**result)
|
await module.fail_json(**result)
|
||||||
else:
|
else:
|
||||||
# Handle exclusive mode: get state and make lists of users to be kicked or invited
|
# 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)
|
await invite_to_room(client, room_id, user_id, result)
|
||||||
for user_id in to_kick:
|
for user_id in to_kick:
|
||||||
await kick_from_room(client, room_id, user_id, result)
|
await kick_from_room(client, room_id, user_id, result)
|
||||||
except:
|
except NioOperationError:
|
||||||
await module.fail_json(**result)
|
await module.fail_json(**result)
|
||||||
|
|
||||||
# Get all current members from the room
|
# Get all current members from the room
|
||||||
try:
|
try:
|
||||||
room_members_after = await get_room_members(client, room_id, result)
|
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()
|
result['members'] = {m: s for m, s in room_members_after.items() if s in ['join', 'invite']}.keys()
|
||||||
except:
|
except NioOperationError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await module.exit_json(**result)
|
await module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(run_module())
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -21,33 +22,43 @@ module: matrix_notification
|
||||||
short_description: Send notifications to matrix
|
short_description: Send notifications to matrix
|
||||||
description:
|
description:
|
||||||
- This module sends html formatted notifications to matrix rooms.
|
- This module sends html formatted notifications to matrix rooms.
|
||||||
version_added: "2.8"
|
version_added: "2.8.0"
|
||||||
options:
|
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:
|
hs_url:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
token:
|
type: str
|
||||||
description:
|
|
||||||
- Authentication token for the API call. If provided, user_id and password are not required
|
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
@ -73,16 +84,27 @@ EXAMPLES = '''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
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():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
msg_plain=dict(type='str', required=True),
|
msg_plain=dict(type='str', required=True),
|
||||||
msg_html=dict(type='str', required=True),
|
msg_html=dict(type='str', required=True),
|
||||||
room_id=dict(type='str', required=True),
|
room_id=dict(type='str', required=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
result = dict(
|
result = dict(
|
||||||
changed=False,
|
changed=False,
|
||||||
|
@ -90,15 +112,17 @@ async def run_module():
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleNioModule(module_args)
|
module = AnsibleNioModule(module_args)
|
||||||
|
if not HAS_LIB:
|
||||||
|
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
await module.matrix_login()
|
await module.matrix_login()
|
||||||
client = module.client
|
client = module.client
|
||||||
|
|
||||||
# send message
|
# send message
|
||||||
await client.room_send(
|
response = await client.room_send(
|
||||||
room_id=module.params['room_id'],
|
room_id=module.params['room_id'],
|
||||||
message_type="m.room.message",
|
message_type="m.room.message",
|
||||||
content={
|
content={
|
||||||
|
@ -108,9 +132,12 @@ async def run_module():
|
||||||
"formatted_body": module.params['msg_html'],
|
"formatted_body": module.params['msg_html'],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if isinstance(response, RoomSendError):
|
||||||
|
await module.fail_json(**result)
|
||||||
|
|
||||||
await module.exit_json(**result)
|
await module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(run_module())
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -20,26 +21,37 @@ author: "Jan Christian Grünhage (@jcgruenhage)"
|
||||||
module: matrix_room
|
module: matrix_room
|
||||||
short_description: Join/Create matrix room
|
short_description: Join/Create matrix room
|
||||||
description:
|
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:
|
options:
|
||||||
alias:
|
|
||||||
description:
|
|
||||||
- Alias of the room to join/create
|
|
||||||
required: true
|
|
||||||
hs_url:
|
hs_url:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
token:
|
type: str
|
||||||
description:
|
|
||||||
- Authentication token for the API call. If provided, user_id and password are not required
|
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -55,12 +67,28 @@ RETURN = '''
|
||||||
room_id:
|
room_id:
|
||||||
description: ID of the room
|
description: ID of the room
|
||||||
type: str
|
type: str
|
||||||
sample: !asdfbuiarbk213e479asf:server.tld
|
returned: success
|
||||||
|
sample: "!asdfbuiarbk213e479asf:server.tld"
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
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():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
|
@ -73,6 +101,9 @@ async def run_module():
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleNioModule(module_args)
|
module = AnsibleNioModule(module_args)
|
||||||
|
if not HAS_LIB:
|
||||||
|
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
|
|
||||||
await module.matrix_login()
|
await module.matrix_login()
|
||||||
client = module.client
|
client = module.client
|
||||||
|
|
||||||
|
@ -87,7 +118,7 @@ async def run_module():
|
||||||
rooms_resp = await client.joined_rooms()
|
rooms_resp = await client.joined_rooms()
|
||||||
if isinstance(rooms_resp, JoinedRoomsError):
|
if isinstance(rooms_resp, JoinedRoomsError):
|
||||||
failed = True
|
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:
|
elif room_id_resp.room_id in rooms_resp.rooms:
|
||||||
result = {"room_id": room_id_resp.room_id, "changed": False}
|
result = {"room_id": room_id_resp.room_id, "changed": False}
|
||||||
else:
|
else:
|
||||||
|
@ -99,7 +130,7 @@ async def run_module():
|
||||||
result = {"room_id": join_resp.room_id, "changed": True}
|
result = {"room_id": join_resp.room_id, "changed": True}
|
||||||
else:
|
else:
|
||||||
failed = True
|
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:
|
else:
|
||||||
# Get local part of alias
|
# Get local part of alias
|
||||||
local_part_regex = re.search("#([^:]*):(.*)", module.params['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}
|
result = {"room_id": create_room_resp.room_id, "changed": True}
|
||||||
else:
|
else:
|
||||||
failed = True
|
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:
|
if failed:
|
||||||
await module.fail_json(**result)
|
await module.fail_json(**result)
|
||||||
else:
|
else:
|
||||||
await module.exit_json(**result)
|
await module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(run_module())
|
asyncio.run(run_module())
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,65 @@
|
||||||
#!/bin/python3
|
#!/usr/bin/python
|
||||||
# Copyright: (c) 2018, Emmanouil Kampitakis <info@kampitakis.de>
|
# Copyright: (c) 2018
|
||||||
# Apache 2.0
|
# 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
|
import os
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
import traceback
|
||||||
from signedjson import key
|
|
||||||
|
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):
|
def write_signing_key(path):
|
||||||
with open(path,'w') as file:
|
with open(path, 'w') as file:
|
||||||
key.write_signing_keys(
|
key.write_signing_keys(
|
||||||
file,
|
file,
|
||||||
[key.generate_signing_key('first')]
|
[key.generate_signing_key('first')]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_module():
|
def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
path=dict(type='str', required=True),
|
path=dict(type='str', required=True),
|
||||||
|
@ -29,6 +76,9 @@ def run_module():
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not HAS_LIB:
|
||||||
|
module.fail_json(msg=missing_required_lib("signedjson"))
|
||||||
|
|
||||||
signing_key_path = module.params['path']
|
signing_key_path = module.params['path']
|
||||||
|
|
||||||
signing_key_exists = os.path.isfile(signing_key_path)
|
signing_key_exists = os.path.isfile(signing_key_path)
|
||||||
|
@ -37,12 +87,17 @@ def run_module():
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
module.exit_json(**result)
|
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)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
run_module()
|
run_module()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -22,36 +23,47 @@ short_description: Set matrix room state
|
||||||
description:
|
description:
|
||||||
- This module sets matrix room state idempotently
|
- This module sets matrix room state idempotently
|
||||||
options:
|
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:
|
hs_url:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
token:
|
type: str
|
||||||
description:
|
|
||||||
- Authentication token for the API call. If provided, user_id and password are not required
|
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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)
|
- matrix-client (Python library)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -79,14 +91,27 @@ event_id:
|
||||||
type: str
|
type: str
|
||||||
sample: $Het2Dv7EEDFNJNgY-ehLSUrdqMo8JOxZDCMnuQPSNfo
|
sample: $Het2Dv7EEDFNJNgY-ehLSUrdqMo8JOxZDCMnuQPSNfo
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
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():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
event_type=dict(type='str', required=True),
|
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),
|
content=dict(type='dict', required=True),
|
||||||
room_id=dict(type='str', required=True)
|
room_id=dict(type='str', required=True)
|
||||||
)
|
)
|
||||||
|
@ -97,6 +122,8 @@ async def run_module():
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleNioModule(module_args)
|
module = AnsibleNioModule(module_args)
|
||||||
|
if not HAS_LIB:
|
||||||
|
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
await module.matrix_login()
|
await module.matrix_login()
|
||||||
client = module.client
|
client = module.client
|
||||||
|
|
||||||
|
@ -110,7 +137,7 @@ async def run_module():
|
||||||
rooms_resp = await client.joined_rooms()
|
rooms_resp = await client.joined_rooms()
|
||||||
if isinstance(rooms_resp, JoinedRoomsError):
|
if isinstance(rooms_resp, JoinedRoomsError):
|
||||||
failed = True
|
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:
|
elif module.params['room_id'] not in rooms_resp.rooms:
|
||||||
failed = True
|
failed = True
|
||||||
result = {"msg": "Not in the room you're trying to set state for."}
|
result = {"msg": "Not in the room you're trying to set state for."}
|
||||||
|
@ -138,13 +165,14 @@ async def run_module():
|
||||||
# Else, fail
|
# Else, fail
|
||||||
else:
|
else:
|
||||||
failed = True
|
failed = True
|
||||||
result = {"msg": "Couldn't set state: {error}".format(error=send_resp)}
|
result = {"msg": f"Couldn't set state: {send_resp}"}
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
await module.fail_json(**result)
|
await module.fail_json(**result)
|
||||||
else:
|
else:
|
||||||
await module.exit_json(**result)
|
await module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(run_module())
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -17,7 +18,7 @@ ANSIBLE_METADATA = {
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
---
|
---
|
||||||
author: "Jan Christian Grünhage"
|
author: "Jan Christian Grünhage (@jcgruenhage)"
|
||||||
module: matrix_token_login
|
module: matrix_token_login
|
||||||
short_description: Use com.famedly.token based logins to obtain an access token
|
short_description: Use com.famedly.token based logins to obtain an access token
|
||||||
description:
|
description:
|
||||||
|
@ -27,17 +28,30 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
required: true
|
required: false
|
||||||
key:
|
type: str
|
||||||
|
password:
|
||||||
description:
|
description:
|
||||||
- The key to sign the log in token with
|
- The password to log in with
|
||||||
required: true
|
required: false
|
||||||
|
type: str
|
||||||
|
token:
|
||||||
|
description:
|
||||||
|
- Authentication token for the API call
|
||||||
|
required: false
|
||||||
|
type: str
|
||||||
admin:
|
admin:
|
||||||
description:
|
description:
|
||||||
- Whether to set the user as admin during login
|
- Whether to set the user as admin during login
|
||||||
|
type: bool
|
||||||
|
key:
|
||||||
|
description: Login key to use
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
requirements:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
- jwcrypto (Python library)
|
- jwcrypto (Python library)
|
||||||
|
@ -62,18 +76,38 @@ device_id:
|
||||||
returned: When login was successful
|
returned: When login was successful
|
||||||
type: str
|
type: str
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
from jwcrypto import jwt, jwk
|
|
||||||
import time
|
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():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
key=dict(type='str', required=True),
|
key=dict(type='str', required=True, no_log=True),
|
||||||
admin=dict(type='bool', required=False),
|
admin=dict(type='bool', required=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -83,6 +117,10 @@ async def run_module():
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleNioModule(module_args, user_logout=False)
|
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:
|
if module.check_mode:
|
||||||
return result
|
return result
|
||||||
|
@ -112,9 +150,9 @@ async def run_module():
|
||||||
}
|
}
|
||||||
key = jwk.JWK(**key)
|
key = jwk.JWK(**key)
|
||||||
claims = {
|
claims = {
|
||||||
"iss": "Matrix UIA Login Ansible Module",
|
"iss": "Matrix UIA Login Ansible Module",
|
||||||
"sub": client.user,
|
"sub": client.user,
|
||||||
"exp": int(time.time()) + 60 * 30,
|
"exp": int(time.time()) + 60 * 30,
|
||||||
}
|
}
|
||||||
|
|
||||||
if admin is not None:
|
if admin is not None:
|
||||||
|
@ -126,12 +164,12 @@ async def run_module():
|
||||||
token.make_signed_token(key)
|
token.make_signed_token(key)
|
||||||
|
|
||||||
auth = {
|
auth = {
|
||||||
"type": "com.famedly.login.token",
|
"type": "com.famedly.login.token",
|
||||||
"identifier" : {
|
"identifier": {
|
||||||
"type": "m.id.user",
|
"type": "m.id.user",
|
||||||
"user": client.user
|
"user": client.user
|
||||||
},
|
},
|
||||||
"token": token.serialize()
|
"token": token.serialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = json.dumps(auth)
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -26,14 +27,22 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
required: true
|
required: false
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to log in with
|
- 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:
|
requirements:
|
||||||
- matrix-nio (Python library)
|
- matrix-nio (Python library)
|
||||||
'''
|
'''
|
||||||
|
@ -56,14 +65,24 @@ device_id:
|
||||||
returned: When login was successful
|
returned: When login was successful
|
||||||
type: str
|
type: str
|
||||||
'''
|
'''
|
||||||
import traceback
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import reduce
|
|
||||||
import json
|
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 = []
|
log = []
|
||||||
|
|
||||||
|
|
||||||
def get_payload(data):
|
def get_payload(data):
|
||||||
payload = json.dumps(data)
|
payload = json.dumps(data)
|
||||||
payload_length = len(payload)
|
payload_length = len(payload)
|
||||||
|
@ -73,55 +92,62 @@ def get_payload(data):
|
||||||
}
|
}
|
||||||
return payload, headers
|
return payload, headers
|
||||||
|
|
||||||
|
|
||||||
async def do_password_stage(client, session, method, path, password):
|
async def do_password_stage(client, session, method, path, password):
|
||||||
auth = {
|
auth = {
|
||||||
"type": "m.login.password",
|
"type": "m.login.password",
|
||||||
"identifier" : {
|
"identifier": {
|
||||||
"type": "m.id.user",
|
"type": "m.id.user",
|
||||||
"user": client.user
|
"user": client.user
|
||||||
},
|
},
|
||||||
"session": session,
|
"session": session,
|
||||||
"password": password
|
"password": password
|
||||||
}
|
}
|
||||||
log.append("DEBUG: attempt stage=" + auth['type'] + " for session="+ auth['session'] + " with password="+auth['password'] + ", method=" + method)
|
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))
|
|
||||||
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'])
|
|
||||||
payload, headers = get_payload({"auth": auth})
|
payload, headers = get_payload({"auth": auth})
|
||||||
raw_response = await client.send(method, path, payload, headers)
|
raw_response = await client.send(method, path, payload, headers)
|
||||||
res = await client.parse_body(raw_response)
|
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
|
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 = {
|
uia_stages = {
|
||||||
"m.login.password": do_password_stage,
|
"m.login.password": do_password_stage,
|
||||||
"m.login.dummy": do_dummy_stage
|
"m.login.dummy": do_dummy_stage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Picks the best compatible flow out of an array of flows
|
# Picks the best compatible flow out of an array of flows
|
||||||
def pick_flow(flows):
|
def pick_flow(flows):
|
||||||
supported_stages = uia_stages.keys()
|
supported_stages = uia_stages.keys()
|
||||||
# reduces each flow to a boolean telling filter if the flow consists only out of compatible stages
|
# 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
|
# 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'])))
|
best = min(compatible_flows, key=(lambda flow: len(flow['stages'])))
|
||||||
return best
|
return best
|
||||||
|
|
||||||
|
|
||||||
async def run_module():
|
async def run_module():
|
||||||
result = dict(
|
result = dict(
|
||||||
changed=False,
|
changed=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleNioModule(user_logout=False)
|
module = AnsibleNioModule(user_logout=False)
|
||||||
|
if not HAS_LIB:
|
||||||
|
await module.fail_json(msg=missing_required_lib("matrix-nio"))
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
return result
|
return result
|
||||||
|
@ -134,6 +160,7 @@ async def run_module():
|
||||||
|
|
||||||
# Collect and check login information
|
# Collect and check login information
|
||||||
password = module.params['password']
|
password = module.params['password']
|
||||||
|
token = module.params['token']
|
||||||
if password is None and token is None:
|
if password is None and token is None:
|
||||||
await module.fail_json(msg="A PASSWORD has to be provided")
|
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, {})
|
raw_response = await client.send(method, path, {})
|
||||||
res = await client.parse_body(raw_response)
|
res = await client.parse_body(raw_response)
|
||||||
uia_session = res['session']
|
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
|
# 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'])
|
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
|
# Attempt each stage in the flow
|
||||||
for stage in flow_to_attempt['stages']:
|
for stage in flow_to_attempt['stages']:
|
||||||
stage_status, stage_result = await uia_stages[stage](client, uia_session, method, path, password)
|
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]:
|
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']
|
completed_stages = stage_result['completed']
|
||||||
elif int(stage_status) == 200 and stage == (flow_to_attempt['stages'])[-1]:
|
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['token'] = stage_result['access_token']
|
||||||
result['device_id'] = stage_result['device_id']
|
result['device_id'] = stage_result['device_id']
|
||||||
failed = False
|
failed = False
|
||||||
|
|
|
@ -16,7 +16,7 @@ ANSIBLE_METADATA = {
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
---
|
---
|
||||||
author: "Jadyn Emma Jäger (@jadyn.dev)"
|
author: "Jadyn Emma Jäger (@jadyndev)"
|
||||||
module: synapse_ratelimit
|
module: synapse_ratelimit
|
||||||
short_description: Change a users rate-limits
|
short_description: Change a users rate-limits
|
||||||
description:
|
description:
|
||||||
|
@ -26,31 +26,34 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
access_token:
|
access_token:
|
||||||
description:
|
description:
|
||||||
- Shared secret to authenticate registration request
|
- Shared secret to authenticate registration request
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The fully qualified MXID of a __local__ user
|
- The fully qualified MXID of a __local__ user
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
action:
|
action:
|
||||||
description:
|
description:
|
||||||
- Which (http) operation should be executed
|
- Which (http) operation should be executed
|
||||||
required: True
|
required: false
|
||||||
type: str
|
type: str
|
||||||
choices: 'get', 'set', 'delete'
|
choices: ['get', 'set', 'delete']
|
||||||
default: 'get'
|
default: 'get'
|
||||||
messages_per_second:
|
messages_per_second:
|
||||||
description:
|
description:
|
||||||
- Set the maximum messages per second (0 = disabled)
|
- Set the maximum messages per second (0 = disabled)
|
||||||
required: False
|
required: false
|
||||||
type: int
|
type: int
|
||||||
default: 0
|
default: 0
|
||||||
burst_count:
|
burst_count:
|
||||||
description:
|
description:
|
||||||
- Set the maximum message burst (0 = disabled)
|
- Set the maximum message burst (0 = disabled)
|
||||||
required: False
|
required: false
|
||||||
type: int
|
type: int
|
||||||
default: 0
|
default: 0
|
||||||
requirements: []
|
requirements: []
|
||||||
|
@ -69,13 +72,21 @@ EXAMPLES = '''
|
||||||
|
|
||||||
RETURN = '''
|
RETURN = '''
|
||||||
ratelimit:
|
ratelimit:
|
||||||
- burst_count: 5
|
description: if a ratelimit is set, otherwise `ratelimit` is empty
|
||||||
- messages_per_second: 10
|
type: dict
|
||||||
if a ratelimit is set, otherwise `ratelimit` is empty
|
returned: success
|
||||||
|
sample:
|
||||||
|
burst_count: 5
|
||||||
|
messages_per_second: 10
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible_collections.famedly.matrix.plugins.module_utils.synapse import *
|
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():
|
def main():
|
||||||
|
@ -97,6 +108,9 @@ def main():
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not HAS_REQUESTS:
|
||||||
|
module.fail_json(msg=missing_required_lib("requests"))
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -117,7 +131,7 @@ def main():
|
||||||
result['ratelimit'] = synapse.ratelimit.delete(module.params['user_id'])
|
result['ratelimit'] = synapse.ratelimit.delete(module.params['user_id'])
|
||||||
result['changed'] = ratelimit != result['ratelimit']
|
result['changed'] = ratelimit != result['ratelimit']
|
||||||
module.exit_json(**result)
|
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:
|
except (Exceptions.HTTPException, Exceptions.MatrixException) as e:
|
||||||
result['msg'] = str(e)
|
result['msg'] = str(e)
|
||||||
module.fail_json(**result)
|
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)
|
# 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)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
ANSIBLE_METADATA = {
|
||||||
|
@ -26,14 +27,17 @@ options:
|
||||||
description:
|
description:
|
||||||
- URL of the homeserver, where the CS-API is reachable
|
- URL of the homeserver, where the CS-API is reachable
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
user_id:
|
user_id:
|
||||||
description:
|
description:
|
||||||
- The user id of the user
|
- The user id of the user
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- The password to register with
|
- The password to register with
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
admin:
|
admin:
|
||||||
description:
|
description:
|
||||||
- Whether or not the new user should be an admin
|
- Whether or not the new user should be an admin
|
||||||
|
@ -44,6 +48,7 @@ options:
|
||||||
description:
|
description:
|
||||||
- Shared secret to authenticate registration request
|
- Shared secret to authenticate registration request
|
||||||
required: true
|
required: true
|
||||||
|
type: str
|
||||||
requirements: []
|
requirements: []
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -57,21 +62,28 @@ EXAMPLES = '''
|
||||||
shared_secret: "long secret string"
|
shared_secret: "long secret string"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
'''
|
|
||||||
import traceback
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import hmac
|
import hmac
|
||||||
import hashlib
|
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
|
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(
|
mac = hmac.new(
|
||||||
key=shared_secret.encode('utf8'),
|
key=shared_secret.encode('utf8'),
|
||||||
digestmod=hashlib.sha1,
|
digestmod=hashlib.sha1,
|
||||||
)
|
)
|
||||||
|
|
||||||
mac.update(nonce.encode('utf8'))
|
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()
|
return mac.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
async def run_module():
|
async def run_module():
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
hs_url=dict(type='str', required=True),
|
hs_url=dict(type='str', required=True),
|
||||||
|
@ -105,18 +118,22 @@ async def run_module():
|
||||||
supports_check_mode=True
|
supports_check_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not HAS_REQUESTS:
|
||||||
|
module.fail_json(msg=missing_required_lib("requests"))
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
failed = False
|
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)
|
response = requests.get(url)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
result["msg"] = response.json()["error"]
|
result["msg"] = response.json()["error"]
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
nonce = response.json()["nonce"]
|
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 = {
|
data = {
|
||||||
"nonce": nonce,
|
"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