feat: make the SCIM IPD id configurable

This commit is contained in:
Éloi Rivard 2024-11-20 10:13:49 +01:00
parent c48e7310a4
commit 36a600c4fb
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
3 changed files with 50 additions and 17 deletions

View file

@ -256,6 +256,19 @@ class MSC3866Config:
require_approval_for_new_accounts: bool = False 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): class ExperimentalConfig(Config):
"""Config section for enabling experimental features""" """Config section for enabling experimental features"""
@ -416,13 +429,18 @@ class ExperimentalConfig(Config):
) )
# MSC4098: SCIM provisioning API # MSC4098: SCIM provisioning API
self.msc4098_enabled = experimental.get("msc4098", False) try:
if self.msc4098_enabled and self.msc3861.enabled: self.msc4098 = MSC4098Config(**experimental.get("msc4098", {}))
if self.msc4098.enabled and self.msc3861.enabled:
raise ConfigError( raise ConfigError(
"MSC3861 and MSC4098 are mutually exclusive. Please disable one or the" "MSC3861 and MSC4098 are mutually exclusive. Please disable one or the"
"other.", "other.",
("experimental", "msc4098"), ("experimental", "msc4098"),
) )
except ValueError as exc:
raise ConfigError(
"Invalid MSC4098 configuration", ("experimental", "msc4098")
) from exc
# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code # MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code
self.msc4108_enabled = experimental.get("msc4108_enabled", False) self.msc4108_enabled = experimental.get("msc4108_enabled", False)

View file

@ -84,7 +84,7 @@ if TYPE_CHECKING:
from synapse.server import HomeServer from synapse.server import HomeServer
SCIM_PREFIX = "/_synapse/admin/scim/v2" SCIM_PREFIX = "/_synapse/admin/scim/v2"
SCIM_IDP_ID = "__scim__" SCIM_DEFAULT_IDP_ID = "__scim__"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -99,7 +99,7 @@ class SCIMResource(JsonResource):
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
if not hs.config.experimental.msc4098_enabled: if not hs.config.experimental.msc4098.enabled:
return return
SchemaListServlet(hs).register(http_server) SchemaListServlet(hs).register(http_server)
@ -156,9 +156,10 @@ class SCIMServlet(RestServlet):
async def get_scim_external_id(self, user_id: str) -> Optional[str]: async def get_scim_external_id(self, user_id: str) -> Optional[str]:
"""Read the external id stored in the special SCIM IDP.""" """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) external_ids = await self.store.get_external_ids_by_user(user_id)
for idp_id, external_id in external_ids: for idp_id, external_id in external_ids:
if idp_id == SCIM_IDP_ID: if idp_id == scim_idp_id:
return external_id return external_id
return None return None
@ -303,13 +304,16 @@ class UserServlet(SCIMServlet):
external_id = await self.get_scim_external_id(user_id) external_id = await self.get_scim_external_id(user_id)
if request_user.external_id != external_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: if external_id:
await self.store.remove_user_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: if request_user.external_id:
await self.store.record_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: if request_user.photos and request_user.photos[0].value:
@ -451,8 +455,11 @@ class UserListServlet(SCIMServlet):
) )
if request_user.external_id: 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( 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() now_ts = self.hs.get_clock().time_msec()

View file

@ -8,7 +8,7 @@ import synapse.rest.scim
from synapse.config import ConfigError from synapse.config import ConfigError
from synapse.config.homeserver import HomeServerConfig from synapse.config.homeserver import HomeServerConfig
from synapse.rest.client import login 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.server import HomeServer
from synapse.types import JsonDict, UserID from synapse.types import JsonDict, UserID
from synapse.util import Clock from synapse.util import Clock
@ -58,7 +58,9 @@ class SCIMExperimentalFeatureTestCase(HomeserverTestCase):
config_dict = { config_dict = {
"experimental_features": { "experimental_features": {
"msc4098": True, "msc4098": {
"enabled": True,
},
"msc3861": {"enabled": True}, "msc3861": {"enabled": True},
}, },
**default_config("test"), **default_config("test"),
@ -90,7 +92,10 @@ class UserProvisioningTestCase(HomeserverTestCase):
def default_config(self) -> JsonDict: def default_config(self) -> JsonDict:
conf = super().default_config() 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 return conf
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@ -125,7 +130,7 @@ class UserProvisioningTestCase(HomeserverTestCase):
) )
self.get_success( self.get_success(
self.store.record_user_external_id( 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: def default_config(self) -> JsonDict:
conf = super().default_config() 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 return conf
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: