diff options
author | Simo Sorce <simo@redhat.com> | 2014-02-23 18:41:13 -0500 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2014-02-24 21:54:57 -0500 |
commit | 953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5 (patch) | |
tree | 8265f5fe966797619330622b04266a5941a54ba0 /ipsilon/providers/saml2/auth.py | |
parent | eaad8751cf92a29a7ab398c6b20e96d4a53b9d69 (diff) | |
download | ipsilon-953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5.tar.gz ipsilon-953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5.tar.xz ipsilon-953a4e418b1bdcbfddaf52d27a4cba9e9d8062e5.zip |
Initial SAML2 provider
Signed-off-by: Simo Sorce <simo@redhat.com>
Diffstat (limited to 'ipsilon/providers/saml2/auth.py')
-rwxr-xr-x | ipsilon/providers/saml2/auth.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py new file mode 100755 index 0000000..e73a692 --- /dev/null +++ b/ipsilon/providers/saml2/auth.py @@ -0,0 +1,168 @@ +#!/usr/bin/python +# +# Copyright (C) 2014 Simo Sorce <simo@redhat.com> +# +# 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 <http://www.gnu.org/licenses/>. + +from ipsilon.providers.common import ProviderPageBase +from ipsilon.util.user import UserSession +import cherrypy +import datetime +import lasso + + +class InvalidRequest(Exception): + + def __init__(self, message): + super(InvalidRequest, self).__init__(message) + self.message = message + + def __str__(self): + return repr(self.message) + + +class AuthenticateRequest(ProviderPageBase): + + def __init__(self, *args, **kwargs): + super(AuthenticateRequest, self).__init__(*args, **kwargs) + self.STAGE_INIT = 0 + self.STAGE_AUTH = 1 + self.stage = self.STAGE_INIT + + def auth(self, login): + self.saml2checks(login) + self.saml2assertion(login) + return self.reply(login) + + def _parse_request(self, message): + + login = lasso.Login(self.cfg.idp) + + try: + login.processAuthnRequestMsg(message) + except (lasso.ProfileInvalidMsgError, + lasso.ProfileMissingIssuerError), e: + + msg = 'Malformed Request %r [%r]' % (e, message) + raise InvalidRequest(msg) + + except (lasso.ProfileInvalidProtocolprofileError, + lasso.DsError), e: + + msg = 'Invalid SAML Request: %r (%r [%r])' % (login.request, + e, message) + raise InvalidRequest(msg) + + except (lasso.ServerProviderNotFoundError, + lasso.ProfileUnknownProviderError), e: + + msg = 'Invalid Service Provider (%r [%r])' % (e, message) + # TODO: return to SP anyway ? + raise InvalidRequest(msg) + + return login + + def saml2login(self, request): + + if not request: + 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 Exception, e: # pylint: disable=broad-except + self._debug(str(e)) + raise cherrypy.HTTPError(500) + + return login + + def saml2checks(self, login): + + session = UserSession() + user = session.get_user() + if user.is_anonymous: + if self.stage < self.STAGE_AUTH: + session.save_data('saml2', 'stage', self.STAGE_AUTH) + session.save_data('saml2', 'Request', login.dump()) + session.save_data('login', 'Return', + '%s/saml2/SSO/Continue' % self.basepath) + raise cherrypy.HTTPRedirect('%s/login' % self.basepath) + else: + raise cherrypy.HTTPError(401) + + self._audit("Logged in user: %s [%s]" % (user.name, user.fullname)) + + # TODO: check if this is the first time this user access this SP + # If required by user prefs, ask user for consent once and then + # record it + consent = True + + # TODO: check Name-ID Policy + + # 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() + + # TODO: get authentication type fnd name format from session + # need to save which login manager authenticated and map it to a + # saml2 authentication context + authn_context = lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED + + timeformat = '%Y-%m-%dT%H:%M:%SZ' + login.buildAssertion(authn_context, + authtime.strftime(timeformat), + 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' + + def reply(self, login): + if login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_ART: + # TODO + raise cherrypy.HTTPError(501) + elif login.protocolProfile == lasso.LOGIN_PROTOCOL_PROFILE_BRWS_POST: + login.buildAuthnResponseMsg() + self._debug('POSTing back to SP [%s]' % (login.msgUrl)) + context = { + "title": 'Redirecting back to the web application', + "action": login.msgUrl, + "fields": [ + [lasso.SAML2_FIELD_RESPONSE, login.msgBody], + [lasso.SAML2_FIELD_RELAYSTATE, login.msgRelayState], + ], + "submit": 'Return to application', + } + # pylint: disable=star-args + return self._template('saml2/post_response.html', **context) + + else: + raise cherrypy.HTTPError(500) |