X-Git-Url: http://git.cascardo.eti.br/?a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2%2Fauth.py;h=d856220bc9f6a0baee80617f7bc644c1c103b9a7;hb=2751451f4158417e66974d6415d2da84f612ab3c;hp=46ad7ebe207bb429c15fc2b0b836efb2208a0fb7;hpb=45cb73a21a90084818c3057e362ef9459f1600f3;p=cascardo%2Fipsilon.git diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py index 46ad7eb..d856220 100644 --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -1,37 +1,26 @@ -# Copyright (C) 2014 Simo Sorce -# -# see file 'COPYING' for use and warranty information -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING from ipsilon.providers.common import ProviderPageBase, ProviderException 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.tools import saml2metadata as metadata +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): @@ -40,14 +29,29 @@ class AuthenticateRequest(ProviderPageBase): super(AuthenticateRequest, self).__init__(*args, **kwargs) self.stage = 'init' self.trans = None + self.binding = None def _preop(self, *args, **kwargs): try: # generate a new id or get current one self.trans = Transaction('saml2', **kwargs) - if self.trans.cookie.value != self.trans.provider: - self.debug('Invalid transaction, %s != %s' % ( - self.trans.cookie.value, self.trans.provider)) + + self.debug('self.binding=%s, transdata=%s' % + (self.binding, self.trans.retrieve())) + if self.binding is None: + # SAML binding is unknown, try to get it from transaction + transdata = self.trans.retrieve() + self.binding = transdata.get('saml2_binding') + else: + # SAML binding known, store in transaction + data = {'saml2_binding': self.binding} + self.trans.store(data) + + # Only check for cookie for those bindings which use one + if self.binding not in (metadata.SAML2_SERVICE_MAP['sso-soap'][1]): + if self.trans.cookie.value != self.trans.provider: + self.debug('Invalid transaction, %s != %s' % ( + self.trans.cookie.value, self.trans.provider)) except Exception, e: # pylint: disable=broad-except self.debug('Transaction initialization failed: %s' % repr(e)) raise cherrypy.HTTPError(400, 'Invalid transaction id') @@ -91,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 @@ -104,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 @@ -180,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 @@ -200,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() @@ -239,6 +274,18 @@ class AuthenticateRequest(ProviderPageBase): self.debug('Assertion: %s' % login.assertion.dump()) + saml_sessions = self.cfg.idp.sessionfactory + + lasso_session = lasso.Session() + lasso_session.addAssertion(login.remoteProviderId, login.assertion) + provider = ServiceProvider(self.cfg, login.remoteProviderId) + saml_sessions.add_session(login.assertion.id, + login.remoteProviderId, + user.name, + lasso_session.dump(), + None, + provider.logout_mechs) + def saml2error(self, login, code, message): status = lasso.Samlp2Status() status.statusCode = lasso.Samlp2StatusCode() @@ -253,7 +300,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, @@ -263,8 +310,12 @@ class AuthenticateRequest(ProviderPageBase): ], "submit": 'Return to application', } - # pylint: disable=star-args return self._template('saml2/post_response.html', **context) + elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_LECP: + login.buildResponseMsg() + self.debug("Returning ECP: %s" % login.msgBody) + return login.msgBody + else: raise cherrypy.HTTPError(500)