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

SAML2 Improvements and redirect stuff #5316

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class LoginType(object):
EMAIL_IDENTITY = u"m.login.email.identity"
MSISDN = u"m.login.msisdn"
RECAPTCHA = u"m.login.recaptcha"
SSO = u"m.login.sso"
TERMS = u"m.login.terms"
DUMMY = u"m.login.dummy"

Expand Down
1 change: 1 addition & 0 deletions synapse/config/saml2_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def default_config(self, config_dir_path, server_name, **kwargs):
# override them.
#
#saml2_config:
# enabled: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh I think this is redundant. The default is for 'enabled' to be considered True: the only reason that the config parser checks it is to avoid breaking people who have enabled: false in their config files.

# sp_config:
# # point this to the IdP's metadata. You can use either a local file or
# # (preferably) a URL.
Expand Down
3 changes: 3 additions & 0 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,9 @@ def validate_login(self, username, login_submission):
if canonical_user_id:
defer.returnValue((canonical_user_id, None))

if login_type == LoginType.SSO:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you share the thinking behind this change? it looks like it's turning a 400 into a 403, but which endpoint is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, because of this

canonical_user_id, callback = yield self.auth_handler.validate_login(
identifier["user"],
login_submission,
)

Due to no if condition being met it would fail because SSO is not a valid login type.

known_login_type = True

if not known_login_type:
raise SynapseError(400, "Unknown login type %s" % login_type)

Expand Down
46 changes: 46 additions & 0 deletions synapse/rest/client/v1/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
from synapse.types import UserID, map_username_to_mxid_localpart
from synapse.util.msisdn import phone_number_to_msisdn

import saml2
from saml2.client import Saml2Client

from .base import ClientV1RestServlet, client_path_patterns

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -93,6 +96,7 @@ def __init__(self, hs):
self.jwt_enabled = hs.config.jwt_enabled
self.jwt_secret = hs.config.jwt_secret
self.jwt_algorithm = hs.config.jwt_algorithm
self.saml2_enabled = hs.config.saml2_enabled
self.cas_enabled = hs.config.cas_enabled
self.auth_handler = self.hs.get_auth_handler()
self.registration_handler = hs.get_registration_handler()
Expand All @@ -104,6 +108,9 @@ def on_GET(self, request):
flows = []
if self.jwt_enabled:
flows.append({"type": LoginRestServlet.JWT_TYPE})
if self.saml2_enabled:
flows.append({"type": LoginRestServlet.SSO_TYPE})
flows.append({"type": LoginRestServlet.TOKEN_TYPE})
if self.cas_enabled:
flows.append({"type": LoginRestServlet.SSO_TYPE})

Expand Down Expand Up @@ -474,6 +481,43 @@ def parse_cas_response(self, cas_response_body):
return user, attributes


class SSORedirectServlet(RestServlet):
PATTERNS = client_path_patterns("/login/sso/redirect")

def __init__(self, hs):
super(SSORedirectServlet, self).__init__()
self.saml2_sp_config = hs.config.saml2_sp_config

def on_GET(self, request):
args = request.args

saml_client = Saml2Client(self.saml2_sp_config)
reqid, info = saml_client.prepare_for_authenticate()

redirect_url = None

# Select the IdP URL to send the AuthN request to
for key, value in info['headers']:
if key is 'Location':
redirect_url = value

if redirect_url is None:
raise LoginError(401, "Unsuccessful SSO SAML2 redirect url response",
errcode=Codes.UNAUTHORIZED)

relay_state = "/_matrix/client/r0/login"
if b"redirectUrl" in args:
relay_state = args[b"redirectUrl"][0]

url_parts = list(urllib.parse.urlparse(redirect_url))
query = dict(urllib.parse.parse_qsl(url_parts[4]))
query.update({"RelayState": relay_state})
url_parts[4] = urllib.parse.urlencode(query)

request.redirect(urllib.parse.urlunparse(url_parts))
finish_request(request)


class SSOAuthHandler(object):
"""
Utility class for Resources and Servlets which handle the response from a SSO
Expand Down Expand Up @@ -549,3 +593,5 @@ def register_servlets(hs, http_server):
if hs.config.cas_enabled:
CasRedirectServlet(hs).register(http_server)
CasTicketServlet(hs).register(http_server)
if hs.config.saml2_enabled:
SSORedirectServlet(hs).register(http_server)
6 changes: 4 additions & 2 deletions synapse/static/client/login/js/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var show_login = function() {
}

if (matrixLogin.serverAcceptsSso) {
$("#sso_form").attr("action", "/_matrix/client/r0/login/sso/redirect");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which point the default? This change was needed as otherwise no SSO redirect would have happened.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default action for #sso_form is /_matrix/client/r0/login/sso/redirect: https://github.com/matrix-org/synapse/blob/master/synapse/static/client/login/index.html#L22

$("#sso_flow").show();
} else if (matrixLogin.serverAcceptsCas) {
$("#sso_form").attr("action", "/_matrix/client/r0/login/cas/redirect");
Expand All @@ -79,7 +80,7 @@ var fetch_info = function(cb) {
$.get(matrixLogin.endpoint, function(response) {
var serverAcceptsPassword = false;
var serverAcceptsCas = false;
for (var i=0; i<response.flows.length; i++) {
for (var i = 0; i < response.flows.length; i++) {
var flow = response.flows[i];
if ("m.login.cas" === flow.type) {
matrixLogin.serverAcceptsCas = true;
Expand Down Expand Up @@ -121,6 +122,7 @@ matrixLogin.onLogin = function(response) {
// clobber this function
console.log("onLogin - This function should be replaced to proceed.");
console.log(response);
alert("Login successful!");
};

var parseQsFromUrl = function(query) {
Expand All @@ -143,7 +145,7 @@ var try_token = function() {
if (pos == -1) {
return false;
}
var qs = parseQsFromUrl(window.location.href.substr(pos+1));
var qs = parseQsFromUrl(window.location.href.substr(pos + 1));

var loginToken = qs.loginToken;

Expand Down