X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2%2Fauth.py;h=cc41bb8f3550ef5f4d35b28e04581a8512fc53f0;hp=12af16b337474a83953c380ca74e4bd29c33fc4a;hb=HEAD;hpb=81ad559af403d4d62f21209d34ba00833e007300 diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py index 12af16b..cc41bb8 100644 --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -1,26 +1,11 @@ -# 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.providers.saml2.sessions import SAMLSessionsContainer +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 @@ -35,23 +20,38 @@ class UnknownProvider(ProviderException): def __init__(self, message): super(UnknownProvider, self).__init__(message) - self._debug(message) + self.debug(message) class AuthenticateRequest(ProviderPageBase): - def __init__(self, *args, **kwargs): - super(AuthenticateRequest, self).__init__(*args, **kwargs) + def __init__(self, site, provider, *args, **kwargs): + super(AuthenticateRequest, self).__init__(site, provider) 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') @@ -95,27 +95,69 @@ 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 - def saml2login(self, request): + def _idp_initiated_login(self, spidentifier, relaystate): + """ + Perform an Idp-initiated login + + Exceptions are handled by the caller + """ + login = self.cfg.idp.get_login_handler() - if not request: + login.initIdpInitiatedAuthnRequest(spidentifier) + + # Hardcode for now, handle Artifact later + login.request.protocolBinding = lasso.SAML2_METADATA_BINDING_POST + + login.processAuthnRequestMsg() + + if relaystate is not None: + login.msgRelayState = relaystate + else: + provider = ServiceProvider(self.cfg, login.remoteProviderId) + if provider.splink is not None: + login.msgRelayState = provider.splink + else: + login.msgRelayState = login.remoteProviderId + + return login + + def saml2login(self, request, spidentifier=None, relaystate=None): + """ + request: the SAML request + spidentifier: the provider ID for IdP-initiated login + relaystate: optional string to direct user to particular place on + the SP after sending POST. If one is not provided then + the protected site from the SP is used, otherwise it + is set to the remote provider ID. + """ + if not request and not spidentifier: raise cherrypy.HTTPError(400, 'SAML request token missing or empty') - try: - login = self._parse_request(request) - except InvalidRequest, e: - self._debug(str(e)) - raise cherrypy.HTTPError(400, 'Invalid SAML request token') - except UnknownProvider, e: - self._debug(str(e)) - raise cherrypy.HTTPError(400, 'Unknown Service Provider') - except Exception, e: # pylint: disable=broad-except - self._debug(str(e)) - raise cherrypy.HTTPError(500) + if spidentifier: + try: + login = self._idp_initiated_login(spidentifier, relaystate) + except lasso.ServerProviderNotFoundError: + raise cherrypy.HTTPError(400, 'Unknown Service Provider') + except Exception, e: # pylint: disable=broad-except + self.debug(str(e)) + raise cherrypy.HTTPError(500) + else: + try: + login = self._parse_request(request) + except InvalidRequest, e: + self.debug(str(e)) + raise cherrypy.HTTPError(400, 'Invalid SAML request token') + except UnknownProvider, e: + self.debug(str(e)) + raise cherrypy.HTTPError(400, 'Unknown Service Provider') + except Exception, e: # pylint: disable=broad-except + self.debug(str(e)) + raise cherrypy.HTTPError(500) return login @@ -197,7 +239,8 @@ class AuthenticateRequest(ProviderPageBase): elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT: 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: @@ -210,6 +253,8 @@ class AuthenticateRequest(ProviderPageBase): login.assertion.subject.nameId.content = nameid else: self.trans.wipe() + self.error('Authentication succeeded but it was not ' + + 'provided by NameID %s' % nameidfmt) raise AuthenticationError("Unavailable Name ID type", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) @@ -226,6 +271,8 @@ class AuthenticateRequest(ProviderPageBase): 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() mappedattrs, _ = policy.map_attributes(userattrs) @@ -271,23 +318,17 @@ 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() + 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, - lasso_session) - us.save_provider_data('saml2', saml_sessions) + user.name, + lasso_session.dump(), + None, + provider.logout_mechs) def saml2error(self, login, code, message): status = lasso.Samlp2Status() @@ -303,7 +344,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, @@ -313,8 +354,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)