diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 2d1990cce5..e38d0924e6 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -1722,13 +1722,17 @@ class AuthHandler: else: return False - async def start_sso_ui_auth(self, request: SynapseRequest, session_id: str) -> str: + async def start_sso_ui_auth( + self, request: SynapseRequest, session_id: str, preferred_idp_id: Optional[str] + ) -> str: """ Get the HTML for the SSO redirect confirmation page. Args: request: The incoming HTTP request session_id: The user interactive authentication session ID. + preferred_idp_id: The ID of the identity provider to use. If `None` one will + be picked randomly from those the user has already signed in with. Returns: The HTML to render. @@ -1752,15 +1756,22 @@ class AuthHandler: # it not being offered. raise SynapseError(400, "User has no SSO identities") - # for now, just pick one - idp_id, sso_auth_provider = next(iter(idps.items())) - if len(idps) > 0: - logger.warning( - "User %r has previously logged in with multiple SSO IdPs; arbitrarily " - "picking %r", - user_id_to_verify, - idp_id, - ) + if preferred_idp_id is not None: + # Use the idp specified by the client. + sso_auth_provider = idps.get(preferred_idp_id) + if sso_auth_provider is None: + raise SynapseError(400, "Unknown IdP %s" % (preferred_idp_id,)) + else: + idp_id, sso_auth_provider = next(iter(idps.items())) + if len(idps) > 0: + # We arbitrarily picked an IdP from multiple potential + # candidates. This may be undesirable for the user. + logger.warning( + "User %r has previously logged in with multiple SSO IdPs; arbitrarily " + "picking %r", + user_id_to_verify, + idp_id, + ) redirect_url = await sso_auth_provider.handle_redirect_request( request, None, session_id diff --git a/synapse/rest/client/auth.py b/synapse/rest/client/auth.py index 600bb51a7e..2a42520907 100644 --- a/synapse/rest/client/auth.py +++ b/synapse/rest/client/auth.py @@ -20,7 +20,7 @@ # import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from twisted.web.server import Request @@ -67,6 +67,11 @@ class AuthRestServlet(RestServlet): if not session: raise SynapseError(400, "No session supplied") + # Unstable query parameter which allows clients to specify the IDP + # they wish to use for SSO. + # XXX: This needs an MSC and an experimental flag. + idp_id: Optional[str] = parse_string(request, "io.element.idp_id") + if stagetype == "org.matrix.cross_signing_reset": if self.hs.config.mas.enabled: assert isinstance(self.auth, MasDelegatedAuth) @@ -114,7 +119,7 @@ class AuthRestServlet(RestServlet): elif stagetype == LoginType.SSO: # Display a confirmation page which prompts the user to # re-authenticate with their SSO provider. - html = await self.auth_handler.start_sso_ui_auth(request, session) + html = await self.auth_handler.start_sso_ui_auth(request, session, idp_id) elif stagetype == LoginType.REGISTRATION_TOKEN: html = self.registration_token_template.render(