From 36a600c4fbbd21ed84027ae0e2c1737d3b7d494d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Wed, 20 Nov 2024 10:13:49 +0100 Subject: [PATCH] feat: make the SCIM IPD id configurable --- synapse/config/experimental.py | 30 ++++++++++++++++++++++++------ synapse/rest/scim.py | 19 +++++++++++++------ tests/rest/test_scim.py | 18 +++++++++++++----- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 73d77718b6..ddc6666e92 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -256,6 +256,19 @@ class MSC3866Config: require_approval_for_new_accounts: bool = False +@attr.s(auto_attribs=True, frozen=True, slots=True) +class MSC4098Config: + """Configuration for MSC4098 (SCIM provisioning)""" + + # Whether the SCIM provisioning API is enabled. + enabled: bool = False + + # The ID of the IDP that will be associated with the SCIM 'externalId' parameter. + # This should be one of the values used in the SSO config. + # If unset, a default '__scim__' id will be used. + idp_id: Optional[str] = None + + class ExperimentalConfig(Config): """Config section for enabling experimental features""" @@ -416,13 +429,18 @@ class ExperimentalConfig(Config): ) # MSC4098: SCIM provisioning API - self.msc4098_enabled = experimental.get("msc4098", False) - if self.msc4098_enabled and self.msc3861.enabled: + try: + self.msc4098 = MSC4098Config(**experimental.get("msc4098", {})) + if self.msc4098.enabled and self.msc3861.enabled: + raise ConfigError( + "MSC3861 and MSC4098 are mutually exclusive. Please disable one or the" + "other.", + ("experimental", "msc4098"), + ) + except ValueError as exc: raise ConfigError( - "MSC3861 and MSC4098 are mutually exclusive. Please disable one or the" - "other.", - ("experimental", "msc4098"), - ) + "Invalid MSC4098 configuration", ("experimental", "msc4098") + ) from exc # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code self.msc4108_enabled = experimental.get("msc4108_enabled", False) diff --git a/synapse/rest/scim.py b/synapse/rest/scim.py index 923bb0cbc6..773e169eb1 100644 --- a/synapse/rest/scim.py +++ b/synapse/rest/scim.py @@ -84,7 +84,7 @@ if TYPE_CHECKING: from synapse.server import HomeServer SCIM_PREFIX = "/_synapse/admin/scim/v2" -SCIM_IDP_ID = "__scim__" +SCIM_DEFAULT_IDP_ID = "__scim__" logger = logging.getLogger(__name__) @@ -99,7 +99,7 @@ class SCIMResource(JsonResource): def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - if not hs.config.experimental.msc4098_enabled: + if not hs.config.experimental.msc4098.enabled: return SchemaListServlet(hs).register(http_server) @@ -156,9 +156,10 @@ class SCIMServlet(RestServlet): async def get_scim_external_id(self, user_id: str) -> Optional[str]: """Read the external id stored in the special SCIM IDP.""" + scim_idp_id = self.hs.config.experimental.msc4098.idp_id or SCIM_DEFAULT_IDP_ID external_ids = await self.store.get_external_ids_by_user(user_id) for idp_id, external_id in external_ids: - if idp_id == SCIM_IDP_ID: + if idp_id == scim_idp_id: return external_id return None @@ -303,13 +304,16 @@ class UserServlet(SCIMServlet): external_id = await self.get_scim_external_id(user_id) if request_user.external_id != external_id: + scim_idp_id = ( + self.hs.config.experimental.msc4098.idp_id or SCIM_DEFAULT_IDP_ID + ) if external_id: await self.store.remove_user_external_id( - SCIM_IDP_ID, external_id, user_id + scim_idp_id, external_id, user_id ) if request_user.external_id: await self.store.record_user_external_id( - SCIM_IDP_ID, request_user.external_id, user_id + scim_idp_id, request_user.external_id, user_id ) if request_user.photos and request_user.photos[0].value: @@ -451,8 +455,11 @@ class UserListServlet(SCIMServlet): ) if request_user.external_id: + scim_idp_id = ( + self.hs.config.experimental.msc4098.idp_id or SCIM_DEFAULT_IDP_ID + ) await self.store.record_user_external_id( - SCIM_IDP_ID, request_user.external_id, user_id + scim_idp_id, request_user.external_id, user_id ) now_ts = self.hs.get_clock().time_msec() diff --git a/tests/rest/test_scim.py b/tests/rest/test_scim.py index 5c998290b7..5421972a39 100644 --- a/tests/rest/test_scim.py +++ b/tests/rest/test_scim.py @@ -8,7 +8,7 @@ import synapse.rest.scim from synapse.config import ConfigError from synapse.config.homeserver import HomeServerConfig from synapse.rest.client import login -from synapse.rest.scim import HAS_SCIM2, SCIM_IDP_ID +from synapse.rest.scim import HAS_SCIM2, SCIM_DEFAULT_IDP_ID from synapse.server import HomeServer from synapse.types import JsonDict, UserID from synapse.util import Clock @@ -58,7 +58,9 @@ class SCIMExperimentalFeatureTestCase(HomeserverTestCase): config_dict = { "experimental_features": { - "msc4098": True, + "msc4098": { + "enabled": True, + }, "msc3861": {"enabled": True}, }, **default_config("test"), @@ -90,7 +92,10 @@ class UserProvisioningTestCase(HomeserverTestCase): def default_config(self) -> JsonDict: conf = super().default_config() - conf.setdefault("experimental_features", {}).setdefault("msc4098", True) + msc4098_conf = conf.setdefault("experimental_features", {}).setdefault( + "msc4098", {} + ) + msc4098_conf.setdefault("enabled", True) return conf def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: @@ -125,7 +130,7 @@ class UserProvisioningTestCase(HomeserverTestCase): ) self.get_success( self.store.record_user_external_id( - SCIM_IDP_ID, "IDP-user", self.user_user_id + SCIM_DEFAULT_IDP_ID, "IDP-user", self.user_user_id ) ) @@ -578,7 +583,10 @@ class SCIMMetadataTestCase(HomeserverTestCase): def default_config(self) -> JsonDict: conf = super().default_config() - conf.setdefault("experimental_features", {}).setdefault("msc4098", True) + msc4098_conf = conf.setdefault("experimental_features", {}).setdefault( + "msc4098", {} + ) + msc4098_conf.setdefault("enabled", True) return conf def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: