1
0
Fork 0
mirror of https://github.com/element-hq/synapse.git synced 2025-03-06 16:06:52 +00:00

Add support for overriding id_token_signing_alg_values_supported for an OpenID identity provider (#18177)

Normally, when `discovery` is enabled,
`id_token_signing_alg_values_supported` comes from the OpenID Discovery
Document (`/.well-known/openid-configuration`). If nothing was
specified, we default to supporting `RS256` in the downstream usage.

This PR just adds support for adding a default/overriding the the
discovered value [just like we do for other things like the
`token_endpoint`](1525a3b4d4/docs/usage/configuration/config_documentation.md (oidc_providers)),
etc.
This commit is contained in:
Eric Eastwood 2025-02-20 17:56:53 -06:00 committed by GitHub
parent 6b4cc9f3f6
commit caa1f9d806
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 119 additions and 4 deletions

View file

@ -0,0 +1 @@
Add support for specifying/overriding `id_token_signing_alg_values_supported` for an OpenID identity provider.

View file

@ -3579,6 +3579,24 @@ Options for each entry include:
to `auto`, which uses PKCE if supported during metadata discovery. Set to `always` to `auto`, which uses PKCE if supported during metadata discovery. Set to `always`
to force enable PKCE or `never` to force disable PKCE. to force enable PKCE or `never` to force disable PKCE.
* `id_token_signing_alg_values_supported`: List of the JWS signing algorithms (`alg`
values) that are supported for signing the `id_token`.
This is *not* required if `discovery` is disabled. We default to supporting `RS256` in
the downstream usage if no algorithms are configured here or in the discovery
document.
According to the spec, the algorithm `"RS256"` MUST be included. The absolute rigid
approach would be to reject this provider as non-compliant if it's not included but we
simply allow whatever and see what happens (you're the one that configured the value
and cooperating with the identity provider).
The `alg` value `"none"` MAY be supported but can only be used if the Authorization
Endpoint does not include `id_token` in the `response_type` (ex.
`/authorize?response_type=code` where `none` can apply,
`/authorize?response_type=code%20id_token` where `none` can't apply) (such as when
using the Authorization Code Flow).
* `scopes`: list of scopes to request. This should normally include the "openid" * `scopes`: list of scopes to request. This should normally include the "openid"
scope. Defaults to `["openid"]`. scope. Defaults to `["openid"]`.

View file

@ -125,6 +125,10 @@ OIDC_PROVIDER_CONFIG_SCHEMA = {
"enum": ["client_secret_basic", "client_secret_post", "none"], "enum": ["client_secret_basic", "client_secret_post", "none"],
}, },
"pkce_method": {"type": "string", "enum": ["auto", "always", "never"]}, "pkce_method": {"type": "string", "enum": ["auto", "always", "never"]},
"id_token_signing_alg_values_supported": {
"type": "array",
"items": {"type": "string"},
},
"scopes": {"type": "array", "items": {"type": "string"}}, "scopes": {"type": "array", "items": {"type": "string"}},
"authorization_endpoint": {"type": "string"}, "authorization_endpoint": {"type": "string"},
"token_endpoint": {"type": "string"}, "token_endpoint": {"type": "string"},
@ -326,6 +330,9 @@ def _parse_oidc_config_dict(
client_secret_jwt_key=client_secret_jwt_key, client_secret_jwt_key=client_secret_jwt_key,
client_auth_method=client_auth_method, client_auth_method=client_auth_method,
pkce_method=oidc_config.get("pkce_method", "auto"), pkce_method=oidc_config.get("pkce_method", "auto"),
id_token_signing_alg_values_supported=oidc_config.get(
"id_token_signing_alg_values_supported"
),
scopes=oidc_config.get("scopes", ["openid"]), scopes=oidc_config.get("scopes", ["openid"]),
authorization_endpoint=oidc_config.get("authorization_endpoint"), authorization_endpoint=oidc_config.get("authorization_endpoint"),
token_endpoint=oidc_config.get("token_endpoint"), token_endpoint=oidc_config.get("token_endpoint"),
@ -402,6 +409,34 @@ class OidcProviderConfig:
# Valid values are 'auto', 'always', and 'never'. # Valid values are 'auto', 'always', and 'never'.
pkce_method: str pkce_method: str
id_token_signing_alg_values_supported: Optional[List[str]]
"""
List of the JWS signing algorithms (`alg` values) that are supported for signing the
`id_token`.
This is *not* required if `discovery` is disabled. We default to supporting `RS256`
in the downstream usage if no algorithms are configured here or in the discovery
document.
According to the spec, the algorithm `"RS256"` MUST be included. The absolute rigid
approach would be to reject this provider as non-compliant if it's not included but
we can just allow whatever and see what happens (they're the ones that configured
the value and cooperating with the identity provider). It wouldn't be wise to add it
ourselves because absence of `RS256` might indicate that the provider actually
doesn't support it, despite the spec requirement. Adding it silently could lead to
failed authentication attempts or strange mismatch attacks.
The `alg` value `"none"` MAY be supported but can only be used if the Authorization
Endpoint does not include `id_token` in the `response_type` (ex.
`/authorize?response_type=code` where `none` can apply,
`/authorize?response_type=code%20id_token` where `none` can't apply) (such as when
using the Authorization Code Flow).
Spec:
- https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
- https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationExamples
"""
# list of scopes to request # list of scopes to request
scopes: Collection[str] scopes: Collection[str]

View file

@ -640,6 +640,11 @@ class OidcProvider:
elif self._config.pkce_method == "never": elif self._config.pkce_method == "never":
metadata.pop("code_challenge_methods_supported", None) metadata.pop("code_challenge_methods_supported", None)
if self._config.id_token_signing_alg_values_supported:
metadata["id_token_signing_alg_values_supported"] = (
self._config.id_token_signing_alg_values_supported
)
self._validate_metadata(metadata) self._validate_metadata(metadata)
return metadata return metadata

View file

@ -70,12 +70,16 @@ DEFAULT_CONFIG = {
} }
# extends the default config with explicit OAuth2 endpoints instead of using discovery # extends the default config with explicit OAuth2 endpoints instead of using discovery
#
# We add "explicit" to things to make them different from the discovered values to make
# sure that the explicit values override the discovered ones.
EXPLICIT_ENDPOINT_CONFIG = { EXPLICIT_ENDPOINT_CONFIG = {
**DEFAULT_CONFIG, **DEFAULT_CONFIG,
"discover": False, "discover": False,
"authorization_endpoint": ISSUER + "authorize", "authorization_endpoint": ISSUER + "authorize-explicit",
"token_endpoint": ISSUER + "token", "token_endpoint": ISSUER + "token-explicit",
"jwks_uri": ISSUER + "jwks", "jwks_uri": ISSUER + "jwks-explicit",
"id_token_signing_alg_values_supported": ["RS256", "<explicit>"],
} }
@ -259,12 +263,64 @@ class OidcHandlerTestCase(HomeserverTestCase):
self.get_success(self.provider.load_metadata()) self.get_success(self.provider.load_metadata())
self.fake_server.get_metadata_handler.assert_not_called() self.fake_server.get_metadata_handler.assert_not_called()
@override_config({"oidc_config": {**EXPLICIT_ENDPOINT_CONFIG, "discover": True}})
def test_discovery_with_explicit_config(self) -> None:
"""
The handler should discover the endpoints from OIDC discovery document but
values are overriden by the explicit config.
"""
# This would throw if some metadata were invalid
metadata = self.get_success(self.provider.load_metadata())
self.fake_server.get_metadata_handler.assert_called_once()
self.assertEqual(metadata.issuer, self.fake_server.issuer)
# It seems like authlib does not have that defined in its metadata models
self.assertEqual(
metadata.get("userinfo_endpoint"),
self.fake_server.userinfo_endpoint,
)
# Ensure the values are overridden correctly since these were configured
# explicitly
self.assertEqual(
metadata.authorization_endpoint,
EXPLICIT_ENDPOINT_CONFIG["authorization_endpoint"],
)
self.assertEqual(
metadata.token_endpoint, EXPLICIT_ENDPOINT_CONFIG["token_endpoint"]
)
self.assertEqual(metadata.jwks_uri, EXPLICIT_ENDPOINT_CONFIG["jwks_uri"])
self.assertEqual(
metadata.id_token_signing_alg_values_supported,
EXPLICIT_ENDPOINT_CONFIG["id_token_signing_alg_values_supported"],
)
# subsequent calls should be cached
self.reset_mocks()
self.get_success(self.provider.load_metadata())
self.fake_server.get_metadata_handler.assert_not_called()
@override_config({"oidc_config": EXPLICIT_ENDPOINT_CONFIG}) @override_config({"oidc_config": EXPLICIT_ENDPOINT_CONFIG})
def test_no_discovery(self) -> None: def test_no_discovery(self) -> None:
"""When discovery is disabled, it should not try to load from discovery document.""" """When discovery is disabled, it should not try to load from discovery document."""
self.get_success(self.provider.load_metadata()) metadata = self.get_success(self.provider.load_metadata())
self.fake_server.get_metadata_handler.assert_not_called() self.fake_server.get_metadata_handler.assert_not_called()
# Ensure the values are overridden correctly since these were configured
# explicitly
self.assertEqual(
metadata.authorization_endpoint,
EXPLICIT_ENDPOINT_CONFIG["authorization_endpoint"],
)
self.assertEqual(
metadata.token_endpoint, EXPLICIT_ENDPOINT_CONFIG["token_endpoint"]
)
self.assertEqual(metadata.jwks_uri, EXPLICIT_ENDPOINT_CONFIG["jwks_uri"])
self.assertEqual(
metadata.id_token_signing_alg_values_supported,
EXPLICIT_ENDPOINT_CONFIG["id_token_signing_alg_values_supported"],
)
@override_config({"oidc_config": DEFAULT_CONFIG}) @override_config({"oidc_config": DEFAULT_CONFIG})
def test_load_jwks(self) -> None: def test_load_jwks(self) -> None:
"""JWKS loading is done once (then cached) if used.""" """JWKS loading is done once (then cached) if used."""