diff options
| author | Emmanuel Raviart <eraviart@entrouvert.com> | 2004-08-11 23:02:55 +0000 |
|---|---|---|
| committer | Emmanuel Raviart <eraviart@entrouvert.com> | 2004-08-11 23:02:55 +0000 |
| commit | 2c3b5a50c5c1e6d84ccc74eb55b355af13fd8aa8 (patch) | |
| tree | 0ecaf6efca3920c6adde149b58ad53d64339132b /python | |
| parent | 02677987a48a67d99f5aacd915119a658dacd42a (diff) | |
| download | lasso-2c3b5a50c5c1e6d84ccc74eb55b355af13fd8aa8.tar.gz lasso-2c3b5a50c5c1e6d84ccc74eb55b355af13fd8aa8.tar.xz lasso-2c3b5a50c5c1e6d84ccc74eb55b355af13fd8aa8.zip | |
Create a new test Proxy server (a server between a SP and an IDP, which acts
as an IDP for the SP and as a SP for the IDP): login works.
Diffstat (limited to 'python')
| -rw-r--r-- | python/tests/IdentityProvider.py | 66 | ||||
| -rw-r--r-- | python/tests/LibertyEnabledProxy.py | 49 | ||||
| -rw-r--r-- | python/tests/ServiceProvider.py | 113 | ||||
| -rw-r--r-- | python/tests/abstractweb.py | 64 | ||||
| -rw-r--r-- | python/tests/http.py | 25 | ||||
| -rw-r--r-- | python/tests/liberty.py | 7 | ||||
| -rw-r--r-- | python/tests/libertysimulator.py | 7 | ||||
| -rw-r--r-- | python/tests/login_tests.py | 19 | ||||
| -rwxr-xr-x | python/tests/sample-idp.py | 4 | ||||
| -rwxr-xr-x | python/tests/sample-lep.py | 152 | ||||
| -rwxr-xr-x | python/tests/sample-sp-lep.py | 147 | ||||
| -rw-r--r-- | python/tests/web.py | 23 | ||||
| -rw-r--r-- | python/tests/websimulator.py | 47 |
13 files changed, 586 insertions, 137 deletions
diff --git a/python/tests/IdentityProvider.py b/python/tests/IdentityProvider.py index 705899b2..33431761 100644 --- a/python/tests/IdentityProvider.py +++ b/python/tests/IdentityProvider.py @@ -34,6 +34,23 @@ class IdentityProviderMixin(Provider.ProviderMixin): Provider.ProviderMixin.__init__(self) self.soapResponseMsgs = {} + def login_done(self, handler, userAuthenticated, authenticationMethod): + # Reconstruct Lasso login from dump. + lassoServer = self.getLassoServer() + session = handler.session + failUnless(session) + failUnless(session.lassoLoginDump) + login = lasso.Login.new_from_dump(lassoServer, session.lassoLoginDump) + del session.lassoLoginDump + # Set identity & session in login, because session.lassoLoginDump doesn't contain them. + if session.lassoSessionDump is not None: + login.set_session_from_dump(session.lassoSessionDump) + user = handler.user + if user is not None and user.lassoIdentityDump is not None: + login.set_identity_from_dump(user.lassoIdentityDump) + + return self.singleSignOn_done(handler, login, userAuthenticated, authenticationMethod) + def singleSignOn(self, handler): lassoServer = self.getLassoServer() if handler.httpRequest.method == 'GET': @@ -50,10 +67,17 @@ class IdentityProviderMixin(Provider.ProviderMixin): if not login.must_authenticate(): userAuthenticated = user is not None authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME - return self.singleSignOn_part2( + return self.singleSignOn_done( handler, login, userAuthenticated, authenticationMethod) - return self.singleSignOn_authenticate(handler, login) + # The authentication may need to change page (needed for a HTML form, for example). + # => Save Lasso login as a dump in session, so that we retrieve it once the user is + # authenticated. + if session is None: + session = handler.createSession() + session.publishToken = True + session.lassoLoginDump = login.dump() + return self.callHttpFunction(self.login, handler) elif handler.httpRequest.method == 'POST' \ and handler.httpRequest.headers.get('Content-Type', None) == 'text/xml': @@ -66,8 +90,9 @@ class IdentityProviderMixin(Provider.ProviderMixin): if user is not None and user.lassoIdentityDump is not None: lecp.set_identity_from_dump(user.lassoIdentityDump) lecp.init_from_authn_request_msg(handler.httpRequest.body, lasso.httpMethodSoap) - # FIXME: lecp.must_authenticate() should always return False. - # The other solution is that we are able to not call lecp.must_authenticate() + # FIXME: lecp.must_authenticate() should always return False. Because we are in SOAP. + # And we can't do a HTTP redirect in SOAP. + # The other solution is that we shall not call lecp.must_authenticate(). # failIf(lecp.must_authenticate()) userAuthenticated = user is not None authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME @@ -95,38 +120,7 @@ class IdentityProviderMixin(Provider.ProviderMixin): 400, 'Bad Request: Method %s not handled by singleSignOn' % handler.httpRequest.method) - def singleSignOn_authenticate(self, handler, login): - if not self.instantAuthentication: - # The authentication needs to change page (needed for a HTML form, for example). - # Save Lasso login as a dump in session. - session = handler.session - if session is None: - session = handler.createSession() - session.publishToken = True - session.lassoLoginDump = login.dump() - login = None - return self.authenticate(handler, self.singleSignOn_authenticate_part2, login) - - def singleSignOn_authenticate_part2(self, handler, userAuthenticated, authenticationMethod, - login = None): - if login is None: - # The authentication needed to change page (needed for a HTML form, for example). - # Reconstruct Lasso login from dump. - lassoServer = self.getLassoServer() - session = handler.session - failUnless(session) - failUnless(session.lassoLoginDump) - login = lasso.Login.new_from_dump(lassoServer, session.lassoLoginDump) - del session.lassoLoginDump - # Set identity & session in login, because session.lassoLoginDump doesn't contain them. - if session.lassoSessionDump is not None: - login.set_session_from_dump(session.lassoSessionDump) - user = handler.user - if user is not None and user.lassoIdentityDump is not None: - login.set_identity_from_dump(user.lassoIdentityDump) - return self.singleSignOn_part2(handler, login, userAuthenticated, authenticationMethod) - - def singleSignOn_part2(self, handler, login, userAuthenticated, authenticationMethod): + def singleSignOn_done(self, handler, login, userAuthenticated, authenticationMethod): failUnlessEqual(login.protocolProfile, lasso.loginProtocolProfileBrwsArt) # FIXME login.build_artifact_msg( userAuthenticated, authenticationMethod, diff --git a/python/tests/LibertyEnabledProxy.py b/python/tests/LibertyEnabledProxy.py new file mode 100644 index 00000000..92582a33 --- /dev/null +++ b/python/tests/LibertyEnabledProxy.py @@ -0,0 +1,49 @@ +# -*- coding: UTF-8 -*- + + +# Lasso Simulator +# By: Emmanuel Raviart <eraviart@entrouvert.com> +# +# Copyright (C) 2004 Entr'ouvert +# http://lasso.entrouvert.org +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import lasso + +from IdentityProvider import IdentityProviderMixin +from ServiceProvider import ServiceProviderMixin + + +class LibertyEnabledProxyMixin(IdentityProviderMixin, ServiceProviderMixin): + def __init__(self): + ServiceProviderMixin.__init__(self) + IdentityProviderMixin.__init__(self) + + def login(self, handler): + # Before, this proxy was considered as an identity provider. Now it is a service provider. + return ServiceProviderMixin.login(self, handler) + + def login_failed(self, handler): + # Before, this proxy was considered as a service provider. Now it acts again as a service + # provider. + return self.login_done(handler, False, None) + + def assertionConsumer_done(self, handler): + # Before, this proxy was considered as a service provider. Now it acts again as a service + # provider. + # FIXME: We should retrieve authentication method from session.lassoSessionDump. + return self.login_done(handler, True, lasso.samlAuthenticationMethodPassword) diff --git a/python/tests/ServiceProvider.py b/python/tests/ServiceProvider.py index d5d654a0..7baea90a 100644 --- a/python/tests/ServiceProvider.py +++ b/python/tests/ServiceProvider.py @@ -36,6 +36,7 @@ class ServiceProviderMixin(Provider.ProviderMixin): login = lasso.Login.new(lassoServer) if handler.httpRequest.method == 'GET': + relayState = handler.httpRequest.getQueryField('RelayState', None) login.init_request(handler.httpRequest.query, lasso.httpMethodRedirect) login.build_request_msg() @@ -57,6 +58,7 @@ class ServiceProviderMixin(Provider.ProviderMixin): else: raise elif handler.httpRequest.method == 'POST': + relayState = handler.httpRequest.getFormField('RelayState', None) authnResponseMsg = handler.httpRequest.getFormField('LARES', None) failUnless(authnResponseMsg) # FIXME: Should we do an init before process_authn_response_msg? @@ -113,14 +115,30 @@ class ServiceProviderMixin(Provider.ProviderMixin): session = handler.createSession() session.publishToken = True if user is None: + # The user has been successfully authenticated on identity provider, but he has no + # account on this service provider or his account is not federated yet and he is not + # logged. # A real service provider would ask user to login locally to create a federation. Or it # would ask user informations to create a local account. Or it would automatically # create a new account... if self.createNewAccountWhenNewFederationForUnknownUser: user = handler.createUser() else: - return self.assertionConsumer_newFederationForUnknownUser( - handler, nameIdentifier, lassoSessionDump, lassoIdentityDump) + # Save some informations in session for a short time (until user is logged). + # These informations can't be stored as fields in URL query, because they are too + # large. + session.lassoIdentityDump = lassoIdentityDump + session.lassoSessionDump = lassoSessionDump + session.nameIdentifier = nameIdentifier + session.relayState = relayState + + # We do a redirect now for two reasons: + # - We don't want the user to be able to reload assertionConsumer page (because the + # artifact has been removed from identity-provider). + # - For HTTP authentication, we don't want to emit a 401 Unauthorized that would + # force the Principal to reload the assertionConsumer page. + # FIXME: Add the session token to redirect URL. + return handler.respondRedirectTemporarily('/login_local') session.userId = user.uniqueId user.sessionToken = session.token @@ -136,56 +154,17 @@ class ServiceProviderMixin(Provider.ProviderMixin): # We do a redirect now because we don't want the user to be able to reload # assertionConsumer page (because the artifact has been removed from identity-provider). # FIXME: Add the session token to redirect URL. - return handler.respondRedirectTemporarily('/assertionConsumer_success') - - def assertionConsumer_newFederationForUnknownUser( - self, handler, nameIdentifier, lassoSessionDump, lassoIdentityDump): - # Called whe the user has been successfully authenticated on identity provider, but he has - # no account on this service provider or is account is not federated yet and he is not - # logged. - # Depending of the policy of the service provider, the user account can be created - # immediately, or the user can be asked to provide informations to create a new account. - # He also can be asked to authenticate locally (for the last time :-) in order for the - # service-provider to create the federation. - - # Save Lasso login as a dump in session. - session = handler.session - session.nameIdentifier = nameIdentifier - session.lassoSessionDump = lassoSessionDump - session.lassoIdentityDump = lassoIdentityDump - nameIdentifier = lassoSessionDump = lassoIdentityDump = None - - # We do a redirect now for two reasons: - # - We don't want the user to be able to reload assertionConsumer page (because the - # artifact has been removed from identity-provider). - # - For HTTP authentication, we don't want to emit a 401 Unauthorized that would force the - # Principal to reload assertionConsumer page. - # FIXME: Add the session token to redirect URL. - return handler.respondRedirectTemporarily( - '/assertionConsumer_newFederationForUnknownUser_part2') - - def assertionConsumer_newFederationForUnknownUser_part2(self, handler): - return self.authenticate(handler, self.assertionConsumer_newFederationForUnknownUser_part3) - - def assertionConsumer_newFederationForUnknownUser_part3( - self, handler, userAuthenticated, authenticationMethod): - if not userAuthenticated: - return handler.respond(401, 'Access Unauthorized: User has no account.') - - # User has been authenticated => Create federation. - session = handler.session - nameIdentifier = session.nameIdentifier - del session.nameIdentifier - user = handler.user - user.lassoIdentityDump = session.lassoIdentityDump - del session.lassoIdentityDump - self.userIdsByNameIdentifier[nameIdentifier] = user.uniqueId - self.sessionTokensByNameIdentifier[nameIdentifier] = session.token - return self.assertionConsumer_success(handler) - - def assertionConsumer_success(self, handler): - return handler.respond(200, headers = {'Content-Type': 'text/plain'}, - body = 'Liberty authentication succeeded') + redirectUrl = '/assertionConsumer_done' + if relayState: + redirectUrl = '%s?RelayState=%s' % (redirectUrl, relayState) + return handler.respondRedirectTemporarily(redirectUrl) + + def assertionConsumer_done(self, handler): + # A real service provider could use the string relayState for any purpose. + relayState = handler.httpRequest.getQueryField('RelayState', None) + return handler.respond( + 200, headers = {'Content-Type': 'text/plain'}, + body = 'Liberty authentication succeeded\nRelayState = %s' % relayState) def login(self, handler): libertyEnabled = handler.httpRequest.headers.get('Liberty-Enabled', None) @@ -206,6 +185,7 @@ class ServiceProviderMixin(Provider.ProviderMixin): forceAuthn = handler.httpRequest.getQueryBoolean('forceAuthn', False) isPassive = handler.httpRequest.getQueryBoolean('isPassive', False) + relayState = handler.httpRequest.getQueryField('RelayState', None) lassoServer = self.getLassoServer() if useLecp: lecp = lasso.Lecp.new(lassoServer) @@ -222,8 +202,8 @@ class ServiceProviderMixin(Provider.ProviderMixin): lecp.request.set_isPassive(isPassive) lecp.request.set_nameIDPolicy(lasso.libNameIDPolicyTypeFederated) lecp.request.set_consent(lasso.libConsentObtained) - relayState = 'fake' - lecp.request.set_relayState(relayState) + if relayState: + lecp.request.set_relayState(relayState) lecp.build_authn_request_envelope_msg() authnRequestEnvelopeMsg = lecp.msg_body @@ -251,13 +231,34 @@ class ServiceProviderMixin(Provider.ProviderMixin): login.request.set_isPassive(isPassive) login.request.set_nameIDPolicy(lasso.libNameIDPolicyTypeFederated) login.request.set_consent(lasso.libConsentObtained) - relayState = 'fake' - login.request.set_relayState(relayState) + if relayState: + login.request.set_relayState(relayState) login.build_authn_request_msg(self.idpSite.providerId) authnRequestUrl = login.msg_url failUnless(authnRequestUrl) return handler.respondRedirectTemporarily(authnRequestUrl) + def login_done(self, handler, userAuthenticated, authenticationMethod): + # Remove informations that are no more needed in session. + session = handler.session + lassoIdentityDump = session.lassoIdentityDump + del session.lassoIdentityDump + nameIdentifier = session.nameIdentifier + del session.nameIdentifier + relayState = session.relayState + del session.relayState + + if not userAuthenticated: + return self.login_failed(handler) + + # User has been authenticated => Create federation. + user = handler.user + user.lassoIdentityDump = lassoIdentityDump + self.userIdsByNameIdentifier[nameIdentifier] = user.uniqueId + self.sessionTokensByNameIdentifier[nameIdentifier] = session.token + # Note: The uppercase for RelayState below is not a bug. + return self.callHttpFunction(self.assertionConsumer_done, handler, RelayState = relayState) + def logoutUsingSoap(self, handler): session = handler.session if session is None: diff --git a/python/tests/abstractweb.py b/python/tests/abstractweb.py index 2f189c94..94fb644d 100644 --- a/python/tests/abstractweb.py +++ b/python/tests/abstractweb.py @@ -30,11 +30,11 @@ class HttpRequestMixin: body = None headers = None method = None # 'GET' or 'POST' or 'PUT' or... - url = None path = None pathAndQuery = None query = None scheme = None # 'http' or 'https' + url = None def getFormField(self, name, default = None): raise NotImplementedError @@ -66,6 +66,33 @@ class HttpRequestMixin: return False +class FunctionHttpRequest(HttpRequestMixin, object): + method = 'GET' + previousHttpRequest = None + queryFields = None + + def __init__(self, previousHttpRequest, **queryFields): + self.previousHttpRequest = previousHttpRequest + self.queryFields = queryFields + + def getFormField(self, name, default = None): + return default + + def getHeaders(self): + return self.previousHttpRequest.headers + + def getQueryField(self, name, default = None): + return self.queryFields.get(name, default) + + def hasFormField(self, name): + return False + + def hasQueryField(self, name): + return name in self.queryFields + + headers = property(getHeaders) + + class HttpResponseMixin: body = None defaultStatusMessages = { @@ -200,10 +227,10 @@ class WebSessionMixin(WebClientMixin): class WebSiteMixin: + FunctionHttpRequest = FunctionHttpRequest # Class httpResponseHeaders = { 'Server': 'Lasso Simulator Web Server', } - instantAuthentication = False lastSessionToken = 0 lastUserId = 0 users = None @@ -215,11 +242,6 @@ class WebSiteMixin: self.users = {} self.sessions = {} - def authenticate(self, handler, callback, *arguments, **keywordArguments): - # The arguments & keywordArguments should be given back to callback only for - # instant authentication. - raise NotImplementedError - def authenticateX509User(self, clientCertificate): # We should check certificate (for example clientCertificate.get_serial_number() # and return the user if one matches, or None otherwise. @@ -229,6 +251,15 @@ class WebSiteMixin: # We should check login & password and return the user if one matches or None otherwise. return None + def callHttpFunction(self, function, httpRequestHandler, **queryFields): + httpRequestHandler.httpRequest = self.FunctionHttpRequest( + httpRequestHandler.httpRequest, **queryFields) + try: + result = function(httpRequestHandler) + finally: + httpRequestHandler.httpRequest = httpRequestHandler.httpRequest.previousHttpRequest + return result + def handleHttpRequestHandler(self, httpRequestHandler): methodName = httpRequestHandler.httpRequest.path.replace('/', '') try: @@ -238,6 +269,25 @@ class WebSiteMixin: 404, 'Path "%s" Not Found.' % httpRequestHandler.httpRequest.path) return method(httpRequestHandler) + def login(self, handler): + # On most site (except Liberty service providers), authentication is done locally. + return self.callHttpFunction(self.login_local, handler) + + def login_done(self, handler, userAuthenticated, authenticationMethod): + if not userAuthenticated: + return self.login_failed(handler) + return handler.respond( + 200, headers = {'Content-Type': 'text/plain'}, + body = 'Login terminated:\n userAuthenticated = %s\n authenticationMethod = %s' % ( + userAuthenticated, authenticationMethod)) + + def login_failed(self, handler): + return handler.respond(401, 'Access Unauthorized: User has no account.') + + def login_local(self, handler): + # Note: Once local login is done, the HTTP function login_done must be called. + raise NotImplementedError + def newSession(self): self.lastSessionToken += 1 sessionToken = str(self.lastSessionToken) diff --git a/python/tests/http.py b/python/tests/http.py index 30561e77..9ce7b6ca 100644 --- a/python/tests/http.py +++ b/python/tests/http.py @@ -165,7 +165,9 @@ class HttpRequest(abstractweb.HttpRequestMixin, object): class HttpResponse(abstractweb.HttpResponseMixin, object): def send(self, httpRequestHandler): statusCode = self.statusCode - if statusCode == 404: + if statusCode == 401: + return self.send401(httpRequestHandler) + elif statusCode == 404: return self.send404(httpRequestHandler) assert statusCode in (200, 207) if self.headers is None: @@ -263,6 +265,19 @@ class HttpResponse(abstractweb.HttpResponseMixin, object): break outputFile.write(chunk) + def send401(self, httpRequestHandler): + logger.info(self.statusMessage) + data = '<html><body>%s</body></html>' % self.statusMessage + headers = {} + if httpRequestHandler.useHttpAuthentication == 'not this time': + del httpRequestHandler.useHttpAuthentication + if httpRequestHandler.useHttpAuthentication != True: + httpRequestHandler.useHttpAuthentication = True + elif httpRequestHandler.useHttpAuthentication: + headers["WWW-Authenticate"] = 'Basic realm="%s"' % httpRequestHandler.realm + return httpRequestHandler.send_error( + self.statusCode, self.statusMessage, data, headers, setCookie = True) + def send404(self, httpRequestHandler): logger.info(self.statusMessage) data = '<html><body>%s</body></html>' % self.statusMessage @@ -373,7 +388,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): if self.httpRequest.hasQueryField('login') and not authorization \ and self.useHttpAuthentication: # Ask for HTTP authentication. - return self.outputErrorUnauthorized(httpPath) + return self.outputErrorUnauthorized(self.httpRequest.path) if self.httpRequest.hasQueryField('logout') and authorization: # Since HTTP authentication provides no way to logout, we send a status # Unauthorized to force the user to press the cancel button. But instead of @@ -385,7 +400,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): try: authenticationScheme, credentials = authorization.split(None, 1) except ValueError: - return self.outputErrorUnauthorized(httpPath) + return self.outputErrorUnauthorized(self.httpRequest.path) authenticationScheme = authenticationScheme.lower() if authenticationScheme == 'basic': loginAndPassword = base64.decodestring(credentials) @@ -401,7 +416,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): if user is None: logger.info('Unknown user (login = "%s" / password = "%s")' % ( login, password)) - return self.outputErrorUnauthorized(httpPath) + return self.outputErrorUnauthorized(self.httpRequest.path) else: sessionToken = user.sessionToken if sessionToken is not None: @@ -428,7 +443,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): user.sessionToken = session.token else: logger.info('Unknown authentication scheme = %s' % authenticationScheme) - return self.outputErrorUnauthorized(httpPath) + return self.outputErrorUnauthorized(self.httpRequest.path) # Handle use of cookies, session and user. cookie = None diff --git a/python/tests/liberty.py b/python/tests/liberty.py index 46c21783..02125d39 100644 --- a/python/tests/liberty.py +++ b/python/tests/liberty.py @@ -23,6 +23,7 @@ from LibertyEnabledClientProxy import LibertyEnabledClientProxyMixin +from LibertyEnabledProxy import LibertyEnabledProxyMixin from IdentityProvider import IdentityProviderMixin from ServiceProvider import ServiceProviderMixin from Provider import ProviderMixin @@ -35,6 +36,12 @@ class LibertyEnabledClientProxy(LibertyEnabledClientProxyMixin, web.WebClient): LibertyEnabledClientProxyMixin.__init__(self) +class LibertyEnabledProxy(LibertyEnabledProxyMixin, web.WebSite): + def __init__(self, url): + web.WebSite.__init__(self, url) + LibertyEnabledProxyMixin.__init__(self) + + class Provider(ProviderMixin, web.WebSite): def __init__(self, url): web.WebSite.__init__(self, url) diff --git a/python/tests/libertysimulator.py b/python/tests/libertysimulator.py index 3cc5cb7a..a7751cdf 100644 --- a/python/tests/libertysimulator.py +++ b/python/tests/libertysimulator.py @@ -23,6 +23,7 @@ from LibertyEnabledClientProxy import LibertyEnabledClientProxyMixin +from LibertyEnabledProxy import LibertyEnabledProxyMixin from IdentityProvider import IdentityProviderMixin from ServiceProvider import ServiceProviderMixin from Provider import ProviderMixin @@ -35,6 +36,12 @@ class LibertyEnabledClientProxy(LibertyEnabledClientProxyMixin, websimulator.Web LibertyEnabledClientProxyMixin.__init__(self) +class LibertyEnabledProxy(LibertyEnabledProxyMixin, websimulator.WebSite): + def __init__(self, internet, url): + websimulator.WebSite.__init__(self, internet, url) + LibertyEnabledProxyMixin.__init__(self) + + class Provider(ProviderMixin, websimulator.WebSite): def __init__(self, internet, url): websimulator.WebSite.__init__(self, internet, url) diff --git a/python/tests/login_tests.py b/python/tests/login_tests.py index ac95b648..5f69f903 100644 --- a/python/tests/login_tests.py +++ b/python/tests/login_tests.py @@ -130,6 +130,25 @@ class LoginTestCase(unittest.TestCase): failIf(spSite.sessions) failIf(idpSite.sessions) + def test01_withRelayState(self): + """Service provider initiated login using HTTP redirect and service provider initiated logout using SOAP. Checking RelayState.""" + + internet = Internet() + idpSite = self.generateIdpSite(internet) + spSite = self.generateSpSite(internet) + spSite.idpSite = idpSite + principal = Principal(internet, 'Romain Chantereau') + principal.keyring[idpSite.url] = 'Chantereau' + principal.keyring[spSite.url] = 'Romain' + + httpResponse = principal.sendHttpRequestToSite( + spSite, 'GET', '/login?RelayState=a_sample_relay_state') + failUnlessEqual(httpResponse.statusCode, 200) + httpResponse = principal.sendHttpRequestToSite(spSite, 'GET', '/logoutUsingSoap') + failUnlessEqual(httpResponse.statusCode, 200) + failIf(spSite.sessions) + failIf(idpSite.sessions) + def test02(self): """Service provider initiated login using HTTP redirect and service provider initiated logout using SOAP. Done three times.""" diff --git a/python/tests/sample-idp.py b/python/tests/sample-idp.py index c4398dab..e8afed7a 100755 --- a/python/tests/sample-idp.py +++ b/python/tests/sample-idp.py @@ -110,6 +110,10 @@ def main(): '../../examples/data/sp-metadata.xml', '../../examples/data/sp-public-key.pem', '../../examples/data/ca-crt.pem') + lassoServer.add_provider( + '../../examples/data/lep-metadata.xml', + '../../examples/data/idp-public-key.pem', + '../../examples/data/ca-crt.pem') site.lassoServerDump = lassoServer.dump() failUnless(site.lassoServerDump) lassoServer.destroy() diff --git a/python/tests/sample-lep.py b/python/tests/sample-lep.py new file mode 100755 index 00000000..308f3e7c --- /dev/null +++ b/python/tests/sample-lep.py @@ -0,0 +1,152 @@ +#! /usr/bin/env python +# -*- coding: UTF-8 -*- + + +# Lasso Simulator +# By: Emmanuel Raviart <eraviart@entrouvert.com> +# +# Copyright (C) 2004 Entr'ouvert +# http://lasso.entrouvert.org +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import logging +from optparse import OptionParser +import sys + +if not '..' in sys.path: + sys.path.insert(0, '..') +if not '../.libs' in sys.path: + sys.path.insert(0, '../.libs') + +import lasso + +import assertions +import builtins +import http +import liberty + + +applicationCamelCaseName = 'LassoSimulator' +applicationPublicName = 'Lasso Simulator' +applicationVersion = '(Unreleased CVS Version)' +logger = None + + +class HttpRequestHandlerMixin: + realm = '%s Web Site' % applicationPublicName + server_version = '%s/%s' % (applicationCamelCaseName, applicationVersion) + + def version_string(self): + return '%s %s' % (applicationPublicName, applicationVersion) + + +class HttpRequestHandler(HttpRequestHandlerMixin, http.HttpRequestHandler): + pass + + +class HttpsRequestHandler(HttpRequestHandlerMixin, http.HttpsRequestHandler): + pass + + +def main(): + # Parse command line options. + parser = OptionParser(version = '%%prog %s' % applicationVersion) + parser.add_option( + '-c', '--config', metavar = 'FILE', dest = 'configurationFilePath', + help = 'specify an alternate configuration file', + default = '/etc/lasso-simulator/config.xml') + parser.add_option( + '-d', '--daemon', dest = 'daemonMode', help = 'run main process in background', + action = 'store_true', default = False) + parser.add_option( + '-D', '--debug', dest = 'debugMode', help = 'enable program debugging', + action = 'store_true', default = False) + parser.add_option( + '-l', '--log', metavar = 'FILE', dest = 'logFilePath', help = 'specify log file', + default = '/dev/null') + parser.add_option( + '-L', '--log-level', metavar = 'LEVEL', dest = 'logLevel', + help = 'specify log level (debug, info, warning, error, critical)', default = 'info') + (options, args) = parser.parse_args() + if options.logLevel.upper() not in logging._levelNames: + raise Exception('Unknown log level: "%s"' % options.logLevel) + + # Configure logger. + logger = logging.getLogger() + if options.debugMode and not options.daemonMode: + handler = logging.StreamHandler(sys.stderr) + else: + handler = logging.FileHandler(options.logFilePath) + formatter = logging.Formatter('%(asctime)s %(levelname)-9s %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging._levelNames[options.logLevel.upper()]) + builtins.set('logger', logger) + + site = liberty.LibertyEnabledProxy('https://liberty-enabled-proxy/') + site.providerId = 'https://liberty-enabled-proxy/metadata' + site.idpSite = liberty.IdentityProvider('https://identity-provider/') + site.idpSite.providerId = 'https://identity-provider/metadata' + + lassoServer = lasso.Server.new( + '../../examples/data/lep-metadata.xml', + None, # '../../examples/data/idp-public-key.pem' is no more used. + '../../examples/data/idp-private-key.pem', + '../../examples/data/idp-crt.pem', + lasso.signatureMethodRsaSha1) + lassoServer.add_provider( + '../../examples/data/idp-metadata.xml', + '../../examples/data/idp-public-key.pem', + '../../examples/data/ca-crt.pem') + lassoServer.add_provider( + '../../examples/data/sp-lep-metadata.xml', + '../../examples/data/sp-public-key.pem', + '../../examples/data/ca-crt.pem') + site.lassoServerDump = lassoServer.dump() + failUnless(site.lassoServerDump) + lassoServer.destroy() + + site.certificateAbsolutePath = '../../examples/data/idp-ssl-crt.pem' + site.privateKeyAbsolutePath = '../../examples/data/idp-ssl-private-key.pem' + site.peerCaCertificateAbsolutePath = '../../examples/data/ca-ssl-crt.pem' + + site.newUser('rc') + site.newUser('nc') + # site.newUser('vf') Valery Febvre has no account on liberty-enabled proxy. + site.newUser('cn') + site.newUser('fp') + + HttpRequestHandlerMixin.site = site # Directly a site, not a server => no virtual host. +## httpServer = http.HttpServer(('127.0.0.4', 80), HttpRequestHandler) +## logger.info('Serving HTTP on %s port %s...' % httpServer.socket.getsockname()) + httpServer = http.HttpsServer( + ('127.0.0.4', 443), + HttpsRequestHandler, + '../../examples/data/idp-ssl-private-key.pem', # Server private key + '../../examples/data/idp-ssl-crt.pem', # Server certificate + '../../examples/data/ca-ssl-crt.pem', # Clients certification authority certificate + None, # sslCertificateChainFile see mod_ssl, ssl_engine_init.c, line 852 + None, # sslVerifyClient http://www.modssl.org/docs/2.1/ssl_reference.html#ToC13 + ) + logger.info('Serving HTTPS on %s port %s...' % httpServer.socket.getsockname()) + try: + httpServer.serve_forever() + except KeyboardInterrupt: + pass + +if __name__ == '__main__': + main() diff --git a/python/tests/sample-sp-lep.py b/python/tests/sample-sp-lep.py new file mode 100755 index 00000000..d5196037 --- /dev/null +++ b/python/tests/sample-sp-lep.py @@ -0,0 +1,147 @@ +#! /usr/bin/env python +# -*- coding: UTF-8 -*- + + +# Lasso Simulator +# By: Emmanuel Raviart <eraviart@entrouvert.com> +# +# Copyright (C) 2004 Entr'ouvert +# http://lasso.entrouvert.org +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +import logging +from optparse import OptionParser +import sys + +if not '..' in sys.path: + sys.path.insert(0, '..') +if not '../.libs' in sys.path: + sys.path.insert(0, '../.libs') + +import lasso + +import assertions +import builtins +import http +import liberty + +applicationCamelCaseName = 'LassoSimulator' +applicationPublicName = 'Lasso Simulator' +applicationVersion = '(Unreleased CVS Version)' +logger = None + + +class HttpRequestHandlerMixin: + realm = '%s Web Site' % applicationPublicName + server_version = '%s/%s' % (applicationCamelCaseName, applicationVersion) + + def version_string(self): + return '%s %s' % (applicationPublicName, applicationVersion) + + +class HttpRequestHandler(HttpRequestHandlerMixin, http.HttpRequestHandler): + pass + + +class HttpsRequestHandler(HttpRequestHandlerMixin, http.HttpsRequestHandler): + pass + + +def main(): + # Parse command line options. + parser = OptionParser(version = '%%prog %s' % applicationVersion) + parser.add_option( + '-c', '--config', metavar = 'FILE', dest = 'configurationFilePath', + help = 'specify an alternate configuration file', + default = '/etc/lasso-simulator/config.xml') + parser.add_option( + '-d', '--daemon', dest = 'daemonMode', help = 'run main process in background', + action = 'store_true', default = False) + parser.add_option( + '-D', '--debug', dest = 'debugMode', help = 'enable program debugging', + action = 'store_true', default = False) + parser.add_option( + '-l', '--log', metavar = 'FILE', dest = 'logFilePath', help = 'specify log file', + default = '/dev/null') + parser.add_option( + '-L', '--log-level', metavar = 'LEVEL', dest = 'logLevel', + help = 'specify log level (debug, info, warning, error, critical)', default = 'info') + (options, args) = parser.parse_args() + if options.logLevel.upper() not in logging._levelNames: + raise Exception('Unknown log level: "%s"' % options.logLevel) + + # Configure logger. + logger = logging.getLogger() + if options.debugMode and not options.daemonMode: + handler = logging.StreamHandler(sys.stderr) + else: + handler = logging.FileHandler(options.logFilePath) + formatter = logging.Formatter('%(asctime)s %(levelname)-9s %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging._levelNames[options.logLevel.upper()]) + builtins.set('logger', logger) + + site = liberty.ServiceProvider('https://service-provider-lep/') + site.providerId = 'https://service-provider-lep/metadata' + site.idpSite = liberty.IdentityProvider('https://liberty-enabled-proxy/') + site.idpSite.providerId = 'https://liberty-enabled-proxy/metadata' + + lassoServer = lasso.Server.new( + '../../examples/data/sp-lep-metadata.xml', + None, # '../../examples/data/sp-public-key.pem' is no more used. + '../../examples/data/sp-private-key.pem', + '../../examples/data/sp-crt.pem', + lasso.signatureMethodRsaSha1) + lassoServer.add_provider( + '../../examples/data/lep-metadata.xml', + '../../examples/data/idp-public-key.pem', + '../../examples/data/ca-crt.pem') + site.lassoServerDump = lassoServer.dump() + failUnless(site.lassoServerDump) + lassoServer.destroy() + + site.certificateAbsolutePath = '../../examples/data/sp-ssl-crt.pem' + site.privateKeyAbsolutePath = '../../examples/data/sp-ssl-private-key.pem' + site.peerCaCertificateAbsolutePath = '../../examples/data/ca-ssl-crt.pem' + + site.newUser('Nicolas') + site.newUser('Romain') + site.newUser('Valery') + # Christophe Nowicki has no account on service provider. + site.newUser('Frederic') + + HttpRequestHandlerMixin.site = site # Directly a site, not a server => no virtual host. +## httpServer = http.HttpServer(('127.0.0.5', 80), HttpRequestHandler) +## logger.info('Serving HTTP on %s port %s...' % httpServer.socket.getsockname()) + httpServer = http.HttpsServer( + ('127.0.0.5', 443), + HttpsRequestHandler, + '../../examples/data/sp-ssl-private-key.pem', # Server private key + '../../examples/data/sp-ssl-crt.pem', # Server certificate + '../../examples/data/ca-ssl-crt.pem', # Clients certification authority certificate + None, # sslCertificateChainFile see mod_ssl, ssl_engine_init.c, line 852 + None, # sslVerifyClient http://www.modssl.org/docs/2.1/ssl_reference.html#ToC13 + ) + logger.info('Serving HTTPS on %s port %s...' % httpServer.socket.getsockname()) + try: + httpServer.serve_forever() + except KeyboardInterrupt: + pass + +if __name__ == '__main__': + main() diff --git a/python/tests/web.py b/python/tests/web.py index d2db8ed4..55011a28 100644 --- a/python/tests/web.py +++ b/python/tests/web.py @@ -114,7 +114,6 @@ class WebUser(abstractweb.WebUserMixin, object): class WebSite(abstractweb.WebSiteMixin, WebClient): - instantAuthentication = True # Authentication doesn't use a HTML form. url = None # The main URL of web site WebSession = WebSession WebUser = WebUser @@ -124,7 +123,12 @@ class WebSite(abstractweb.WebSiteMixin, WebClient): abstractweb.WebSiteMixin.__init__(self) self.url = url - def authenticate(self, handler, callback, *arguments, **keywordArguments): + def authenticateLoginPasswordUser(self, login, password): + # We should check login & password and return the user if one matches or None otherwise. + # FIXME: Check password also. + return self.users.get(login) + + def login_local(self, handler): user = handler.user if user is None: failUnless(handler.useHttpAuthentication) @@ -133,8 +137,8 @@ class WebSite(abstractweb.WebSiteMixin, WebClient): # The user is already authenticated using HTTP authentication. userAuthenticated = True - import lasso - authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME + # authenticationMethod = lasso.samlAuthenticationMethodPassword + authenticationMethod = 'urn:oasis:names:tc:SAML:1.0:am:password' if userAuthenticated: session = handler.session if session is None: @@ -147,10 +151,9 @@ class WebSite(abstractweb.WebSiteMixin, WebClient): user = handler.createUser() session.userId = user.uniqueId user.sessionToken = session.token - return callback(handler, userAuthenticated, authenticationMethod, *arguments, - **keywordArguments) + return self.login_done(handler, userAuthenticated, authenticationMethod) - def authenticateLoginPasswordUser(self, login, password): - # We should check login & password and return the user if one matches or None otherwise. - # FIXME: Check password also. - return self.users.get(login) + def login_failed(self, handler): + if handler.useHttpAuthentication: + handler.useHttpAuthentication = 'not this time' + return handler.respond(401, 'Access Unauthorized: User has no account.') diff --git a/python/tests/websimulator.py b/python/tests/websimulator.py index 1a07e8b5..2d6738fa 100644 --- a/python/tests/websimulator.py +++ b/python/tests/websimulator.py @@ -27,11 +27,7 @@ import abstractweb class HttpRequest(abstractweb.HttpRequestMixin, object): client = None # Principal or web site sending the request. - body = None form = None - headers = None - method = None # 'GET' or 'POST' or 'PUT' or... - url = None def __init__(self, client, method, url, headers = None, body = None, form = None): self.client = client @@ -85,6 +81,13 @@ class HttpRequest(abstractweb.HttpRequestMixin, object): scheme = property(getScheme) +class FunctionHttpRequest(abstractweb.FunctionHttpRequest): + def getClient(self): + return self.previousHttpRequest.client + + client = property(getClient) + + class HttpResponse(abstractweb.HttpResponseMixin, object): def send(self, httpRequestHandler): return self @@ -199,7 +202,7 @@ class WebUser(abstractweb.WebUserMixin, object): class WebSite(abstractweb.WebSiteMixin, WebClient): """Simulation of a web site""" - instantAuthentication = True # Authentication doesn't use a HTML form. + FunctionHttpRequest = FunctionHttpRequest # Class url = None # The main URL of web site WebSession = WebSession WebUser = WebUser @@ -210,24 +213,6 @@ class WebSite(abstractweb.WebSiteMixin, WebClient): self.url = url self.internet.addWebSite(self) - def authenticate(self, handler, callback, *arguments, **keywordArguments): - userId = handler.httpRequest.client.keyring.get(self.url, None) - userAuthenticated = userId in self.users - - import lasso - authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME - if userAuthenticated: - session = handler.session - if session is None: - session = handler.createSession() - user = handler.user - if user is None: - user = handler.createUser() - session.userId = user.uniqueId - user.sessionToken = session.token - return callback(handler, userAuthenticated, authenticationMethod, *arguments, - **keywordArguments) - def handleHttpRequest(self, httpRequest): httpRequestHandler = HttpRequestHandler(self, httpRequest) @@ -241,3 +226,19 @@ class WebSite(abstractweb.WebSiteMixin, WebClient): httpRequestHandler.user = self.users.get(session.userId, None) return self.handleHttpRequestHandler(httpRequestHandler) + + def login_local(self, handler): + userId = handler.httpRequest.client.keyring.get(self.url, None) + userAuthenticated = userId in self.users + # authenticationMethod = lasso.samlAuthenticationMethodPassword + authenticationMethod = 'urn:oasis:names:tc:SAML:1.0:am:password' + if userAuthenticated: + session = handler.session + if session is None: + session = handler.createSession() + user = handler.user + if user is None: + user = handler.createUser() + session.userId = user.uniqueId + user.sessionToken = session.token + return self.login_done(handler, userAuthenticated, authenticationMethod) |
