mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-13 21:53:49 +01:00
Add config option for always using "userinfo endpoint" for OIDC (#7658)
This allows for connecting to certain IdPs, e.g. GitLab.
This commit is contained in:
parent
0b68577ed6
commit
05ee048f2c
6 changed files with 65 additions and 15 deletions
1
changelog.d/7658.feature
Normal file
1
changelog.d/7658.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add a configuration option for always using the "userinfo endpoint" for OpenID Connect. This fixes support for some identity providers, e.g. GitLab. Contributed by Benjamin Koch.
|
|
@ -238,13 +238,36 @@ Synapse config:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
oidc_config:
|
oidc_config:
|
||||||
enabled: true
|
enabled: true
|
||||||
issuer: "https://id.twitch.tv/oauth2/"
|
issuer: "https://id.twitch.tv/oauth2/"
|
||||||
client_id: "your-client-id" # TO BE FILLED
|
client_id: "your-client-id" # TO BE FILLED
|
||||||
client_secret: "your-client-secret" # TO BE FILLED
|
client_secret: "your-client-secret" # TO BE FILLED
|
||||||
client_auth_method: "client_secret_post"
|
client_auth_method: "client_secret_post"
|
||||||
user_mapping_provider:
|
user_mapping_provider:
|
||||||
config:
|
config:
|
||||||
localpart_template: '{{ user.preferred_username }}'
|
localpart_template: "{{ user.preferred_username }}"
|
||||||
display_name_template: '{{ user.name }}'
|
display_name_template: "{{ user.name }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitLab
|
||||||
|
|
||||||
|
1. Create a [new application](https://gitlab.com/profile/applications).
|
||||||
|
2. Add the `read_user` and `openid` scopes.
|
||||||
|
3. Add this Callback URL: `[synapse public baseurl]/_synapse/oidc/callback`
|
||||||
|
|
||||||
|
Synapse config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
issuer: "https://gitlab.com/"
|
||||||
|
client_id: "your-client-id" # TO BE FILLED
|
||||||
|
client_secret: "your-client-secret" # TO BE FILLED
|
||||||
|
client_auth_method: "client_secret_post"
|
||||||
|
scopes: ["openid", "read_user"]
|
||||||
|
user_profile_method: "userinfo_endpoint"
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
localpart_template: '{{ user.nickname }}'
|
||||||
|
display_name_template: '{{ user.name }}'
|
||||||
```
|
```
|
||||||
|
|
|
@ -1714,6 +1714,14 @@ oidc_config:
|
||||||
#
|
#
|
||||||
#skip_verification: true
|
#skip_verification: true
|
||||||
|
|
||||||
|
# Whether to fetch the user profile from the userinfo endpoint. Valid
|
||||||
|
# values are: "auto" or "userinfo_endpoint".
|
||||||
|
#
|
||||||
|
# Defaults to "auto", which fetches the userinfo endpoint if "openid" is included
|
||||||
|
# in `scopes`. Uncomment the following to always fetch the userinfo endpoint.
|
||||||
|
#
|
||||||
|
#user_profile_method: "userinfo_endpoint"
|
||||||
|
|
||||||
# Uncomment to allow a user logging in via OIDC to match a pre-existing account instead
|
# Uncomment to allow a user logging in via OIDC to match a pre-existing account instead
|
||||||
# of failing. This could be used if switching from password logins to OIDC. Defaults to false.
|
# of failing. This could be used if switching from password logins to OIDC. Defaults to false.
|
||||||
#
|
#
|
||||||
|
|
|
@ -56,6 +56,7 @@ class OIDCConfig(Config):
|
||||||
self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint")
|
self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint")
|
||||||
self.oidc_jwks_uri = oidc_config.get("jwks_uri")
|
self.oidc_jwks_uri = oidc_config.get("jwks_uri")
|
||||||
self.oidc_skip_verification = oidc_config.get("skip_verification", False)
|
self.oidc_skip_verification = oidc_config.get("skip_verification", False)
|
||||||
|
self.oidc_user_profile_method = oidc_config.get("user_profile_method", "auto")
|
||||||
self.oidc_allow_existing_users = oidc_config.get("allow_existing_users", False)
|
self.oidc_allow_existing_users = oidc_config.get("allow_existing_users", False)
|
||||||
|
|
||||||
ump_config = oidc_config.get("user_mapping_provider", {})
|
ump_config = oidc_config.get("user_mapping_provider", {})
|
||||||
|
@ -159,6 +160,14 @@ class OIDCConfig(Config):
|
||||||
#
|
#
|
||||||
#skip_verification: true
|
#skip_verification: true
|
||||||
|
|
||||||
|
# Whether to fetch the user profile from the userinfo endpoint. Valid
|
||||||
|
# values are: "auto" or "userinfo_endpoint".
|
||||||
|
#
|
||||||
|
# Defaults to "auto", which fetches the userinfo endpoint if "openid" is included
|
||||||
|
# in `scopes`. Uncomment the following to always fetch the userinfo endpoint.
|
||||||
|
#
|
||||||
|
#user_profile_method: "userinfo_endpoint"
|
||||||
|
|
||||||
# Uncomment to allow a user logging in via OIDC to match a pre-existing account instead
|
# Uncomment to allow a user logging in via OIDC to match a pre-existing account instead
|
||||||
# of failing. This could be used if switching from password logins to OIDC. Defaults to false.
|
# of failing. This could be used if switching from password logins to OIDC. Defaults to false.
|
||||||
#
|
#
|
||||||
|
|
|
@ -96,6 +96,7 @@ class OidcHandler:
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self._callback_url = hs.config.oidc_callback_url # type: str
|
self._callback_url = hs.config.oidc_callback_url # type: str
|
||||||
self._scopes = hs.config.oidc_scopes # type: List[str]
|
self._scopes = hs.config.oidc_scopes # type: List[str]
|
||||||
|
self._user_profile_method = hs.config.oidc_user_profile_method # type: str
|
||||||
self._client_auth = ClientAuth(
|
self._client_auth = ClientAuth(
|
||||||
hs.config.oidc_client_id,
|
hs.config.oidc_client_id,
|
||||||
hs.config.oidc_client_secret,
|
hs.config.oidc_client_secret,
|
||||||
|
@ -196,11 +197,11 @@ class OidcHandler:
|
||||||
% (m["response_types_supported"],)
|
% (m["response_types_supported"],)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the openid scope was not requested, we need a userinfo endpoint to fetch user infos
|
# Ensure there's a userinfo endpoint to fetch from if it is required.
|
||||||
if self._uses_userinfo:
|
if self._uses_userinfo:
|
||||||
if m.get("userinfo_endpoint") is None:
|
if m.get("userinfo_endpoint") is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'provider has no "userinfo_endpoint", even though it is required because the "openid" scope is not requested'
|
'provider has no "userinfo_endpoint", even though it is required'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# If we're not using userinfo, we need a valid jwks to validate the ID token
|
# If we're not using userinfo, we need a valid jwks to validate the ID token
|
||||||
|
@ -220,8 +221,10 @@ class OidcHandler:
|
||||||
``access_token`` with the ``userinfo_endpoint``.
|
``access_token`` with the ``userinfo_endpoint``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Maybe that should be user-configurable and not inferred?
|
return (
|
||||||
return "openid" not in self._scopes
|
"openid" not in self._scopes
|
||||||
|
or self._user_profile_method == "userinfo_endpoint"
|
||||||
|
)
|
||||||
|
|
||||||
async def load_metadata(self) -> OpenIDProviderMetadata:
|
async def load_metadata(self) -> OpenIDProviderMetadata:
|
||||||
"""Load and validate the provider metadata.
|
"""Load and validate the provider metadata.
|
||||||
|
|
|
@ -286,9 +286,15 @@ class OidcHandlerTestCase(HomeserverTestCase):
|
||||||
h._validate_metadata,
|
h._validate_metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tests for configs that the userinfo endpoint
|
# Tests for configs that require the userinfo endpoint
|
||||||
self.assertFalse(h._uses_userinfo)
|
self.assertFalse(h._uses_userinfo)
|
||||||
h._scopes = [] # do not request the openid scope
|
self.assertEqual(h._user_profile_method, "auto")
|
||||||
|
h._user_profile_method = "userinfo_endpoint"
|
||||||
|
self.assertTrue(h._uses_userinfo)
|
||||||
|
|
||||||
|
# Revert the profile method and do not request the "openid" scope.
|
||||||
|
h._user_profile_method = "auto"
|
||||||
|
h._scopes = []
|
||||||
self.assertTrue(h._uses_userinfo)
|
self.assertTrue(h._uses_userinfo)
|
||||||
self.assertRaisesRegex(ValueError, "userinfo_endpoint", h._validate_metadata)
|
self.assertRaisesRegex(ValueError, "userinfo_endpoint", h._validate_metadata)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue