mirror of
https://github.com/element-hq/synapse.git
synced 2025-03-06 07:56:51 +00:00
Add redirect_uri
option to oidc_providers
entries (#18197)
Allows overriding the `redirect_uri` parameter sent to both the authorization and token endpoints of the IdP. Typically this parameter is hardcoded to `<public_baseurl>/_synapse/client/oidc/callback`. Yet it can be useful in certain contexts to allow a different callback URL. For instance, if you would like to intercept the authorization code returned from the IdP and do something with it, before eventually calling Synapse's OIDC callback URL yourself. This change enables enterprise use cases but does not change the default behaviour. --- Best reviewed commit-by-commit. --------- Co-authored-by: Eric Eastwood <erice@element.io>
This commit is contained in:
parent
c360da0f8b
commit
154e23f6d7
5 changed files with 80 additions and 1 deletions
1
changelog.d/18197.feature
Normal file
1
changelog.d/18197.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add support for specifying/overriding `redirect_uri` in the authorization and token requests against an OpenID identity provider.
|
|
@ -3662,6 +3662,13 @@ Options for each entry include:
|
||||||
not included in `scopes`. Set to `userinfo_endpoint` to always use the
|
not included in `scopes`. Set to `userinfo_endpoint` to always use the
|
||||||
userinfo endpoint.
|
userinfo endpoint.
|
||||||
|
|
||||||
|
* `redirect_uri`: An optional string, that if set will override the `redirect_uri`
|
||||||
|
parameter sent in the requests to the authorization and token endpoints.
|
||||||
|
Useful if you want to redirect the client to another endpoint as part of the
|
||||||
|
OIDC login. Be aware that the client must then call Synapse's OIDC callback
|
||||||
|
URL (`<public_baseurl>/_synapse/client/oidc/callback`) manually afterwards.
|
||||||
|
Must be a valid URL including scheme and path.
|
||||||
|
|
||||||
* `additional_authorization_parameters`: String to string dictionary that will be passed as
|
* `additional_authorization_parameters`: String to string dictionary that will be passed as
|
||||||
additional parameters to the authorization grant URL.
|
additional parameters to the authorization grant URL.
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,9 @@ OIDC_PROVIDER_CONFIG_SCHEMA = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["auto", "userinfo_endpoint"],
|
"enum": ["auto", "userinfo_endpoint"],
|
||||||
},
|
},
|
||||||
|
"redirect_uri": {
|
||||||
|
"type": ["string", "null"],
|
||||||
|
},
|
||||||
"allow_existing_users": {"type": "boolean"},
|
"allow_existing_users": {"type": "boolean"},
|
||||||
"user_mapping_provider": {"type": ["object", "null"]},
|
"user_mapping_provider": {"type": ["object", "null"]},
|
||||||
"attribute_requirements": {
|
"attribute_requirements": {
|
||||||
|
@ -344,6 +347,7 @@ def _parse_oidc_config_dict(
|
||||||
),
|
),
|
||||||
skip_verification=oidc_config.get("skip_verification", False),
|
skip_verification=oidc_config.get("skip_verification", False),
|
||||||
user_profile_method=oidc_config.get("user_profile_method", "auto"),
|
user_profile_method=oidc_config.get("user_profile_method", "auto"),
|
||||||
|
redirect_uri=oidc_config.get("redirect_uri"),
|
||||||
allow_existing_users=oidc_config.get("allow_existing_users", False),
|
allow_existing_users=oidc_config.get("allow_existing_users", False),
|
||||||
user_mapping_provider_class=user_mapping_provider_class,
|
user_mapping_provider_class=user_mapping_provider_class,
|
||||||
user_mapping_provider_config=user_mapping_provider_config,
|
user_mapping_provider_config=user_mapping_provider_config,
|
||||||
|
@ -467,6 +471,18 @@ class OidcProviderConfig:
|
||||||
# values are: "auto" or "userinfo_endpoint".
|
# values are: "auto" or "userinfo_endpoint".
|
||||||
user_profile_method: str
|
user_profile_method: str
|
||||||
|
|
||||||
|
redirect_uri: Optional[str]
|
||||||
|
"""
|
||||||
|
An optional replacement for Synapse's hardcoded `redirect_uri` URL
|
||||||
|
(`<public_baseurl>/_synapse/client/oidc/callback`). This can be used to send
|
||||||
|
the client to a different URL after it receives a response from the
|
||||||
|
`authorization_endpoint`.
|
||||||
|
|
||||||
|
If this is set, the client is expected to call Synapse's OIDC callback URL
|
||||||
|
reproduced above itself with the necessary parameters and session cookie, in
|
||||||
|
order to complete OIDC login.
|
||||||
|
"""
|
||||||
|
|
||||||
# whether to allow a user logging in via OIDC to match a pre-existing account
|
# whether to allow a user logging in via OIDC to match a pre-existing account
|
||||||
# instead of failing
|
# instead of failing
|
||||||
allow_existing_users: bool
|
allow_existing_users: bool
|
||||||
|
|
|
@ -382,7 +382,12 @@ class OidcProvider:
|
||||||
self._macaroon_generaton = macaroon_generator
|
self._macaroon_generaton = macaroon_generator
|
||||||
|
|
||||||
self._config = provider
|
self._config = provider
|
||||||
self._callback_url: str = hs.config.oidc.oidc_callback_url
|
|
||||||
|
self._callback_url: str
|
||||||
|
if provider.redirect_uri is not None:
|
||||||
|
self._callback_url = provider.redirect_uri
|
||||||
|
else:
|
||||||
|
self._callback_url = hs.config.oidc.oidc_callback_url
|
||||||
|
|
||||||
# Calculate the prefix for OIDC callback paths based on the public_baseurl.
|
# Calculate the prefix for OIDC callback paths based on the public_baseurl.
|
||||||
# We'll insert this into the Path= parameter of any session cookies we set.
|
# We'll insert this into the Path= parameter of any session cookies we set.
|
||||||
|
|
|
@ -57,6 +57,7 @@ CLIENT_ID = "test-client-id"
|
||||||
CLIENT_SECRET = "test-client-secret"
|
CLIENT_SECRET = "test-client-secret"
|
||||||
BASE_URL = "https://synapse/"
|
BASE_URL = "https://synapse/"
|
||||||
CALLBACK_URL = BASE_URL + "_synapse/client/oidc/callback"
|
CALLBACK_URL = BASE_URL + "_synapse/client/oidc/callback"
|
||||||
|
TEST_REDIRECT_URI = "https://test/oidc/callback"
|
||||||
SCOPES = ["openid"]
|
SCOPES = ["openid"]
|
||||||
|
|
||||||
# config for common cases
|
# config for common cases
|
||||||
|
@ -586,6 +587,24 @@ class OidcHandlerTestCase(HomeserverTestCase):
|
||||||
code_verifier = get_value_from_macaroon(macaroon, "code_verifier")
|
code_verifier = get_value_from_macaroon(macaroon, "code_verifier")
|
||||||
self.assertEqual(code_verifier, "")
|
self.assertEqual(code_verifier, "")
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{"oidc_config": {**DEFAULT_CONFIG, "redirect_uri": TEST_REDIRECT_URI}}
|
||||||
|
)
|
||||||
|
def test_redirect_request_with_overridden_redirect_uri(self) -> None:
|
||||||
|
"""The authorization endpoint redirect has the overridden `redirect_uri` value."""
|
||||||
|
req = Mock(spec=["cookies"])
|
||||||
|
req.cookies = []
|
||||||
|
|
||||||
|
url = urlparse(
|
||||||
|
self.get_success(
|
||||||
|
self.provider.handle_redirect_request(req, b"http://client/redirect")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that the redirect_uri in the returned url has been overridden.
|
||||||
|
params = parse_qs(url.query)
|
||||||
|
self.assertEqual(params["redirect_uri"], [TEST_REDIRECT_URI])
|
||||||
|
|
||||||
@override_config({"oidc_config": DEFAULT_CONFIG})
|
@override_config({"oidc_config": DEFAULT_CONFIG})
|
||||||
def test_callback_error(self) -> None:
|
def test_callback_error(self) -> None:
|
||||||
"""Errors from the provider returned in the callback are displayed."""
|
"""Errors from the provider returned in the callback are displayed."""
|
||||||
|
@ -953,6 +972,37 @@ class OidcHandlerTestCase(HomeserverTestCase):
|
||||||
self.assertEqual(args["client_id"], [CLIENT_ID])
|
self.assertEqual(args["client_id"], [CLIENT_ID])
|
||||||
self.assertEqual(args["redirect_uri"], [CALLBACK_URL])
|
self.assertEqual(args["redirect_uri"], [CALLBACK_URL])
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"oidc_config": {
|
||||||
|
**DEFAULT_CONFIG,
|
||||||
|
"redirect_uri": TEST_REDIRECT_URI,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_code_exchange_with_overridden_redirect_uri(self) -> None:
|
||||||
|
"""Code exchange behaves correctly and handles various error scenarios."""
|
||||||
|
# Set up a fake IdP with a token endpoint handler.
|
||||||
|
token = {
|
||||||
|
"type": "Bearer",
|
||||||
|
"access_token": "aabbcc",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fake_server.post_token_handler.side_effect = None
|
||||||
|
self.fake_server.post_token_handler.return_value = FakeResponse.json(
|
||||||
|
payload=token
|
||||||
|
)
|
||||||
|
code = "code"
|
||||||
|
|
||||||
|
# Exchange the code against the fake IdP.
|
||||||
|
self.get_success(self.provider._exchange_code(code, code_verifier=""))
|
||||||
|
|
||||||
|
# Check that the `redirect_uri` parameter provided matches our
|
||||||
|
# overridden config value.
|
||||||
|
kwargs = self.fake_server.request.call_args[1]
|
||||||
|
args = parse_qs(kwargs["data"].decode("utf-8"))
|
||||||
|
self.assertEqual(args["redirect_uri"], [TEST_REDIRECT_URI])
|
||||||
|
|
||||||
@override_config(
|
@override_config(
|
||||||
{
|
{
|
||||||
"oidc_config": {
|
"oidc_config": {
|
||||||
|
|
Loading…
Add table
Reference in a new issue