Merge pull request #299 from stevenhammerton/sh-cas-required-attribute

SH CAS Required Attribute
This commit is contained in:
Erik Johnston 2015-10-12 16:08:34 +01:00
commit 427943907f
2 changed files with 61 additions and 27 deletions

View file

@ -27,13 +27,17 @@ class CasConfig(Config):
if cas_config: if cas_config:
self.cas_enabled = True self.cas_enabled = True
self.cas_server_url = cas_config["server_url"] self.cas_server_url = cas_config["server_url"]
self.cas_required_attributes = cas_config.get("required_attributes", {})
else: else:
self.cas_enabled = False self.cas_enabled = False
self.cas_server_url = None self.cas_server_url = None
self.cas_required_attributes = {}
def default_config(self, config_dir_path, server_name, **kwargs): def default_config(self, config_dir_path, server_name, **kwargs):
return """ return """
# Enable CAS for registration and login. # Enable CAS for registration and login.
#cas_config: #cas_config:
# server_url: "https://cas-server.com" # server_url: "https://cas-server.com"
# #required_attributes:
# # name: value
""" """

View file

@ -45,8 +45,8 @@ class LoginRestServlet(ClientV1RestServlet):
self.idp_redirect_url = hs.config.saml2_idp_redirect_url self.idp_redirect_url = hs.config.saml2_idp_redirect_url
self.saml2_enabled = hs.config.saml2_enabled self.saml2_enabled = hs.config.saml2_enabled
self.cas_enabled = hs.config.cas_enabled self.cas_enabled = hs.config.cas_enabled
self.cas_server_url = hs.config.cas_server_url self.cas_server_url = hs.config.cas_server_url
self.cas_required_attributes = hs.config.cas_required_attributes
self.servername = hs.config.server_name self.servername = hs.config.server_name
def on_GET(self, request): def on_GET(self, request):
@ -125,6 +125,47 @@ class LoginRestServlet(ClientV1RestServlet):
@defer.inlineCallbacks @defer.inlineCallbacks
def do_cas_login(self, cas_response_body): 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) root = ET.fromstring(cas_response_body)
if not root.tag.endswith("serviceResponse"): if not root.tag.endswith("serviceResponse"):
raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED) raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
@ -133,33 +174,22 @@ class LoginRestServlet(ClientV1RestServlet):
for child in root[0]: for child in root[0]:
if child.tag.endswith("user"): if child.tag.endswith("user"):
user = child.text user = child.text
user_id = UserID.create(user, self.hs.hostname).to_string() if child.tag.endswith("attributes"):
auth_handler = self.handlers.auth_handler attributes = {}
user_exists = yield auth_handler.does_user_exist(user_id) for attribute in child:
if user_exists: # ElementTree library expands the namespace in attribute tags
user_id, access_token, refresh_token = ( # to the full URL of the namespace.
yield auth_handler.login_with_cas_user_id(user_id) # 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
result = { # curly braces, so we remove them.
"user_id": user_id, # may have changed if "}" in attribute.tag:
"access_token": access_token, attributes[attribute.tag.split("}")[1]] = attribute.text
"refresh_token": refresh_token, else:
"home_server": self.hs.hostname, attributes[attribute.tag] = attribute.text
} if user is None or attributes is None:
raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
else: return (user, attributes)
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))
raise LoginError(401, "Invalid CAS response", errcode=Codes.UNAUTHORIZED)
class LoginFallbackRestServlet(ClientV1RestServlet): class LoginFallbackRestServlet(ClientV1RestServlet):