diff --git a/changelog.d/8180.feature b/changelog.d/8180.feature new file mode 100644 index 000000000000..4ee5b6a56e37 --- /dev/null +++ b/changelog.d/8180.feature @@ -0,0 +1 @@ +Add a configuration option that allows existing users to log in with OpenID Connect. Contributed by @BBBSnowball and @OmmyZhang. diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml index 2a5b2e093537..6afe71b24f78 100644 --- a/docs/sample_config.yaml +++ b/docs/sample_config.yaml @@ -1711,6 +1711,10 @@ oidc_config: # #skip_verification: true + # Uncomment to add oidc token to the account instead of failing when user already exists. Defaults to false. + # + #allow_existing_users: true + # An external module can be provided here as a custom solution to mapping # attributes returned from a OIDC provider onto a matrix user. # diff --git a/synapse/config/oidc_config.py b/synapse/config/oidc_config.py index e0939bce84a1..c9561771c892 100644 --- a/synapse/config/oidc_config.py +++ b/synapse/config/oidc_config.py @@ -56,6 +56,7 @@ def read_config(self, config, **kwargs): self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint") self.oidc_jwks_uri = oidc_config.get("jwks_uri") self.oidc_skip_verification = oidc_config.get("skip_verification", False) + self.oidc_allow_existing_users = oidc_config.get("allow_existing_users", False) ump_config = oidc_config.get("user_mapping_provider", {}) ump_config.setdefault("module", DEFAULT_USER_MAPPING_PROVIDER) @@ -158,6 +159,10 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs): # #skip_verification: true + # Uncomment to add oidc token to the account instead of failing when user already exists. Defaults to false. + # + #allow_existing_users: true + # An external module can be provided here as a custom solution to mapping # attributes returned from a OIDC provider onto a matrix user. # diff --git a/synapse/handlers/oidc_handler.py b/synapse/handlers/oidc_handler.py index 1b06f3173fa0..482997eaa2b3 100644 --- a/synapse/handlers/oidc_handler.py +++ b/synapse/handlers/oidc_handler.py @@ -114,6 +114,7 @@ def __init__(self, hs: "HomeServer"): hs.config.oidc_user_mapping_provider_config ) # type: OidcMappingProvider self._skip_verification = hs.config.oidc_skip_verification # type: bool + self._allow_existing_users = hs.config.oidc_allow_existing_users # type: bool self._http_client = hs.get_proxied_http_client() self._auth_handler = hs.get_auth_handler() @@ -849,7 +850,8 @@ async def _map_userinfo_to_user( If we don't find the user that way, we should register the user, mapping the localpart and the display name from the UserInfo. - If a user already exists with the mxid we've mapped, raise an exception. + If a user already exists with the mxid we've mapped and allow_existing_users + is disabled , raise an exception. Args: userinfo: an object representing the user @@ -906,20 +908,25 @@ async def _map_userinfo_to_user( localpart = map_username_to_mxid_localpart(attributes["localpart"]) user_id = UserID(localpart, self._hostname) - if await self._datastore.get_users_by_id_case_insensitive(user_id.to_string()): - # This mxid is taken - raise MappingException( - "mxid '{}' is already taken".format(user_id.to_string()) - ) - - # It's the first time this user is logging in and the mapped mxid was - # not taken, register the user - registered_user_id = await self._registration_handler.register_user( - localpart=localpart, - default_display_name=attributes["display_name"], - user_agent_ips=(user_agent, ip_address), + matches = await self._datastore.get_users_by_id_case_insensitive( + user_id.to_string() ) - + if matches: + if self._allow_existing_users: + registered_user_id = next(iter(matches)) + else: + # This mxid is taken + raise MappingException( + "mxid '{}' is already taken".format(user_id.to_string()) + ) + else: + # It's the first time this user is logging in and the mapped mxid was + # not taken, register the user + registered_user_id = await self._registration_handler.register_user( + localpart=localpart, + default_display_name=attributes["display_name"], + user_agent_ips=(user_agent, ip_address), + ) await self._datastore.record_user_external_id( self._auth_provider_id, remote_user_id, registered_user_id, )