X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Fproviders%2Fsaml2%2Fauth.py;h=e35ff1398313e65120c85d4ebca944b168de4366;hp=e73a692f426e3c57ba315fc86eb7779e019c82b3;hb=e0895efb26de64a28de7b9219f524b715c396b2b;hpb=953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5 diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py index e73a692..e35ff13 100755 --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -17,21 +17,36 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from ipsilon.providers.common import ProviderPageBase +from ipsilon.providers.common import ProviderPageBase, ProviderException +from ipsilon.providers.saml2.provider import ServiceProvider +from ipsilon.providers.saml2.provider import InvalidProviderId +from ipsilon.providers.saml2.provider import NameIdNotAllowed from ipsilon.util.user import UserSession import cherrypy import datetime import lasso -class InvalidRequest(Exception): +class AuthenticationError(ProviderException): + + def __init__(self, message, code): + super(AuthenticationError, self).__init__(message) + self.code = code + self._debug('%s [%s]' % (message, code)) + + +class InvalidRequest(ProviderException): def __init__(self, message): super(InvalidRequest, self).__init__(message) - self.message = message + self._debug(message) + - def __str__(self): - return repr(self.message) +class UnknownProvider(ProviderException): + + def __init__(self, message): + super(UnknownProvider, self).__init__(message) + self._debug(message) class AuthenticateRequest(ProviderPageBase): @@ -43,13 +58,15 @@ class AuthenticateRequest(ProviderPageBase): self.stage = self.STAGE_INIT def auth(self, login): - self.saml2checks(login) - self.saml2assertion(login) + try: + self.saml2checks(login) + except AuthenticationError, e: + self.saml2error(login, e.code, e.message) return self.reply(login) def _parse_request(self, message): - login = lasso.Login(self.cfg.idp) + login = self.cfg.idp.get_login_handler() try: login.processAuthnRequestMsg(message) @@ -69,9 +86,11 @@ class AuthenticateRequest(ProviderPageBase): except (lasso.ServerProviderNotFoundError, lasso.ProfileUnknownProviderError), e: - msg = 'Invalid Service Provider (%r [%r])' % (e, message) - # TODO: return to SP anyway ? - raise InvalidRequest(msg) + msg = 'Invalid SP [%s] (%r [%r])' % (login.remoteProviderId, + e, message) + raise UnknownProvider(msg) + + self._debug('SP %s requested authentication' % login.remoteProviderId) return login @@ -86,6 +105,9 @@ class AuthenticateRequest(ProviderPageBase): 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) @@ -104,7 +126,8 @@ class AuthenticateRequest(ProviderPageBase): '%s/saml2/SSO/Continue' % self.basepath) raise cherrypy.HTTPRedirect('%s/login' % self.basepath) else: - raise cherrypy.HTTPError(401) + raise AuthenticationError( + "Unknown user", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) self._audit("Logged in user: %s [%s]" % (user.name, user.fullname)) @@ -113,20 +136,29 @@ class AuthenticateRequest(ProviderPageBase): # record it consent = True - # TODO: check Name-ID Policy + # TODO: check destination + + try: + provider = ServiceProvider(self.cfg, login.remoteProviderId) + nameidfmt = provider.get_valid_nameid(login.request.nameIdPolicy) + except NameIdNotAllowed, e: + raise AuthenticationError( + str(e), lasso.SAML2_STATUS_CODE_INVALID_NAME_ID_POLICY) + except InvalidProviderId, e: + raise AuthenticationError( + str(e), lasso.SAML2_STATUS_CODE_AUTHN_FAILED) # TODO: check login.request.forceAuthn login.validateRequestMsg(not user.is_anonymous, consent) - def saml2assertion(self, login): - authtime = datetime.datetime.utcnow() skew = datetime.timedelta(0, 60) authtime_notbefore = authtime - skew authtime_notafter = authtime + skew - user = UserSession().get_user() + us = UserSession() + user = us.get_user() # TODO: get authentication type fnd name format from session # need to save which login manager authenticated and map it to a @@ -139,11 +171,59 @@ class AuthenticateRequest(ProviderPageBase): None, authtime_notbefore.strftime(timeformat), authtime_notafter.strftime(timeformat)) - login.assertion.subject.nameId.format = \ - lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT - login.assertion.subject.nameId.content = user.name - # TODO: add user attributes as policy requires taking from 'user' + nameid = None + if nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT: + # TODO map to something else ? + nameid = provider.normalize_username(user.name) + elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT: + # TODO map to something else ? + nameid = provider.normalize_username(user.name) + elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS: + nameid = us.get_data('user', 'krb_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) + + if nameid: + login.assertion.subject.nameId.format = nameidfmt + login.assertion.subject.nameId.content = nameid + else: + 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] + else: + attrstat = login.assertion.attributeStatement[0] + if not attrstat.attribute: + attrstat.attribute = () + + attributes = us.get_user_attrs() + for key in attributes: + attr = lasso.Saml2Attribute() + attr.name = key + attr.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC + value = str(attributes[key]).encode('utf-8') + node = lasso.MiscTextNode.newWithString(value) + node.textChild = True + attrvalue = lasso.Saml2AttributeValue() + attrvalue.any = [node] + attr.attributeValue = [attrvalue] + attrstat.attribute = attrstat.attribute + (attr,) + + self.debug('Assertion: %s' % login.assertion.dump()) + + def saml2error(self, login, code, message): + status = lasso.Samlp2Status() + status.statusCode = lasso.Samlp2StatusCode() + status.statusCode.value = lasso.SAML2_STATUS_CODE_RESPONDER + status.statusCode.statusCode = lasso.Samlp2StatusCode() + status.statusCode.statusCode.value = code + login.response.status = status def reply(self, login): if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART: