Support OIDC backchannel logouts (#11414)

If configured an OIDC IdP can log a user's session out of
Synapse when they log out of the identity provider.

The IdP sends a request directly to Synapse (and must be
configured with an endpoint) when a user logs out.
This commit is contained in:
Quentin Gliech 2022-10-31 18:07:30 +01:00 committed by GitHub
parent 15bdb0da52
commit cc3a52b33d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 960 additions and 66 deletions

View file

@ -553,6 +553,34 @@ class RestHelper:
return channel.json_body
def whoami(
self,
access_token: str,
expect_code: Literal[HTTPStatus.OK, HTTPStatus.UNAUTHORIZED] = HTTPStatus.OK,
) -> JsonDict:
"""Perform a 'whoami' request, which can be a quick way to check for access
token validity
Args:
access_token: The user token to use during the request
expect_code: The return code to expect from attempting the whoami request
"""
channel = make_request(
self.hs.get_reactor(),
self.site,
"GET",
"account/whoami",
access_token=access_token,
)
assert channel.code == expect_code, "Exepcted: %d, got %d, resp: %r" % (
expect_code,
channel.code,
channel.result["body"],
)
return channel.json_body
def fake_oidc_server(self, issuer: str = TEST_OIDC_ISSUER) -> FakeOidcServer:
"""Create a ``FakeOidcServer``.
@ -572,6 +600,7 @@ class RestHelper:
fake_server: FakeOidcServer,
remote_user_id: str,
with_sid: bool = False,
idp_id: Optional[str] = None,
expected_status: int = 200,
) -> Tuple[JsonDict, FakeAuthorizationGrant]:
"""Log in (as a new user) via OIDC
@ -588,7 +617,11 @@ class RestHelper:
client_redirect_url = "https://x"
userinfo = {"sub": remote_user_id}
channel, grant = self.auth_via_oidc(
fake_server, userinfo, client_redirect_url, with_sid=with_sid
fake_server,
userinfo,
client_redirect_url,
with_sid=with_sid,
idp_id=idp_id,
)
# expect a confirmation page
@ -623,6 +656,7 @@ class RestHelper:
client_redirect_url: Optional[str] = None,
ui_auth_session_id: Optional[str] = None,
with_sid: bool = False,
idp_id: Optional[str] = None,
) -> Tuple[FakeChannel, FakeAuthorizationGrant]:
"""Perform an OIDC authentication flow via a mock OIDC provider.
@ -648,6 +682,7 @@ class RestHelper:
ui_auth_session_id: if set, we will perform a UI Auth flow. The session id
of the UI auth.
with_sid: if True, generates a random `sid` (OIDC session ID)
idp_id: if set, explicitely chooses one specific IDP
Returns:
A FakeChannel containing the result of calling the OIDC callback endpoint.
@ -665,7 +700,9 @@ class RestHelper:
oauth_uri = self.initiate_sso_ui_auth(ui_auth_session_id, cookies)
else:
# otherwise, hit the login redirect endpoint
oauth_uri = self.initiate_sso_login(client_redirect_url, cookies)
oauth_uri = self.initiate_sso_login(
client_redirect_url, cookies, idp_id=idp_id
)
# we now have a URI for the OIDC IdP, but we skip that and go straight
# back to synapse's OIDC callback resource. However, we do need the "state"
@ -742,7 +779,10 @@ class RestHelper:
return channel, grant
def initiate_sso_login(
self, client_redirect_url: Optional[str], cookies: MutableMapping[str, str]
self,
client_redirect_url: Optional[str],
cookies: MutableMapping[str, str],
idp_id: Optional[str] = None,
) -> str:
"""Make a request to the login-via-sso redirect endpoint, and return the target
@ -753,6 +793,7 @@ class RestHelper:
client_redirect_url: the client redirect URL to pass to the login redirect
endpoint
cookies: any cookies returned will be added to this dict
idp_id: if set, explicitely chooses one specific IDP
Returns:
the URI that the client gets redirected to (ie, the SSO server)
@ -761,6 +802,12 @@ class RestHelper:
if client_redirect_url:
params["redirectUrl"] = client_redirect_url
uri = "/_matrix/client/r0/login/sso/redirect"
if idp_id is not None:
uri = f"{uri}/{idp_id}"
uri = f"{uri}?{urllib.parse.urlencode(params)}"
# hit the redirect url (which should redirect back to the redirect url. This
# is the easiest way of figuring out what the Host header ought to be set to
# to keep Synapse happy.
@ -768,7 +815,7 @@ class RestHelper:
self.hs.get_reactor(),
self.site,
"GET",
"/_matrix/client/r0/login/sso/redirect?" + urllib.parse.urlencode(params),
uri,
)
assert channel.code == 302