diff --git a/synapse/config/_base.pyi b/synapse/config/_base.pyi
index 86bc965ee4..3053fc9d27 100644
--- a/synapse/config/_base.pyi
+++ b/synapse/config/_base.pyi
@@ -24,6 +24,7 @@ from synapse.config import (
server,
server_notices_config,
spam_checker,
+ sso,
stats,
third_party_event_rules,
tls,
@@ -57,6 +58,7 @@ class RootConfig:
key: key.KeyConfig
saml2: saml2_config.SAML2Config
cas: cas.CasConfig
+ sso: sso.SSOConfig
jwt: jwt_config.JWTConfig
password: password.PasswordConfig
email: emailconfig.EmailConfig
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index 6e348671c7..b4bca08b20 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -38,6 +38,7 @@ from .saml2_config import SAML2Config
from .server import ServerConfig
from .server_notices_config import ServerNoticesConfig
from .spam_checker import SpamCheckerConfig
+from .sso import SSOConfig
from .stats import StatsConfig
from .third_party_event_rules import ThirdPartyRulesConfig
from .tls import TlsConfig
@@ -65,6 +66,7 @@ class HomeServerConfig(RootConfig):
KeyConfig,
SAML2Config,
CasConfig,
+ SSOConfig,
JWTConfig,
PasswordConfig,
EmailConfig,
diff --git a/synapse/config/sso.py b/synapse/config/sso.py
new file mode 100644
index 0000000000..f426b65b4f
--- /dev/null
+++ b/synapse/config/sso.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Matrix.org Foundation C.I.C.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+from typing import Any, Dict
+
+import pkg_resources
+
+from ._base import Config, ConfigError
+
+
+class SSOConfig(Config):
+ """SSO Configuration
+ """
+
+ section = "sso"
+
+ def read_config(self, config, **kwargs):
+ sso_config = config.get("sso") or {} # type: Dict[str, Any]
+
+ # Pick a template directory in order of:
+ # * The sso-specific template_dir
+ # * /path/to/synapse/install/res/templates
+ template_dir = sso_config.get("template_dir")
+ if not template_dir:
+ template_dir = pkg_resources.resource_filename("synapse", "res/templates",)
+
+ self.sso_redirect_confirm_template_dir = template_dir
+
+ def generate_config_section(self, **kwargs):
+ return """\
+ # Additional settings to use with single-sign on systems such as SAML2 and CAS.
+ #
+ sso:
+ # 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.
+ #
+ # DO NOT UNCOMMENT THIS SETTING unless you want to customise the templates.
+ # If you *do* uncomment it, you will need to make sure that all the templates
+ # below are in the directory.
+ #
+ # Synapse will look for the following templates in this directory:
+ #
+ # * HTML page for a confirmation step before redirecting back to the client
+ # with the login token: 'sso_redirect_confirm.html'.
+ #
+ # When rendering, this template is given three variables:
+ # * redirect_url: the URL the user is about to be redirected to. Needs
+ # manual escaping (see
+ # https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
+ #
+ # * display_url: the same as `redirect_url`, but with the query
+ # parameters stripped. The intention is to have a
+ # human-readable URL to show to users, not to use it as
+ # the final address to redirect to. Needs manual escaping
+ # (see https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
+ #
+ # * server_name: the homeserver's name.
+ #
+ # You can see the default templates at:
+ # https://github.com/matrix-org/synapse/tree/master/synapse/res/templates
+ #
+ #template_dir: "res/templates"
+ """
diff --git a/synapse/res/templates/sso_redirect_confirm.html b/synapse/res/templates/sso_redirect_confirm.html
new file mode 100644
index 0000000000..20a15e1e74
--- /dev/null
+++ b/synapse/res/templates/sso_redirect_confirm.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>SSO redirect confirmation</title>
+</head>
+ <body>
+ <p>The application at <span style="font-weight:bold">{{ display_url | e }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p>
+ <p>If you don't recognise this address, you should ignore this and close this tab.</p>
+ <p>
+ <a href="{{ redirect_url | e }}">I trust this address</a>
+ </p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py
index 1294e080dc..1acfd01d8e 100644
--- a/synapse/rest/client/v1/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -29,6 +29,7 @@ from synapse.http.servlet import (
parse_string,
)
from synapse.http.site import SynapseRequest
+from synapse.push.mailer import load_jinja2_templates
from synapse.rest.client.v2_alpha._base import client_patterns
from synapse.rest.well_known import WellKnownBuilder
from synapse.types import UserID, map_username_to_mxid_localpart
@@ -548,6 +549,13 @@ class SSOAuthHandler(object):
self._registration_handler = hs.get_registration_handler()
self._macaroon_gen = hs.get_macaroon_generator()
+ # Load the redirect page HTML template
+ self._template = load_jinja2_templates(
+ hs.config.sso_redirect_confirm_template_dir, ["sso_redirect_confirm.html"],
+ )[0]
+
+ self._server_name = hs.config.server_name
+
async def on_successful_auth(
self, username, request, client_redirect_url, user_display_name=None
):
@@ -592,21 +600,41 @@ class SSOAuthHandler(object):
request:
client_redirect_url:
"""
-
+ # Create a login token
login_token = self._macaroon_gen.generate_short_term_login_token(
registered_user_id
)
- redirect_url = self._add_login_token_to_redirect_url(
- client_redirect_url, login_token
+
+ # 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
+ # 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_url = self._add_query_param_to_url(
+ client_redirect_url, "loginToken", login_token
+ )
+
+ # Serve the redirect confirmation page
+ html = self._template.render(
+ display_url=redirect_url_no_params,
+ redirect_url=redirect_url,
+ server_name=self._server_name,
)
- request.redirect(redirect_url)
+
+ request.setResponseCode(200)
+ request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
+ request.setHeader(b"Content-Length", b"%d" % (len(html),))
+ request.write(html.encode("utf8"))
finish_request(request)
@staticmethod
- def _add_login_token_to_redirect_url(url, token):
+ def _add_query_param_to_url(url, param_name, param):
url_parts = list(urllib.parse.urlparse(url))
query = dict(urllib.parse.parse_qsl(url_parts[4]))
- query.update({"loginToken": token})
+ query.update({param_name: param})
url_parts[4] = urllib.parse.urlencode(query)
return urllib.parse.urlunparse(url_parts)
|