diff --git a/modules/restricted-guests/synapse/README.md b/modules/restricted-guests/synapse/README.md index 11fbc70afe..e3ed2ccac8 100644 --- a/modules/restricted-guests/synapse/README.md +++ b/modules/restricted-guests/synapse/README.md @@ -41,6 +41,17 @@ The module provides (optional) configuration options: - `enable_user_reaper` - if true, the module disables all users that are older than the configured expiration time. Default: `true`. - `user_expiration_seconds` - the expiration time in seconds when a guest user expires after their creation. Default: `86400` (=24 hours). +If matrix-authentication-service (MAS) is configured, the module will need to +interface with it in order to register/deactivate users. Provide the below +options in order to give the module access to [MAS' Admin +API](https://element-hq.github.io/matrix-authentication-service/topics/admin-api.html). + +- `mas` - optional configuration for Matrix Authentication Service (MAS). When set, the module creates users via MAS' admin API. + - `admin_api_base_url` - Base URL for MAS' admin API (e.g. `https://mas.example.org`). Trailing slashes will be automatically stripped. + - `oauth_base_url` - Base URL for MAS' OAuth endpoints (defaults to `admin_api_base_url` if not set). Trailing slashes will be automatically stripped. + - `client_id` - client ID for the automated tool. Must be a valid [ULID](https://github.com/ulid/spec). Generate one [here](https://ulidtools.com/). + - `client_secret` - client secret for the automated tool. Ideally long and cryptographically secure. Keep it a secret! + Example configuration: ```yaml @@ -49,6 +60,37 @@ modules: config: # Use a german suffix display_name_suffix: " (Gast)" + # The below is required if using MAS + mas: + admin_api_base_url: https://mas.example.org + oauth_base_url: https://mas.example.org + # The `client_id` must be a valid ULID: + # https://github.com/ulid/spec + # Generate ULID's easily at: + # https://ulidtools.com/ + client_id: 000000000000000000000G0EST + client_secret: your-client-secret +``` + +Enable [the Admin API on a MAS +listener](https://element-hq.github.io/matrix-authentication-service/topics/admin-api.html#enabling-the-api). +Then, add the following to your MAS config file: + +```yaml +policy: + data: + admin_clients: + - 000000000000000000000G0EST + +# ... + +clients: + # The `client_id` must be a valid ULID https://github.com/ulid/spec + # Generate ULID's easily at: https://ulidtools.com/ + - client_id: 000000000000000000000G0EST + # The guest module uses the client_secret_basic authentication method. + client_auth_method: client_secret_basic + client_secret: your-client-secret ``` ## Production installation diff --git a/modules/restricted-guests/synapse/synapse_guest_module/config.py b/modules/restricted-guests/synapse/synapse_guest_module/config.py index a0ddbe569a..5e54f2f642 100644 --- a/modules/restricted-guests/synapse/synapse_guest_module/config.py +++ b/modules/restricted-guests/synapse/synapse_guest_module/config.py @@ -7,12 +7,24 @@ # Originally licensed under the Apache License, Version 2.0: # . +from typing import Optional + import attr +@attr.s(frozen=True, auto_attribs=True) +class MasConfig: + admin_api_base_url: str + oauth_base_url: str + client_id: str + # TODO: Add a filepath option for the secret as well. + client_secret: str + + @attr.s(frozen=True, auto_attribs=True) class GuestModuleConfig: user_id_prefix: str display_name_suffix: str enable_user_reaper: bool user_expiration_seconds: int + mas: Optional[MasConfig] = None diff --git a/modules/restricted-guests/synapse/synapse_guest_module/guest_module.py b/modules/restricted-guests/synapse/synapse_guest_module/guest_module.py index 156012c35d..174c3474f6 100644 --- a/modules/restricted-guests/synapse/synapse_guest_module/guest_module.py +++ b/modules/restricted-guests/synapse/synapse_guest_module/guest_module.py @@ -8,7 +8,7 @@ # . import logging -from typing import Any, Dict, Literal, Tuple, Union +from typing import Any, Dict, Literal, Optional, Tuple, Union from synapse.module_api import ( NOT_SPAM, @@ -21,7 +21,8 @@ from synapse.module_api import ( from synapse.module_api.errors import ConfigError from synapse.types import UserID -from synapse_guest_module.config import GuestModuleConfig +from synapse_guest_module.config import GuestModuleConfig, MasConfig +from synapse_guest_module.mas_admin_client import MasAdminClient from synapse_guest_module.guest_registration_servlet import GuestRegistrationServlet from synapse_guest_module.guest_user_reaper import GuestUserReaper @@ -33,7 +34,12 @@ class GuestModule: self._api = api self._config = config - self.registration_servlet = GuestRegistrationServlet(config, api) + mas_admin_client = ( + MasAdminClient(api, config.mas) if config.mas is not None else None + ) + self.registration_servlet = GuestRegistrationServlet( + config, api, mas_admin_client + ) self._api.register_web_resource( "/_synapse/client/register_guest", self.registration_servlet ) @@ -81,11 +87,43 @@ class GuestModule: "Config option 'user_expiration_seconds' must be a number" ) + mas_config = config.get("mas") + mas: Optional[MasConfig] = None + if mas_config is not None: + if not isinstance(mas_config, dict): + raise ConfigError("Config option 'mas' must be an object") + + admin_api_base_url = mas_config.get("admin_api_base_url") + if not isinstance(admin_api_base_url, str) or len(admin_api_base_url.strip()) == 0: + raise ConfigError("Config option 'mas.admin_api_base_url' is required and must be a string") + + oauth_base_url = mas_config.get("oauth_base_url", admin_api_base_url) + if not isinstance(oauth_base_url, str) or len(oauth_base_url.strip()) == 0: + raise ConfigError( + "Config option 'mas.oauth_base_url' must be a string" + ) + + client_id = mas_config.get("client_id") + if not isinstance(client_id, str) or len(client_id.strip()) == 0: + raise ConfigError("Config option 'mas.client_id' is required and must be a string") + + client_secret = mas_config.get("client_secret") + if not isinstance(client_secret, str) or len(client_secret.strip()) == 0: + raise ConfigError("Config option 'mas.client_secret' is required and must be a string") + + mas = MasConfig( + admin_api_base_url.strip(), + oauth_base_url.strip(), + client_id.strip(), + client_secret.strip(), + ) + return GuestModuleConfig( user_id_prefix, display_name_suffix, enable_user_reaper, user_expiration_seconds, + mas, ) async def profile_update(