0
0
Fork 1
mirror of https://mau.dev/maunium/synapse.git synced 2024-11-19 08:24:25 +01:00

Merge branch 'rav/sso-confirm-whitelist' into 'release-v1.11.1'

Add a whitelist for the SSO confirmation step.

See merge request new-vector/synapse!3
This commit is contained in:
Richard van der Hoff 2020-03-02 17:05:09 +00:00
commit 809e8567f6
4 changed files with 84 additions and 14 deletions

View file

@ -1363,6 +1363,22 @@ saml2_config:
# Additional settings to use with single-sign on systems such as SAML2 and CAS. # Additional settings to use with single-sign on systems such as SAML2 and CAS.
# #
sso: sso:
# A list of client URLs which are whitelisted so that the user does not
# have to confirm giving access to their account to the URL. Any client
# whose URL starts with an entry in the following list will not be subject
# to an additional confirmation step after the SSO login is completed.
#
# WARNING: An entry such as "https://my.client" is insecure, because it
# will also match "https://my.client.evil.site", exposing your users to
# phishing attacks from evil.site. To avoid this, include a slash after the
# hostname: "https://my.client/".
#
# By default, this list is empty.
#
#client_whitelist:
# - https://riot.im/develop
# - https://my.custom.client/
# Directory in which Synapse will try to find the template files below. # Directory in which Synapse will try to find the template files below.
# If not set, default templates from within the Synapse package will be used. # If not set, default templates from within the Synapse package will be used.
# #
@ -1372,8 +1388,8 @@ sso:
# #
# Synapse will look for the following templates in this directory: # Synapse will look for the following templates in this directory:
# #
# * HTML page for confirmation of redirect during authentication: # * HTML page for a confirmation step before redirecting back to the client
# 'sso_redirect_confirm.html'. # with the login token: 'sso_redirect_confirm.html'.
# #
# When rendering, this template is given three variables: # When rendering, this template is given three variables:
# * redirect_url: the URL the user is about to be redirected to. Needs # * redirect_url: the URL the user is about to be redirected to. Needs

View file

@ -37,11 +37,29 @@ class SSOConfig(Config):
self.sso_redirect_confirm_template_dir = template_dir self.sso_redirect_confirm_template_dir = template_dir
self.sso_client_whitelist = sso_config.get("client_whitelist") or []
def generate_config_section(self, **kwargs): def generate_config_section(self, **kwargs):
return """\ return """\
# Additional settings to use with single-sign on systems such as SAML2 and CAS. # Additional settings to use with single-sign on systems such as SAML2 and CAS.
# #
sso: sso:
# A list of client URLs which are whitelisted so that the user does not
# have to confirm giving access to their account to the URL. Any client
# whose URL starts with an entry in the following list will not be subject
# to an additional confirmation step after the SSO login is completed.
#
# WARNING: An entry such as "https://my.client" is insecure, because it
# will also match "https://my.client.evil.site", exposing your users to
# phishing attacks from evil.site. To avoid this, include a slash after the
# hostname: "https://my.client/".
#
# By default, this list is empty.
#
#client_whitelist:
# - https://riot.im/develop
# - https://my.custom.client/
# Directory in which Synapse will try to find the template files below. # Directory in which Synapse will try to find the template files below.
# If not set, default templates from within the Synapse package will be used. # If not set, default templates from within the Synapse package will be used.
# #

View file

@ -556,6 +556,9 @@ class SSOAuthHandler(object):
self._server_name = hs.config.server_name self._server_name = hs.config.server_name
# cast to tuple for use with str.startswith
self._whitelisted_sso_clients = tuple(hs.config.sso_client_whitelist)
async def on_successful_auth( async def on_successful_auth(
self, username, request, client_redirect_url, user_display_name=None self, username, request, client_redirect_url, user_display_name=None
): ):
@ -605,11 +608,6 @@ class SSOAuthHandler(object):
registered_user_id registered_user_id
) )
# Remove the query parameters from the redirect URL to get a shorter version of
# it. This is only to display a human-readable URL in the template, but not the
# URL we redirect users to.
redirect_url_no_params = client_redirect_url.split("?")[0]
# Append the login token to the original redirect URL (i.e. with its query # Append the login token to the original redirect URL (i.e. with its query
# parameters kept intact) to build the URL to which the template needs to # parameters kept intact) to build the URL to which the template needs to
# redirect the users once they have clicked on the confirmation link. # redirect the users once they have clicked on the confirmation link.
@ -617,17 +615,29 @@ class SSOAuthHandler(object):
client_redirect_url, "loginToken", login_token client_redirect_url, "loginToken", login_token
) )
# Serve the redirect confirmation page # if the client is whitelisted, we can redirect straight to it
if client_redirect_url.startswith(self._whitelisted_sso_clients):
request.redirect(redirect_url)
finish_request(request)
return
# Otherwise, serve the redirect confirmation page.
# Remove the query parameters from the redirect URL to get a shorter version of
# it. This is only to display a human-readable URL in the template, but not the
# URL we redirect users to.
redirect_url_no_params = client_redirect_url.split("?")[0]
html = self._template.render( html = self._template.render(
display_url=redirect_url_no_params, display_url=redirect_url_no_params,
redirect_url=redirect_url, redirect_url=redirect_url,
server_name=self._server_name, server_name=self._server_name,
) ).encode("utf-8")
request.setResponseCode(200) request.setResponseCode(200)
request.setHeader(b"Content-Type", b"text/html; charset=utf-8") request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
request.setHeader(b"Content-Length", b"%d" % (len(html),)) request.setHeader(b"Content-Length", b"%d" % (len(html),))
request.write(html.encode("utf8")) request.write(html)
finish_request(request) finish_request(request)
@staticmethod @staticmethod

View file

@ -268,13 +268,11 @@ class CASRedirectConfirmTestCase(unittest.HomeserverTestCase):
self.redirect_path = "_synapse/client/login/sso/redirect/confirm" self.redirect_path = "_synapse/client/login/sso/redirect/confirm"
config = self.default_config() config = self.default_config()
config["enable_registration"] = True
config["cas_config"] = { config["cas_config"] = {
"enabled": True, "enabled": True,
"server_url": "https://fake.test", "server_url": "https://fake.test",
"service_url": "https://matrix.goodserver.com:8448", "service_url": "https://matrix.goodserver.com:8448",
} }
config["public_baseurl"] = self.base_url
async def get_raw(uri, args): async def get_raw(uri, args):
"""Return an example response payload from a call to the `/proxyValidate` """Return an example response payload from a call to the `/proxyValidate`
@ -310,7 +308,7 @@ class CASRedirectConfirmTestCase(unittest.HomeserverTestCase):
"""Tests that the SSO login flow serves a confirmation page before redirecting a """Tests that the SSO login flow serves a confirmation page before redirecting a
user to the redirect URL. user to the redirect URL.
""" """
base_url = "/login/cas/ticket?redirectUrl" base_url = "/_matrix/client/r0/login/cas/ticket?redirectUrl"
redirect_url = "https://dodgy-site.com/" redirect_url = "https://dodgy-site.com/"
url_parts = list(urllib.parse.urlparse(base_url)) url_parts = list(urllib.parse.urlparse(base_url))
@ -325,6 +323,7 @@ class CASRedirectConfirmTestCase(unittest.HomeserverTestCase):
self.render(request) self.render(request)
# Test that the response is HTML. # Test that the response is HTML.
self.assertEqual(channel.code, 200)
content_type_header_value = "" content_type_header_value = ""
for header in channel.result.get("headers", []): for header in channel.result.get("headers", []):
if header[0] == b"Content-Type": if header[0] == b"Content-Type":
@ -337,3 +336,30 @@ class CASRedirectConfirmTestCase(unittest.HomeserverTestCase):
# And that it contains our redirect link # And that it contains our redirect link
self.assertIn(redirect_url, channel.result["body"].decode("UTF-8")) self.assertIn(redirect_url, channel.result["body"].decode("UTF-8"))
@override_config(
{
"sso": {
"client_whitelist": [
"https://legit-site.com/",
"https://other-site.com/",
]
}
}
)
def test_cas_redirect_whitelisted(self):
"""Tests that the SSO login flow serves a redirect to a whitelisted url
"""
redirect_url = "https://legit-site.com/"
cas_ticket_url = (
"/_matrix/client/r0/login/cas/ticket?redirectUrl=%s&ticket=ticket"
% (urllib.parse.quote(redirect_url))
)
# Get Synapse to call the fake CAS and serve the template.
request, channel = self.make_request("GET", cas_ticket_url)
self.render(request)
self.assertEqual(channel.code, 302)
location_headers = channel.headers.getRawHeaders("Location")
self.assertEqual(location_headers[0][: len(redirect_url)], redirect_url)