Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Support CAS in addition to SAML.
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep committed Mar 19, 2020
1 parent c79b150 commit bbbee5a
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 32 deletions.
6 changes: 2 additions & 4 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ def __init__(self, hs):
self.hs = hs # FIXME better possibility to access registrationHandler later?
self.macaroon_gen = hs.get_macaroon_generator()
self._password_enabled = hs.config.password_enabled
self._saml2_enabled = hs.config.saml2_enabled
# TODO CAS?
# self._cas_enabled = hs.config.cas_enabled
self._sso_enabled = hs.config.saml2_enabled or hs.config.cas_enabled

# we keep this as a list despite the O(N^2) implication so that we can
# keep PASSWORD first and avoid confusing clients which pick the first
Expand All @@ -108,7 +106,7 @@ def __init__(self, hs):
for t in provider.get_supported_login_types().keys():
if t not in login_types:
login_types.append(t)
if self._saml2_enabled:
if self._sso_enabled:
login_types.append(LoginType.SSO)
self._supported_login_types = login_types

Expand Down
2 changes: 0 additions & 2 deletions synapse/rest/client/v1/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,8 +486,6 @@ def handle_cas_response(self, request, cas_response_body, client_redirect_url):
if required_value != actual_value:
raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED)

# TODO Do something here to complete the ui-auth instead of login.

return self._sso_auth_handler.on_successful_auth(
user, request, client_redirect_url, displayname
)
Expand Down
111 changes: 85 additions & 26 deletions synapse/rest/client/v2_alpha/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
# limitations under the License.

import logging
import urllib

from twisted.web.client import PartialDownloadError

from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError
from synapse.api.urls import CLIENT_API_PREFIX
from synapse.http.server import finish_request
from synapse.http.servlet import RestServlet, parse_string
from synapse.rest.client.v1.login import CasTicketServlet, SSOAuthHandler
from synapse.types import UserID, map_username_to_mxid_localpart

from ._base import client_patterns

Expand Down Expand Up @@ -113,7 +118,7 @@
</html>
"""

SAML2_TEMPLATE = """
SSO_TEMPLATE = """
<html>
<head>
<title>Authentication</title>
Expand Down Expand Up @@ -147,7 +152,13 @@ def __init__(self, hs):
self.auth = hs.get_auth()
self.auth_handler = hs.get_auth_handler()
self.registration_handler = hs.get_registration_handler()

# SSO configuration.
self._saml_enabled = hs.config.saml2_enabled
self._saml_handler = hs.get_saml_handler()
self._cas_enabled = hs.config.cas_enabled
self._cas_server_url = hs.config.cas_server_url
self._cas_service_url = hs.config.cas_service_url

def on_GET(self, request, stagetype):
session = parse_string(request, "session")
Expand All @@ -170,14 +181,30 @@ def on_GET(self, request, stagetype):
% (CLIENT_API_PREFIX, LoginType.TERMS),
}
elif stagetype == LoginType.SSO:
# TODO Display a confirmation page which lets the user redirect to
# SAML / CAS.
client_redirect_url = ""
value = self._saml_handler.handle_redirect_request(
client_redirect_url, session
)
html = SAML2_TEMPLATE % {
"myurl": value,
# Display a confirmation page which prompts the user to
# re-authenticate with their SSO provider.
if self._cas_enabled:
# Generate a request to CAS that redirects back to an endpoint
# to verify the successful authentication.
hs_redirect_url = (
self._cas_service_url + "/_matrix/client/r0/auth/cas/ticket"
)
service_param = urllib.parse.urlencode(
{"service": "%s" % hs_redirect_url, "session": session}
)
sso_redirect_url = "%s/login?%s" % (self._cas_server_url, service_param)

elif self._saml_enabled:
client_redirect_url = ""
sso_redirect_url = self._saml_handler.handle_redirect_request(
client_redirect_url, session
)

else:
raise SynapseError(400, "Homeserver not configured for SSO.")

html = SSO_TEMPLATE % {
"myurl": sso_redirect_url,
}
else:
raise SynapseError(404, "Unknown auth stage type")
Expand Down Expand Up @@ -241,23 +268,7 @@ async def on_POST(self, request, stagetype):
}
elif stagetype == LoginType.SSO:
# TODO Display an error page here? Is the 404 below enough?
authdict = {"session": session}

# TODO
success = await self.auth_handler.add_oob_auth(
LoginType.TERMS, authdict, self.hs.get_ip_from_request(request)
)

if success:
html = SUCCESS_TEMPLATE
else:
client_redirect_url = ""
value = self._saml_handler.handle_redirect_request(
client_redirect_url, session
)
html = SAML2_TEMPLATE % {
"myurl": value,
}
raise SynapseError(404, "SSO should not POST here.")
else:
raise SynapseError(404, "Unknown auth stage type")

Expand All @@ -275,5 +286,53 @@ def on_OPTIONS(self, _):
return 200, {}


class SSOUIAuthHandler(SSOAuthHandler):
def __init__(self, hs):
self._hostname = hs.hostname
self._auth_handler = hs.get_auth_handler()

async def on_successful_auth(
self, username, request, client_redirect_url, user_display_name=None
):
# Pull the UI Auth session ID out.
session_id = parse_string(request, "session", required=True)

localpart = map_username_to_mxid_localpart(username)
user_id = UserID(localpart, self._hostname).to_string()
registered_user_id = await self._auth_handler.check_user_exists(user_id)

return self._auth_handler.complete_sso_ui_auth(
registered_user_id, session_id, request,
)


class CasAuthTicketServlet(CasTicketServlet):
PATTERNS = client_patterns(r"/auth/cas/ticket")

def __init__(self, hs):
super().__init__(hs)
# Override the auth handler.
self._sso_auth_handler = SSOUIAuthHandler(hs)

async def on_GET(self, request):
# TODO Check if CAS is enabled?

uri = self.cas_server_url + "/proxyValidate"
args = {
"ticket": parse_string(request, "ticket", required=True),
"service": self.cas_service_url,
}
try:
body = await self._http_client.get_raw(uri, args)
except PartialDownloadError as pde:
# Twisted raises this error if the connection is closed,
# even if that's being used old-http style to signal end-of-data
body = pde.response
result = await self.handle_cas_response(request, body, "")
return result


def register_servlets(hs, http_server):
AuthRestServlet(hs).register(http_server)
if hs.config.cas_enabled:
CasAuthTicketServlet(hs).register(http_server)

0 comments on commit bbbee5a

Please sign in to comment.