forked from MirrorHub/synapse
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
|
||||
oidc_config:
|
||||
enabled: true
|
||||
issuer: "https://id.twitch.tv/oauth2/"
|
||||
client_id: "your-client-id" # TO BE FILLED
|
||||
client_secret: "your-client-secret" # TO BE FILLED
|
||||
client_auth_method: "client_secret_post"
|
||||
user_mapping_provider:
|
||||
config:
|
||||
localpart_template: '{{ user.preferred_username }}'
|
||||
display_name_template: '{{ user.name }}'
|
||||
enabled: true
|
||||
issuer: "https://id.twitch.tv/oauth2/"
|
||||
client_id: "your-client-id" # TO BE FILLED
|
||||
client_secret: "your-client-secret" # TO BE FILLED
|
||||
client_auth_method: "client_secret_post"
|
||||
user_mapping_provider:
|
||||
config:
|
||||
localpart_template: "{{ user.preferred_username }}"
|
||||
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
|
||||
|
||||
# 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
|
||||
# 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_jwks_uri = oidc_config.get("jwks_uri")
|
||||
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)
|
||||
|
||||
ump_config = oidc_config.get("user_mapping_provider", {})
|
||||
|
@ -159,6 +160,14 @@ class OIDCConfig(Config):
|
|||
#
|
||||
#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
|
||||
# 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._callback_url = hs.config.oidc_callback_url # type: 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(
|
||||
hs.config.oidc_client_id,
|
||||
hs.config.oidc_client_secret,
|
||||
|
@ -196,11 +197,11 @@ class OidcHandler:
|
|||
% (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 m.get("userinfo_endpoint") is None:
|
||||
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:
|
||||
# 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``.
|
||||
"""
|
||||
|
||||
# Maybe that should be user-configurable and not inferred?
|
||||
return "openid" not in self._scopes
|
||||
return (
|
||||
"openid" not in self._scopes
|
||||
or self._user_profile_method == "userinfo_endpoint"
|
||||
)
|
||||
|
||||
async def load_metadata(self) -> OpenIDProviderMetadata:
|
||||
"""Load and validate the provider metadata.
|
||||
|
|
|
@ -286,9 +286,15 @@ class OidcHandlerTestCase(HomeserverTestCase):
|
|||
h._validate_metadata,
|
||||
)
|
||||
|
||||
# Tests for configs that the userinfo endpoint
|
||||
# Tests for configs that require the userinfo endpoint
|
||||
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.assertRaisesRegex(ValueError, "userinfo_endpoint", h._validate_metadata)
|
||||
|
||||
|
|
Loading…
Reference in a new issue