Combine the SSO Redirect Servlets (#9015)

* Implement CasHandler.handle_redirect_request

... to make it match OidcHandler and SamlHandler

* Clean up interface for OidcHandler.handle_redirect_request

Make it accept `client_redirect_url=None`.

* Clean up interface for `SamlHandler.handle_redirect_request`

... bring it into line with CAS and OIDC by making it take a Request parameter,
move the magic for `client_redirect_url` for UIA into the handler, and fix the
return type to be a `str` rather than a `bytes`.

* Define a common protocol for SSO auth provider impls

* Give SsoIdentityProvider an ID and register them

* Combine the SSO Redirect servlets

Now that the SsoHandler knows about the identity providers, we can combine the
various *RedirectServlets into a single implementation which delegates to the
right IdP.

* changelog
This commit is contained in:
Richard van der Hoff 2021-01-04 18:13:49 +00:00 committed by GitHub
parent 31b1905e13
commit d2c616a413
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 174 additions and 113 deletions

View file

@ -12,15 +12,16 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import logging
from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional
import attr
from typing_extensions import NoReturn
from typing_extensions import NoReturn, Protocol
from twisted.web.http import Request
from synapse.api.errors import RedirectException, SynapseError
from synapse.api.errors import Codes, RedirectException, SynapseError
from synapse.http.server import respond_with_html
from synapse.http.site import SynapseRequest
from synapse.types import JsonDict, UserID, contains_invalid_mxid_characters
@ -40,6 +41,53 @@ class MappingException(Exception):
"""
class SsoIdentityProvider(Protocol):
"""Abstract base class to be implemented by SSO Identity Providers
An Identity Provider, or IdP, is an external HTTP service which authenticates a user
to say whether they should be allowed to log in, or perform a given action.
Synapse supports various implementations of IdPs, including OpenID Connect, SAML,
and CAS.
The main entry point is `handle_redirect_request`, which should return a URI to
redirect the user's browser to the IdP's authentication page.
Each IdP should be registered with the SsoHandler via
`hs.get_sso_handler().register_identity_provider()`, so that requests to
`/_matrix/client/r0/login/sso/redirect` can be correctly dispatched.
"""
@property
@abc.abstractmethod
def idp_id(self) -> str:
"""A unique identifier for this SSO provider
Eg, "saml", "cas", "github"
"""
@abc.abstractmethod
async def handle_redirect_request(
self,
request: SynapseRequest,
client_redirect_url: Optional[bytes],
ui_auth_session_id: Optional[str] = None,
) -> str:
"""Handle an incoming request to /login/sso/redirect
Args:
request: the incoming HTTP request
client_redirect_url: the URL that we should redirect the
client to after login (or None for UI Auth).
ui_auth_session_id: The session ID of the ongoing UI Auth (or
None if this is a login).
Returns:
URL to redirect to
"""
raise NotImplementedError()
@attr.s
class UserAttributes:
# the localpart of the mxid that the mapper has assigned to the user.
@ -100,6 +148,14 @@ class SsoHandler:
# a map from session id to session data
self._username_mapping_sessions = {} # type: Dict[str, UsernameMappingSession]
# map from idp_id to SsoIdentityProvider
self._identity_providers = {} # type: Dict[str, SsoIdentityProvider]
def register_identity_provider(self, p: SsoIdentityProvider):
p_id = p.idp_id
assert p_id not in self._identity_providers
self._identity_providers[p_id] = p
def render_error(
self,
request: Request,
@ -124,6 +180,32 @@ class SsoHandler:
)
respond_with_html(request, code, html)
async def handle_redirect_request(
self, request: SynapseRequest, client_redirect_url: bytes,
) -> str:
"""Handle a request to /login/sso/redirect
Args:
request: incoming HTTP request
client_redirect_url: the URL that we should redirect the
client to after login.
Returns:
the URI to redirect to
"""
if not self._identity_providers:
raise SynapseError(
400, "Homeserver not configured for SSO.", errcode=Codes.UNRECOGNIZED
)
# if we only have one auth provider, redirect to it directly
if len(self._identity_providers) == 1:
ap = next(iter(self._identity_providers.values()))
return await ap.handle_redirect_request(request, client_redirect_url)
# otherwise, we have a configuration error
raise Exception("Multiple SSO identity providers have been configured!")
async def get_sso_user_by_remote_user_id(
self, auth_provider_id: str, remote_user_id: str
) -> Optional[str]: