summary refs log tree commit diff
diff options
context:
space:
mode:
authorErik Johnston <erik@matrix.org>2015-10-12 16:08:34 +0100
committerErik Johnston <erik@matrix.org>2015-10-12 16:08:34 +0100
commit427943907f41a94751f101963ce25bebd93f4271 (patch)
tree41f59b0bec6d27a8b51c0a6f9da3b6717ce29e40
parentMerge pull request #296 from matrix-org/markjh/eventstream_presence (diff)
parentAdd a comment to clarify why we split on closing curly brace when reading CAS... (diff)
downloadsynapse-427943907f41a94751f101963ce25bebd93f4271.tar.xz
Merge pull request #299 from stevenhammerton/sh-cas-required-attribute
SH CAS Required Attribute
-rw-r--r--synapse/config/cas.py4
-rw-r--r--synapse/rest/client/v1/login.py84
2 files changed, 61 insertions, 27 deletions
diff --git a/synapse/config/cas.py b/synapse/config/cas.py
index 81d034e8f0..d268680729 100644
--- a/synapse/config/cas.py
+++ b/synapse/config/cas.py
@@ -27,13 +27,17 @@ class CasConfig(Config):
         if cas_config:
             self.cas_enabled = True
             self.cas_server_url = cas_config["server_url"]
+            self.cas_required_attributes = cas_config.get("required_attributes", {})
         else:
             self.cas_enabled = False
             self.cas_server_url = None
+            self.cas_required_attributes = {}
 
     def default_config(self, config_dir_path, server_name, **kwargs):
         return """
         # Enable CAS for registration and login.
         #cas_config:
         #   server_url: "https://cas-server.com"
+        #   #required_attributes:
+        #   #    name: value
         """
diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py
index a99dcaab6f..2e3e4f39f3 100644
--- a/synapse/rest/client/v1/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -45,8 +45,8 @@ class LoginRestServlet(ClientV1RestServlet):
         self.idp_redirect_url = hs.config.saml2_idp_redirect_url
         self.saml2_enabled = hs.config.saml2_enabled
         self.cas_enabled = hs.config.cas_enabled
-
         self.cas_server_url = hs.config.cas_server_url
+        self.cas_required_attributes = hs.config.cas_required_attributes
         self.servername = hs.config.server_name
 
     def on_GET(self, request):
@@ -125,6 +125,47 @@ class LoginRestServlet(ClientV1RestServlet):
 
     @defer.inlineCallbacks
     def do_cas_login(self, cas_response_body):
+        user, attributes = self.parse_cas_response(cas_response_body)
+
+        for required_attribute, required_value in self.cas_required_attributes.items():
+            # If required attribute was not in CAS Response - Forbidden
+            if required_attribute not in attributes:
+                raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED)
+
+            # Also need to check value
+            if required_value is not None:
+                actual_value = attributes[required_attribute]
+                # If required attribute value does not match expected - Forbidden
+                if required_value != actual_value:
+                    raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED)
+
+        user_id = UserID.create(user, self.hs.hostname).to_string()
+        auth_handler = self.handlers.auth_handler
+        user_exists = yield auth_handler.does_user_exist(user_id)
+        if user_exists:
+            user_id, access_token, refresh_token = (
+                yield auth_handler.login_with_cas_user_id(user_id)
+            )
+            result = {
+                "user_id": user_id,  # may have changed
+                "access_token": access_token,
+                "refresh_token": refresh_token,
+                "home_server": self.hs.hostname,
+            }
+
+        else:
+            user_id, access_token = (
+                yield self.handlers.registration_handler.register(localpart=user)
+            )
+            result = {
+                "user_id": user_id,  # may have changed
+                "access_token": access_token,
+                "home_server": self.hs.hostname,
+            }
+
+        defer.returnValue((200, result))
+
+    def parse_cas_response(self, cas_response_body):
         root = ET.fromstring(cas_response_body)
         if not root.tag.endswith("serviceResponse"):
             raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
@@ -133,33 +174,22 @@ class LoginRestServlet(ClientV1RestServlet):
         for child in root[0]:
             if child.tag.endswith("user"):
                 user = child.text
-                user_id = UserID.create(user, self.hs.hostname).to_string()
-                auth_handler = self.handlers.auth_handler
-                user_exists = yield auth_handler.does_user_exist(user_id)
-                if user_exists:
-                    user_id, access_token, refresh_token = (
-                        yield auth_handler.login_with_cas_user_id(user_id)
-                    )
-                    result = {
-                        "user_id": user_id,  # may have changed
-                        "access_token": access_token,
-                        "refresh_token": refresh_token,
-                        "home_server": self.hs.hostname,
-                    }
-
-                else:
-                    user_id, access_token = (
-                        yield self.handlers.registration_handler.register(localpart=user)
-                    )
-                    result = {
-                        "user_id": user_id,  # may have changed
-                        "access_token": access_token,
-                        "home_server": self.hs.hostname,
-                    }
-
-                defer.returnValue((200, result))
+            if child.tag.endswith("attributes"):
+                attributes = {}
+                for attribute in child:
+                    # ElementTree library expands the namespace in attribute tags
+                    # to the full URL of the namespace.
+                    # See (https://docs.python.org/2/library/xml.etree.elementtree.html)
+                    # We don't care about namespace here and it will always be encased in
+                    # curly braces, so we remove them.
+                    if "}" in attribute.tag:
+                        attributes[attribute.tag.split("}")[1]] = attribute.text
+                    else:
+                        attributes[attribute.tag] = attribute.text
+        if user is None or attributes is None:
+            raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
 
-        raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
+        return (user, attributes)
 
 
 class LoginFallbackRestServlet(ClientV1RestServlet):