X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2%2Fauth.py;h=9d2bb7d065ed84bf06ae7d0c92f408834515f264;hp=87f4ac8a266df42c2cab4dc291228529cd2bb4fc;hb=1bcc0d697dd37a9268641f0cbaa7e9e781552233;hpb=d274763d8dc06b42f70014b14fcb2e852c086751 diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py old mode 100755 new mode 100644 index 87f4ac8..9d2bb7d --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -1,5 +1,3 @@ -#!/usr/bin/python -# # Copyright (C) 2014 Simo Sorce # # see file 'COPYING' for use and warranty information @@ -22,18 +20,22 @@ from ipsilon.providers.common import AuthenticationError, InvalidRequest from ipsilon.providers.saml2.provider import ServiceProvider from ipsilon.providers.saml2.provider import InvalidProviderId from ipsilon.providers.saml2.provider import NameIdNotAllowed +from ipsilon.providers.saml2.sessions import SAMLSessionsContainer +from ipsilon.util.policy import Policy from ipsilon.util.user import UserSession from ipsilon.util.trans import Transaction import cherrypy import datetime import lasso +import uuid +import hashlib class UnknownProvider(ProviderException): def __init__(self, message): super(UnknownProvider, self).__init__(message) - self._debug(message) + self.debug(message) class AuthenticateRequest(ProviderPageBase): @@ -93,7 +95,7 @@ class AuthenticateRequest(ProviderPageBase): e, message) raise UnknownProvider(msg) - self._debug('SP %s requested authentication' % login.remoteProviderId) + self.debug('SP %s requested authentication' % login.remoteProviderId) return login @@ -106,13 +108,13 @@ class AuthenticateRequest(ProviderPageBase): try: login = self._parse_request(request) except InvalidRequest, e: - self._debug(str(e)) + self.debug(str(e)) raise cherrypy.HTTPError(400, 'Invalid SAML request token') except UnknownProvider, e: - self._debug(str(e)) + self.debug(str(e)) raise cherrypy.HTTPError(400, 'Unknown Service Provider') except Exception, e: # pylint: disable=broad-except - self._debug(str(e)) + self.debug(str(e)) raise cherrypy.HTTPError(500) return login @@ -127,7 +129,8 @@ class AuthenticateRequest(ProviderPageBase): self.basepath, self.trans.get_GET_arg()) data = {'saml2_stage': 'auth', 'saml2_request': login.dump(), - 'login_return': returl} + 'login_return': returl, + 'login_target': login.remoteProviderId} self.trans.store(data) redirect = '%s/login?%s' % (self.basepath, self.trans.get_GET_arg()) @@ -181,17 +184,27 @@ class AuthenticateRequest(ProviderPageBase): nameid = None if nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT: - # TODO map to something else ? - nameid = provider.normalize_username(user.name) + idpsalt = self.cfg.idp_nameid_salt + if idpsalt is None: + raise AuthenticationError( + "idp nameid salt is not set in configuration" + ) + value = hashlib.sha512() + value.update(idpsalt) + value.update(login.remoteProviderId) + value.update(user.name) + nameid = '_' + value.hexdigest() elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT: - # TODO map to something else ? - nameid = provider.normalize_username(user.name) + nameid = '_' + uuid.uuid4().hex elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS: - nameid = us.get_data('user', 'krb_principal_name') + userattrs = us.get_user_attrs() + nameid = userattrs.get('gssapi_principal_name') elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL: nameid = us.get_user().email if not nameid: nameid = '%s@%s' % (user.name, self.cfg.default_email_domain) + elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED: + nameid = provider.normalize_username(user.name) if nameid: login.assertion.subject.nameId.format = nameidfmt @@ -201,29 +214,50 @@ class AuthenticateRequest(ProviderPageBase): raise AuthenticationError("Unavailable Name ID type", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) - # TODO: filter user attributes as policy requires from 'usersession' - if not login.assertion.attributeStatement: - attrstat = lasso.Saml2AttributeStatement() - login.assertion.attributeStatement = [attrstat] + # Check attribute policy and perform mapping and filtering. + # If the SP has its own mapping or filtering policy use that + # instead of the global policy. + if (provider.attribute_mappings is not None and + len(provider.attribute_mappings) > 0): + attribute_mappings = provider.attribute_mappings else: - attrstat = login.assertion.attributeStatement[0] - if not attrstat.attribute: - attrstat.attribute = () - - attributes = dict() + attribute_mappings = self.cfg.default_attribute_mapping + if (provider.allowed_attributes is not None and + len(provider.allowed_attributes) > 0): + allowed_attributes = provider.allowed_attributes + else: + allowed_attributes = self.cfg.default_allowed_attributes + self.debug("Allowed attrs: %s" % allowed_attributes) + self.debug("Mapping: %s" % attribute_mappings) + policy = Policy(attribute_mappings, allowed_attributes) userattrs = us.get_user_attrs() - for key, value in userattrs.get('userdata', {}).iteritems(): - if type(value) is str: - attributes[key] = value - if 'groups' in userattrs: - attributes['group'] = userattrs['groups'] - for _, info in userattrs.get('extras', {}).iteritems(): - for key, value in info.items(): - attributes[key] = value + mappedattrs, _ = policy.map_attributes(userattrs) + attributes = policy.filter_attributes(mappedattrs) + + if '_groups' in attributes and 'groups' not in attributes: + attributes['groups'] = attributes['_groups'] + + self.debug("%s's attributes: %s" % (user.name, attributes)) + + # The saml-core-2.0-os specification section 2.7.3 requires + # the AttributeStatement element to be non-empty. + if attributes: + if not login.assertion.attributeStatement: + attrstat = lasso.Saml2AttributeStatement() + login.assertion.attributeStatement = [attrstat] + else: + attrstat = login.assertion.attributeStatement[0] + if not attrstat.attribute: + attrstat.attribute = () for key in attributes: + # skip internal info + if key[0] == '_': + continue values = attributes[key] - if type(values) is not list: + if isinstance(values, dict): + continue + if not isinstance(values, list): values = [values] for value in values: attr = lasso.Saml2Attribute() @@ -240,6 +274,24 @@ class AuthenticateRequest(ProviderPageBase): self.debug('Assertion: %s' % login.assertion.dump()) + saml_sessions = us.get_provider_data('saml2') + if saml_sessions is None: + saml_sessions = SAMLSessionsContainer() + + session = saml_sessions.find_session_by_provider( + login.remoteProviderId) + if session: + # TODO: something... + self.debug('Login session for this user already exists!?') + session.dump() + + lasso_session = lasso.Session() + lasso_session.addAssertion(login.remoteProviderId, login.assertion) + saml_sessions.add_session(login.assertion.id, + login.remoteProviderId, + lasso_session) + us.save_provider_data('saml2', saml_sessions) + def saml2error(self, login, code, message): status = lasso.Samlp2Status() status.statusCode = lasso.Samlp2StatusCode() @@ -254,7 +306,7 @@ class AuthenticateRequest(ProviderPageBase): raise cherrypy.HTTPError(501) elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST: login.buildAuthnResponseMsg() - self._debug('POSTing back to SP [%s]' % (login.msgUrl)) + self.debug('POSTing back to SP [%s]' % (login.msgUrl)) context = { "title": 'Redirecting back to the web application', "action": login.msgUrl, @@ -264,7 +316,6 @@ class AuthenticateRequest(ProviderPageBase): ], "submit": 'Return to application', } - # pylint: disable=star-args return self._template('saml2/post_response.html', **context) else: