diff options
| author | Emmanuel Raviart <eraviart@entrouvert.com> | 2004-08-09 16:19:45 +0000 |
|---|---|---|
| committer | Emmanuel Raviart <eraviart@entrouvert.com> | 2004-08-09 16:19:45 +0000 |
| commit | a0a74c1a170f5e8e2c02bd52e43abfe598433a96 (patch) | |
| tree | ca0de00de00271e46552e44b506965e7126c115b /python/tests | |
| parent | 710b77536cf42d6c43fddd45e0d717704520dfe6 (diff) | |
| download | lasso-a0a74c1a170f5e8e2c02bd52e43abfe598433a96.tar.gz lasso-a0a74c1a170f5e8e2c02bd52e43abfe598433a96.tar.xz lasso-a0a74c1a170f5e8e2c02bd52e43abfe598433a96.zip | |
Updated Python tests. Not finished but Valos want it to debug Lasso.
Diffstat (limited to 'python/tests')
| -rw-r--r-- | python/tests/IdentityProvider.py | 143 | ||||
| -rw-r--r-- | python/tests/LibertyEnabledClient.py | 21 | ||||
| -rw-r--r-- | python/tests/Provider.py | 10 | ||||
| -rw-r--r-- | python/tests/ServiceProvider.py | 139 | ||||
| -rw-r--r-- | python/tests/abstractweb.py | 53 | ||||
| -rw-r--r-- | python/tests/http.py | 600 | ||||
| -rw-r--r-- | python/tests/login_tests.py | 104 | ||||
| -rw-r--r-- | python/tests/websimulator.py | 154 |
8 files changed, 709 insertions, 515 deletions
diff --git a/python/tests/IdentityProvider.py b/python/tests/IdentityProvider.py index 27ca0265..76cd3f0a 100644 --- a/python/tests/IdentityProvider.py +++ b/python/tests/IdentityProvider.py @@ -36,73 +36,74 @@ class IdentityProvider(Provider): self.soapResponseMsgs = {} def singleSignOn(self, handler): - server = self.getServer() + lassoServer = self.getLassoServer() if handler.httpRequest.method == 'GET': # Single sign-on using HTTP redirect. - login = lasso.Login.new(server) - webSession = self.getWebSession(handler.httpRequest.client) - webUser = None - if webSession is not None: - if webSession.sessionDump is not None: - login.set_session_from_dump(webSession.sessionDump) - webUser = self.getWebUserFromWebSession(webSession) - if webUser is not None and webUser.identityDump is not None: - login.set_identity_from_dump(webUser.identityDump) + login = lasso.Login.new(lassoServer) + session = handler.session + user = None + if session is not None: + if session.lassoSessionDump is not None: + login.set_session_from_dump(session.lassoSessionDump) + user = self.getUserFromSession(session) + if user is not None and user.lassoIdentityDump is not None: + login.set_identity_from_dump(user.lassoIdentityDump) login.init_from_authn_request_msg(handler.httpRequest.query, lasso.httpMethodRedirect) if not login.must_authenticate(): - userAuthenticated = webUser is not None + userAuthenticated = user is not None authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME return self.singleSignOn_part2( - handler, login, webSession, webUser, userAuthenticated, authenticationMethod) + handler, login, session, user, userAuthenticated, authenticationMethod) - if webSession is None: - webSession = self.createWebSession(handler.httpRequest.client) - webSession.loginDump = login.dump() + if session is None: + session = self.createSession(handler.httpRequest.client) + session.loginDump = login.dump() # A real identity provider using a HTML form to ask user's login & password would store # idpLoginDump in a session variable and display the HTML login form. - webUserId = handler.httpRequest.client.keyring.get(self.url, None) - userAuthenticated = webUserId in self.webUsers + userId = handler.httpRequest.client.keyring.get(self.url, None) + userAuthenticated = userId in self.users if userAuthenticated: - webSession.webUserId = webUserId + session.userId = userId authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME - server = self.getServer() - webSession = self.getWebSession(handler.httpRequest.client) - loginDump = webSession.loginDump - del webSession.loginDump - login = lasso.Login.new_from_dump(server, loginDump) + lassoServer = self.getLassoServer() + session = handler.session + loginDump = session.loginDump + del session.loginDump + login = lasso.Login.new_from_dump(lassoServer, loginDump) # Set identity & session in login, because loginDump doesn't contain them. - if webSession.sessionDump is not None: - login.set_session_from_dump(webSession.sessionDump) - webUser = self.getWebUserFromWebSession(webSession) - if webUser is not None and webUser.identityDump is not None: - login.set_identity_from_dump(webUser.identityDump) + if session.lassoSessionDump is not None: + login.set_session_from_dump(session.lassoSessionDump) + user = self.getUserFromSession(session) + if user is not None and user.lassoIdentityDump is not None: + login.set_identity_from_dump(user.lassoIdentityDump) return self.singleSignOn_part2( - handler, login, webSession, webUser, userAuthenticated, authenticationMethod) + handler, login, session, user, userAuthenticated, authenticationMethod) elif handler.httpRequest.method == 'POST' \ and handler.httpRequest.headers.get('Content-Type', None) == 'text/xml': # SOAP request => LECP single sign-on. - lecp = lasso.Lecp.new(server) - webSession = self.getWebSession(handler.httpRequest.client) - webUser = None - if webSession is not None: - if webSession.sessionDump is not None: - lecp.set_session_from_dump(webSession.sessionDump) - webUser = self.getWebUserFromWebSession(webSession) - if webUser is not None and webUser.identityDump is not None: - lecp.set_identity_from_dump(webUser.identityDump) + lecp = lasso.Lecp.new(lassoServer) + session = handler.session + user = None + if session is not None: + if session.lassoSessionDump is not None: + lecp.set_session_from_dump(session.lassoSessionDump) + user = self.getUserFromSession(session) + 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() # failIf(lecp.must_authenticate()) - userAuthenticated = webUser is not None + userAuthenticated = user is not None authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME # FIXME: It is very very strange that we don't provide userAuthenticated, # authenticationMethod, reauthenticateOnOrAfter' to lecp before or when build response. - lecp.build_authn_response_envelope_msg() + lecp.build_authn_response_envelope_msg( + userAuthenticated, authenticationMethod, 'FIXME: reauthenticateOnOrAfter') soapResponseMsg = lecp.msg_body failUnless(soapResponseMsg) # FIXME: Lasso should set a lecp.msg_content_type to @@ -123,7 +124,7 @@ class IdentityProvider(Provider): 400, 'Bad Request: Method %s not handled by singleSignOn' % handler.httpRequest.method) - def singleSignOn_part2(self, handler, login, webSession, webUser, userAuthenticated, + def singleSignOn_part2(self, handler, login, session, user, userAuthenticated, authenticationMethod): failUnlessEqual(login.protocolProfile, lasso.loginProtocolProfileBrwsArt) # FIXME login.build_artifact_msg( @@ -131,17 +132,17 @@ class IdentityProvider(Provider): lasso.httpMethodRedirect) if userAuthenticated: if login.is_identity_dirty(): - identityDump = login.get_identity().dump() - failUnless(identityDump) - webUser.identityDump = identityDump + lassoIdentityDump = login.get_identity().dump() + failUnless(lassoIdentityDump) + user.lassoIdentityDump = lassoIdentityDump failUnless(login.is_session_dirty()) - sessionDump = login.get_session().dump() - failUnless(sessionDump) - webSession.sessionDump = sessionDump + lassoSessionDump = login.get_session().dump() + failUnless(lassoSessionDump) + session.lassoSessionDump = lassoSessionDump nameIdentifier = login.nameIdentifier failUnless(nameIdentifier) - self.webUserIdsByNameIdentifier[nameIdentifier] = webUser.uniqueId - self.webSessionIdsByNameIdentifier[nameIdentifier] = webSession.uniqueId + self.userIdsByNameIdentifier[nameIdentifier] = user.uniqueId + self.sessionTokensByNameIdentifier[nameIdentifier] = session.token else: failIf(login.is_identity_dirty()) failIf(login.is_session_dirty()) @@ -158,8 +159,8 @@ class IdentityProvider(Provider): soapRequestMsg = handler.httpRequest.body requestType = lasso.get_request_type_from_soap_msg(soapRequestMsg) if requestType == lasso.requestTypeLogin: - server = self.getServer() - login = lasso.Login.new(server) + lassoServer = self.getLassoServer() + login = lasso.Login.new(lassoServer) login.process_request_msg(soapRequestMsg) artifact = login.assertionArtifact failUnless(artifact) @@ -169,49 +170,49 @@ class IdentityProvider(Provider): return handler.respond( headers = {'Content-Type': 'text/xml'}, body = soapResponseMsg) elif requestType == lasso.requestTypeLogout: - server = self.getServer() - logout = lasso.Logout.new(server, lasso.providerTypeIdp) + lassoServer = self.getLassoServer() + logout = lasso.Logout.new(lassoServer, lasso.providerTypeIdp) logout.process_request_msg(soapRequestMsg, lasso.httpMethodSoap) nameIdentifier = logout.nameIdentifier failUnless(nameIdentifier) # Retrieve session dump and identity dump using name identifier. - webSession = self.getWebSessionFromNameIdentifier(nameIdentifier) - if webSession is None: + session = self.getSessionFromNameIdentifier(nameIdentifier) + if session is None: raise Exception('FIXME: Handle the case when there is no web session') - sessionDump = webSession.sessionDump - if sessionDump is None: + lassoSessionDump = session.lassoSessionDump + if lassoSessionDump is None: raise Exception( 'FIXME: Handle the case when there is no session dump in web session') - logout.set_session_from_dump(sessionDump) - webUser = self.getWebUserFromNameIdentifier(nameIdentifier) - if webUser is None: + logout.set_session_from_dump(lassoSessionDump) + user = self.getUserFromNameIdentifier(nameIdentifier) + if user is None: raise Exception('FIXME: Handle the case when there is no web user') - identityDump = webUser.identityDump - if identityDump is None: + lassoIdentityDump = user.lassoIdentityDump + if lassoIdentityDump is None: raise Exception( 'FIXME: Handle the case when there is no identity dump in web user') - logout.set_identity_from_dump(identityDump) + logout.set_identity_from_dump(lassoIdentityDump) logout.validate_request() failIf(logout.is_identity_dirty()) - identity = logout.get_identity() - failUnless(identity) - identityDump = identity.dump() - failUnless(identityDump) + lassoIdentity = logout.get_identity() + failUnless(lassoIdentity) + lassoIdentityDump = lassoIdentity.dump() + failUnless(lassoIdentityDump) failUnless(logout.is_session_dirty()) # Log the user out. # It is done before logout from other service providers, since we don't want to # accept passive login connections inbetween. - del webSession.sessionDump - del webSession.webUserId + del session.lassoSessionDump + del session.userId # We also delete the session, but it is not mandantory, since the user is logged out # anyway. - del self.webSessions[webSession.uniqueId] + del self.sessions[session.token] nameIdentifier = logout.nameIdentifier failUnless(nameIdentifier) - del self.webSessionIdsByNameIdentifier[nameIdentifier] + del self.sessionTokensByNameIdentifier[nameIdentifier] # Tell each other service provider to logout the user. otherProviderId = logout.get_next_providerID() diff --git a/python/tests/LibertyEnabledClient.py b/python/tests/LibertyEnabledClient.py index 9ac5e1ee..9a02872b 100644 --- a/python/tests/LibertyEnabledClient.py +++ b/python/tests/LibertyEnabledClient.py @@ -69,29 +69,32 @@ class LibertyEnabledClient(WebClient): # 'LIBV=urn:liberty:iff:2003-08,http://projectliberty.org/specs/v1')) 'Accept': ','.join((httpRequestHeaders['Accept'], 'application/vnd.liberty-request+xml')) }) - # FIXME: Lasso should provide a way for Liberty-enabled client to create a "server" without - # metadata, instead of using 'singleSignOnServiceUrl'. + # FIXME: Lasso should provide a way for Liberty-enabled client to create a "lassoServer" + # without metadata, instead of using 'singleSignOnServiceUrl'. idpSingleSignOnServiceUrl = None + lassoServerDump = None def __init__(self, internet): WebClient.__init__(self, internet) + def getLassoServer(self): + return lasso.Server.new_from_dump(self.lassoServerDump) + def login(self, principal, site, path): httpResponse = self.sendHttpRequestToSite(site, 'GET', path) failUnlessEqual( httpResponse.headers['Content-Type'], 'application/vnd.liberty-request+xml') - lecp = lasso.Lecp.new(None) + lassoServer = self.getLassoServer() + lecp = lasso.Lecp.new(lassoServer) authnRequestEnvelope = httpResponse.body - # FIXME: I don't understand why authnRequestEnvelopeMsg is base64 encoded. I think this - # is an error. - import base64 - authnRequestEnvelope = base64.encodestring(authnRequestEnvelope) lecp.process_authn_request_envelope_msg(authnRequestEnvelope) - lecp.build_authn_request_msg() # FIXME: The service provider could return an IDPList in authnRequestEnvelope, so that # we verify that self.idpSingleSignOnServiceUrl belongs to one of them + lecp.build_authn_request_msg(self.idpSite.providerId) + failUnless(lecp.msg_url) + failUnless(lecp.msg_body) httpResponse = self.sendHttpRequest( - 'POST', self.idpSingleSignOnServiceUrl, headers = {'Content-Type': 'text/xml'}, + 'POST', lecp.msg_url, headers = {'Content-Type': 'text/xml'}, body = lecp.msg_body) failUnlessEqual( httpResponse.headers.get('Content-Type', None), 'application/vnd.liberty-response+xml') diff --git a/python/tests/Provider.py b/python/tests/Provider.py index 53cb10c2..498c81fb 100644 --- a/python/tests/Provider.py +++ b/python/tests/Provider.py @@ -32,9 +32,9 @@ class Provider(WebSite): httpResponseHeaders.update({ 'Liberty-Enabled': 'LIBV=urn:liberty:iff:2003-08,http://projectliberty.org/specs/v1', }) - serverDump = None - webUserIdsByNameIdentifier = None - webSessionIdsByNameIdentifier = None + lassoServerDump = None + sessionTokensByNameIdentifier = None + userIdsByNameIdentifier = None - def getServer(self): - return lasso.Server.new_from_dump(self.serverDump) + def getLassoServer(self): + return lasso.Server.new_from_dump(self.lassoServerDump) diff --git a/python/tests/ServiceProvider.py b/python/tests/ServiceProvider.py index 2fd518a3..e23a9cf2 100644 --- a/python/tests/ServiceProvider.py +++ b/python/tests/ServiceProvider.py @@ -32,8 +32,8 @@ class ServiceProvider(Provider): idpSite = None # The identity provider, this service provider will use to authenticate users. def assertionConsumer(self, handler): - server = self.getServer() - login = lasso.Login.new(server) + lassoServer = self.getLassoServer() + login = lasso.Login.new(lassoServer) if handler.httpRequest.method == 'GET': login.init_request(handler.httpRequest.query, lasso.httpMethodRedirect) @@ -72,66 +72,67 @@ class ServiceProvider(Provider): else: return handler.respond( 400, - 'Bad Request: Method %s not handled by assertionConsumer' % handler.httpRequest.method) + 'Bad Request: Method %s not handled by assertionConsumer' + % handler.httpRequest.method) nameIdentifier = login.nameIdentifier failUnless(nameIdentifier) # Retrieve session dump, using name identifier or else try to use the client web session. # If session dump exists, give it to Lasso, so that it updates it. - webSession = self.getWebSessionFromNameIdentifier(nameIdentifier) - if webSession is None: - webSession = self.getWebSession(handler.httpRequest.client) - if webSession is not None: - sessionDump = webSession.sessionDump - if sessionDump is not None: - login.set_session_from_dump(sessionDump) + session = self.getSessionFromNameIdentifier(nameIdentifier) + if session is None: + session = handler.session + if session is not None: + lassoSessionDump = session.lassoSessionDump + if lassoSessionDump is not None: + login.set_session_from_dump(lassoSessionDump) # Retrieve identity dump, using name identifier or else try to retrieve him from web # session. If identity dump exists, give it to Lasso, so that it updates it. - webUser = self.getWebUserFromNameIdentifier(nameIdentifier) - if webUser is None: - webUser = self.getWebUserFromWebSession(webSession) - if webUser is not None: - identityDump = webUser.identityDump - if identityDump is not None: - login.set_identity_from_dump(identityDump) + user = self.getUserFromNameIdentifier(nameIdentifier) + if user is None: + user = self.getUserFromSession(session) + if user is not None: + lassoIdentityDump = user.lassoIdentityDump + if lassoIdentityDump is not None: + login.set_identity_from_dump(lassoIdentityDump) login.accept_sso() - if webUser is not None and identityDump is None: + if user is not None and lassoIdentityDump is None: failUnless(login.is_identity_dirty()) - identity = login.get_identity() - failUnless(identity) - identityDump = identity.dump() - failUnless(identityDump) + lassoIdentity = login.get_identity() + failUnless(lassoIdentity) + lassoIdentityDump = lassoIdentity.dump() + failUnless(lassoIdentityDump) failUnless(login.is_session_dirty()) - session = login.get_session() - failUnless(session) - sessionDump = session.dump() - failUnless(sessionDump) + lassoSession = login.get_session() + failUnless(lassoSession) + lassoSessionDump = lassoSession.dump() + failUnless(lassoSessionDump) # User is now authenticated. # If there was no web session yet, create it. Idem for the web user account. - if webSession is None: - webSession = self.createWebSession(handler.httpRequest.client) - if webUser is None: + if session is None: + session = self.createSession(handler.httpRequest.client) + if user is None: # A real service provider would ask user to login locally to create federation. Or it # would ask user informations to create a local account. - webUserId = handler.httpRequest.client.keyring.get(self.url, None) - userAuthenticated = webUserId in self.webUsers + userId = handler.httpRequest.client.keyring.get(self.url, None) + userAuthenticated = userId in self.users if not userAuthenticated: return handler.respond(401, 'Access Unauthorized: User has no account.') - webUser = self.webUsers[webUserId] + user = self.users[userId] - webSession.webUserId = webUser.uniqueId + session.userId = user.uniqueId # Store the updated identity dump and session dump. if login.is_identity_dirty(): - webUser.identityDump = identityDump - webSession.sessionDump = sessionDump + user.lassoIdentityDump = lassoIdentityDump + session.lassoSessionDump = lassoSessionDump - self.webUserIdsByNameIdentifier[nameIdentifier] = webUser.uniqueId - self.webSessionIdsByNameIdentifier[nameIdentifier] = webSession.uniqueId + self.userIdsByNameIdentifier[nameIdentifier] = user.uniqueId + self.sessionTokensByNameIdentifier[nameIdentifier] = session.token return handler.respond() @@ -139,7 +140,7 @@ class ServiceProvider(Provider): libertyEnabled = handler.httpRequest.headers.get('Liberty-Enabled', None) userAgent = handler.httpRequest.headers.get('User-Agent', None) # FIXME: Lasso should have a function to compute useLecp. - # Or this should be done in lasso.Login.new(server, libertyEnabled, userAgent) + # Or this should be done in lasso.Login.new(lassoServer, libertyEnabled, userAgent) useLecp = False if libertyEnabled: useLecp = 'urn:liberty:iff:2003-08' in libertyEnabled @@ -154,10 +155,10 @@ class ServiceProvider(Provider): forceAuthn = handler.httpRequest.getQueryBoolean('forceAuthn', False) isPassive = handler.httpRequest.getQueryBoolean('isPassive', False) - server = self.getServer() + lassoServer = self.getLassoServer() if useLecp: - lecp = lasso.Lecp.new(server) - lecp.init_authn_request(self.idpSite.providerId) # FIXME: The argument should be None. + lecp = lasso.Lecp.new(lassoServer) + lecp.init_authn_request() failUnlessEqual(lecp.request_type, lasso.messageTypeAuthnRequest) # FIXME: This protocol profile should be set by default by Lasso. @@ -173,12 +174,8 @@ class ServiceProvider(Provider): relayState = 'fake' lecp.request.set_relayState(relayState) - # FIXME: In my opinion, this method should be the renamed to build_authn_request_msg. lecp.build_authn_request_envelope_msg() authnRequestEnvelopeMsg = lecp.msg_body - # FIXME: I don't understand why authnRequestEnvelopeMsg is base64 encoded. - import base64 - authnRequestEnvelopeMsg = base64.decodestring(authnRequestEnvelopeMsg) failUnless(authnRequestEnvelopeMsg) # FIXME: Lasso should set a lecp.msg_content_type to # "application/vnd.liberty-request+xml". This should also be done for SOAP, etc, with @@ -194,8 +191,8 @@ class ServiceProvider(Provider): }, body = authnRequestEnvelopeMsg) else: - login = lasso.Login.new(server) - login.init_authn_request(self.idpSite.providerId) + login = lasso.Login.new(lassoServer) + login.init_authn_request() failUnlessEqual(login.request_type, lasso.messageTypeAuthnRequest) if forceAuthn: login.request.set_forceAuthn(forceAuthn) @@ -205,27 +202,27 @@ class ServiceProvider(Provider): login.request.set_consent(lasso.libConsentObtained) relayState = 'fake' login.request.set_relayState(relayState) - login.build_authn_request_msg() + login.build_authn_request_msg(self.idpSite.providerId) authnRequestUrl = login.msg_url failUnless(authnRequestUrl) return handler.respondRedirectTemporarily(authnRequestUrl) def logoutUsingSoap(self, handler): - webSession = self.getWebSession(handler.httpRequest.client) - if webSession is None: + session = handler.session + if session is None: return handler.respond(401, 'Access Unauthorized: User has no session opened.') - webUser = self.getWebUserFromWebSession(webSession) - if webUser is None: + user = self.getUserFromSession(session) + if user is None: return handler.respond(401, 'Access Unauthorized: User is not logged in.') - server = self.getServer() - logout = lasso.Logout.new(server, lasso.providerTypeSp) - identityDump = self.getIdentityDump(handler.httpRequest.client) - if identityDump is not None: - logout.set_identity_from_dump(identityDump) - sessionDump = self.getSessionDump(handler.httpRequest.client) - if sessionDump is not None: - logout.set_session_from_dump(sessionDump) + lassoServer = self.getLassoServer() + logout = lasso.Logout.new(lassoServer, lasso.providerTypeSp) + lassoIdentityDump = self.getIdentityDump(handler.httpRequest.client) + if lassoIdentityDump is not None: + logout.set_identity_from_dump(lassoIdentityDump) + lassoSessionDump = self.getLassoSessionDump(handler.httpRequest.client) + if lassoSessionDump is not None: + logout.set_session_from_dump(lassoSessionDump) logout.init_request() logout.build_request_msg() @@ -241,24 +238,24 @@ class ServiceProvider(Provider): failIf(logout.is_identity_dirty()) identity = logout.get_identity() failUnless(identity) - identityDump = identity.dump() - failUnless(identityDump) + lassoIdentityDump = identity.dump() + failUnless(lassoIdentityDump) failUnless(logout.is_session_dirty()) - session = logout.get_session() - if session is None: + lassoSession = logout.get_session() + if lassoSession is None: # The user is no more authenticated on any identity provider. Log him out. - del webSession.sessionDump - del webSession.webUserId + del session.lassoSessionDump + del session.userId # We also delete the session, but it is not mandantory, since the user is logged out # anyway. - del self.webSessions[webSession.uniqueId] + del self.sessions[session.token] else: # The user is still logged in on some other identity providers. - sessionDump = session.dump() - failUnless(sessionDump) - webSession.sessionDump = sessionDump + lassoSessionDump = lassoSession.dump() + failUnless(lassoSessionDump) + session.lassoSessionDump = lassoSessionDump nameIdentifier = logout.nameIdentifier failUnless(nameIdentifier) - del self.webSessionIdsByNameIdentifier[nameIdentifier] + del self.sessionTokensByNameIdentifier[nameIdentifier] return handler.respond() diff --git a/python/tests/abstractweb.py b/python/tests/abstractweb.py index 14eaced3..6c80ff7a 100644 --- a/python/tests/abstractweb.py +++ b/python/tests/abstractweb.py @@ -35,28 +35,35 @@ class HttpRequestMixin: query = None scheme = None # 'http' or 'https' - def getFormField(self, name, default = 'none'): + def getFormField(self, name, default = None): raise NotImplementedError - def getQueryBoolean(self, name, default = 'none'): - try: - fieldValue = self.getQueryField(name) - except KeyError: - if default == 'none': - raise + def getQueryBoolean(self, name, default = None): + fieldValue = self.getQueryField(name, 'none') + if fieldValue == 'none': return default - return fieldValue.lower not in ('', '0', 'false') + else: + return fieldValue.lower not in ('', '0', 'false') - def getQueryField(self, name, default = 'none'): + def getQueryField(self, name, default = None): if self.query: for field in self.query.split('&'): fieldName, fieldValue = field.split('=') if name == fieldName: return fieldValue - if default == 'none': - raise KeyError(name) return default + def hasFormField(self, name): + raise NotImplementedError + + def hasQueryField(self, name): + if self.query: + for field in self.query.split('&'): + fieldName, fieldValue = field.split('=') + if name == fieldName: + return True + return False + class HttpResponseMixin: body = None @@ -113,7 +120,7 @@ class HttpResponseMixin: self.statusMessage = statusMessage else: self.statusMessage = self.defaultStatusMessages.get(statusCode) - httpResponseHeaders = httpRequestHandler.webServer.httpResponseHeaders + httpResponseHeaders = httpRequestHandler.site.httpResponseHeaders if headers: httpResponseHeaders = httpResponseHeaders.copy() for name, value in headers.iteritems(): @@ -133,7 +140,7 @@ class HttpRequestHandlerMixin: httpResponse = None session = None user = None - webServer = None # The web server, which can then redirect to several virtual hosts + site = None # The virtual host def respond(self, statusCode = 200, statusMessage = None, headers = None, body = None): # Session must be saved before responding. Otherwise, when the server is multitasked or @@ -154,3 +161,23 @@ class HttpRequestHandlerMixin: def respondRedirectTemporarily(self, url): raise NotImplementedError + + +class WebSessionMixin: + publishToken = False + token = None + + +class WebSiteMixin: + def authenticateX509User(self, clientCertificate): + # We should check certificate (for example clientCertificate.get_serial_number() + # and return the user if one matches, or None otherwise. + return None + + def authenticateLoginPasswordUser(self, login, password): + # We should check login & password and return the user if one matches or None otherwise. + return None + + +class WebUserMixin: + sessionToken = None diff --git a/python/tests/http.py b/python/tests/http.py index fca7eec7..301f0393 100644 --- a/python/tests/http.py +++ b/python/tests/http.py @@ -274,7 +274,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): # requestline = None # Strange # request_version = None # Strange server_version = 'HttpRequestHandlerMixin/1.0' - webServer = None # Class variable. + site = None # Class variable. def handle(self): """Handle multiple requests if necessary.""" @@ -313,11 +313,186 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): return logger.info(self.raw_requestline.strip()) logger.debug(str(self.headers)) -## if hasattr(self.connection, 'get_peer_certificate'): -## peerCertificate = self.connection.get_peer_certificate() + +############### + + session = None + sessionToken = None + user = None + + # Handle X.509 certificate authentication. + if hasattr(self.connection, 'get_peer_certificate'): + clientCertificate = self.connection.get_peer_certificate() + if clientCertificate: + user = self.site.authenticateX509User(clientCertificate) + if user is None: + logger.info('Unknown certificate (serial number = %s)' + % clientCertificate.get_serial_number()) + else: + sessionToken = user.sessionToken + if sessionToken: + session = self.site.getSessionFromToken(sessionToken) + if session is None: + sessionToken = None + del user.sessionToken + # Don't call user.getDocument().save() now, because the session + # will be retrieved (from cookie or sessionToken query field) + # or created. So user/@sessionToken will be updated. + else: + # For security reasons, we want to minimize the publication of + # session token (it is better not to store it in a cookie or in + # URLs). The client need to send the certificate each time, for the + # session to continue. + if session.publishToken: + del session.publishToken + + # Handle HTTP authentication. + authorization = self.httpRequest.headers.get('authorization') + if self.httpRequest.hasQueryField('login') and not authorization \ + and rootDataHolder.getConfigBoolean('yep:useHttpAuthentication', default = False): + # Ask for HTTP authentication. + return self.outputErrorUnauthorized(httpPath) + 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 + # sending an error page immediately, we send the real page, so the user will see + # the page instead of an error message. + authorization = None + self.httpAuthenticationLogoutTrick = True + if authorization: + try: + authenticationScheme, credentials = authorization.split(None, 1) + except ValueError: + return self.outputErrorUnauthorized(httpPath) + authenticationScheme = authenticationScheme.lower() + if authenticationScheme == 'basic': + loginAndPassword = base64.decodestring(credentials) + try: + login, password = loginAndPassword.split(':', 1) + except: + login = loginAndPassword + password = '' + logs.debug('Basic authentication: login = "%s" / password = "%s"' % ( + login, password)) + if password: + user = self.site.authenticateLoginPasswordUser(login, password) + if user is None: + logger.info('Unknown user (login = "%s" / password = "%s")' % ( + login, password)) + return self.outputErrorUnauthorized(httpPath) + else: + sessionToken = user.sessionToken + if sessionToken: + session = sessions.retrieveSession(sessionToken) + if session is None: + sessionToken = None + user.deleteSessionToken() + # Don't call user.getDocument().save() now, because the session + # will be retrieved (from cookie or sessionToken query field) + # or created. So user/@sessionToken will be updated. + else: + # For security reasons, we want to minimize the publication of + # session token (it is better not to store it in a cookie or in + # URLs). + if session.publishToken: + del session.publishToken + elif login: + # No password was given. Assume login contains a session token. + # TODO: sanity chek on login + session = sessions.retrieveSession(login) + if session is not None: + account = session.getAccount() + if account is not None: + user = account.getUser() + if user is not None \ + and user.getSessionToken() != session.getToken(): + # Sanity check. + user.setSessionToken(session.getToken()) + user.getDocument().save() + else: + logs.info('Unknown authentication scheme = %s' % authenticationScheme) + return self.outputErrorUnauthorized(httpPath) + + # Handle use of cookies, session and user. + cookie = None + cookieContent = {} + if self.httpRequest.headers.has_key('Cookie'): + logs.debug('Cookie received:') + cookie = Cookie.SimpleCookie( + self.httpRequest.headers['Cookie']) + for k, v in cookie.items(): + cookieContent[k] = v.value + logs.debug(' %s = %s' % (k, cookieContent[k])) + self.cookie = cookie + + sessionToken = None + sessionTokenInCookie = False + if self.httpRequest.hasQueryField('sessionToken'): + sessionToken = self.httpRequest.getQueryField('sessionToken') + if not sessionToken: + sessionToken = None + if session is not None and sessionToken != session.token: + sessionToken = None + if cookieContent.has_key('sessionToken'): + cookieSessionToken = cookieContent['sessionToken'] + if cookieSessionToken: + if session is None or cookieSessionToken == session.token: + if sessionToken is None: + sessionToken = cookieSessionToken + if cookieSessionToken == sessionToken: + sessionTokenInCookie = True + canUseCookie = True + if session is None and sessionToken is not None: + session = sessions.retrieveSession(sessionToken) + if session is None: + sessionToken = None + sessionTokenInCookie = False + else: + if user is None: + # import expression.modules.passwordaccounts as passwordaccounts + account = session.getAccount(acceptOnlyAccount = False) + if account is not None: + user = account.getUser() + if user is not None and user.getSessionToken() != sessionToken: + # Sanity check. + user.setSessionToken(session.getToken()) + user.getDocument().save() + else: + # The user has been authenticated (using HTTP authentication), but the + # associated session didn't exist (or was too old, or...). So, update + # its sessionToken. + user.setSessionToken(session.getToken()) + user.getDocument().save() + # For security reasons, we want to minimize the publication of session + # token (it is better not to store it in a cookie or in URLs). + if session.publishToken: + del session.publishToken + if session is None and user is not None: + # The user has been authenticated (using HTTP authentication), but the session + # doesn't exist yet (or was too old, or...). Create a new session. + session = sessions.getOrCreateSession() + # For security reasons, we want to minimize the publication of session + # token (it is better not to store it in a cookie or in URLs). + # session.publishToken = False # False is the default value. + session.setAccountAbsolutePath(account.getAbsolutePath()) + user.setSessionToken(session.getToken()) + user.getDocument().save() + self.user = user + if user is not None: + logs.debug('User: %s' % user.simpleLabel) + self.session = session + if session is not None: + if not sessionTokenInCookie: + # The sessionToken is valid but is not stored in the cookie. So, don't try to + # use cookie. + canUseCookie = False + logs.debug('Session: %s' % session.token) + self.canUseCookie = canUseCookie + +############### try: - self.webServer.handleHttpRequestHandler(self) + self.site.handleHttpRequestHandler(self) except IOError: logger.exception('An exception occured:') path = self.path.split('?')[0] @@ -332,146 +507,146 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): logger.info('%s - - [%s] %s' % ( self.address_string(), self.log_date_time_string(), format % arguments)) - def outputAlert(self, data, title = None, url = None): - import html - if title is None: - title = N_('Alert') - # FIXME: Handle XSLT template. - if url: - buttonsBar = html.div(class_ = 'buttons-bar') - actionButtonsBar = html.span(class_ = 'action-buttons-bar') - buttonsBar.append(actionButtonsBar) - actionButtonsBar.append(html.a(_('OK'), class_ = 'button', href = url)) - else: - buttonsBar = None - layout = html.html( - html.head(html.title(_(title))), - html.body( - html.p(_(data), class_ = 'alert'), - buttonsBar, - ), - ) - self.outputData(layout.serialize(), contentLocation = None, mimeType = 'text/html') - - def outputData(self, data, contentLocation = None, headers = None, mimeType = None, - modificationTime = None, successCode = 200): - # Session must be saved before responding. Otherwise, when the server is multitasked or - # multithreaded, it may receive a new HTTP request before the session is saved. - if self.session is not None and self.session.isDirty: - self.session.save() - - if isinstance(data, basestring): - dataFile = None - dataSize = len(data) - else: - dataFile = data - data = '' - if hasattr(dataFile, 'fileno'): - dataSize = os.fstat(dataFile.fileno())[6] - else: - # For StringIO and cStringIO classes. - dataSize = len(dataFile.getvalue()) - - if headers is None: - headers = {} - if time.time() > self.socketCreationTime + 300: - headers['Connection'] = 'close' - elif not self.close_connection: - headers['Connection'] = 'Keep-Alive' - if contentLocation is not None: - headers['Content-Location'] = contentLocation - if mimeType: - headers['Content-Type'] = '%s; charset=utf-8' % mimeType - if modificationTime: - headers['Last-Modified'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', modificationTime) - # TODO: Could also output Content-MD5. - ifModifiedSince = self.headers.get('If-Modified-Since') - if modificationTime and ifModifiedSince: - # We don't want to use bandwith if the file was not modified. - try: - ifModifiedSinceTime = time.strptime(ifModifiedSince[:25], '%a, %d %b %Y %H:%M:%S') - if modificationTime[:8] <= ifModifiedSinceTime[:8]: - self.send_response(304, 'Not Modified.') - for key in ('Connection', 'Content-Location'): - if key in headers: - self.send_header(key, headers[key]) - self.setCookie() - self.end_headers() - return - except (ValueError, KeyError): - pass - if dataFile is not None: - assert not data - data = dataFile.read(1048576) # Read first MB chunk - if mimeType == 'text/html' and data.startswith('<?xml'): - # Internet Explorer 6 renders the page differently when they start with <?xml...>, so - # skip it. - i = data.find('\n') - if i > 0: - data = data[i + 1:] - else: - i = data.find('>') - if i > 0: - data = data[i + 1:] - dataSize -= i + 1 - # Compress data if possible and if data is not too big. - acceptEncoding = self.headers.get('Accept-Encoding', '') - if 0 < dataSize < 1048576 and 'gzip' in acceptEncoding \ - and 'gzip;q=0' not in acceptEncoding: - # Since dataSize < 1 MB, the data is fully contained in string. - zbuf = cStringIO.StringIO() - zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf) - zfile.write(data) - zfile.close() - data = zbuf.getvalue() - dataSize = len(data) - headers['Content-Encoding'] = 'gzip' - headers['Content-Length'] = '%d' % dataSize - successMessages = { - 200: 'OK', - 207: 'Multi-Status', - } - assert successCode in successMessages, 'Unknown success code %d.' % successCode - if self.httpAuthenticationLogoutTrick and successCode == 200: - successCode = 401 - successMessage = 'Access Unauthorized' - headers['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm - else: - successMessage = successMessages[successCode] - self.send_response(successCode, successMessage) - for key, value in headers.items(): - self.send_header(key, value) - self.setCookie() - self.end_headers() - if self.httpRequest.method != 'HEAD' and dataSize > 0: - outputFile = self.wfile - if data: - outputFile.write(data) - if dataFile is not None: - while True: - chunk = dataFile.read(1048576) # 1 MB chunk - if not chunk: - break - outputFile.write(chunk) - return - - def outputErrorAccessForbidden(self, filePath): - if filePath is None: - message = 'Access Forbidden' - else: - message = 'Access to "%s" Forbidden.' % filePath - logger.info(message) - data = '<html><body>%s</body></html>' % message - return self.send_error(403, message, data, setCookie = True) - - def outputErrorBadRequest(self, reason): - if reason: - message = 'Bad Request: %s' % reason - else: - message = 'Bad Request' - logger.info(message) - data = '<html><body>%s</body></html>' % message - return self.send_error(400, message, data) +## def outputAlert(self, data, title = None, url = None): +## import html +## if title is None: +## title = N_('Alert') +## # FIXME: Handle XSLT template. +## if url: +## buttonsBar = html.div(class_ = 'buttons-bar') +## actionButtonsBar = html.span(class_ = 'action-buttons-bar') +## buttonsBar.append(actionButtonsBar) +## actionButtonsBar.append(html.a(_('OK'), class_ = 'button', href = url)) +## else: +## buttonsBar = None +## layout = html.html( +## html.head(html.title(_(title))), +## html.body( +## html.p(_(data), class_ = 'alert'), +## buttonsBar, +## ), +## ) +## self.outputData(layout.serialize(), contentLocation = None, mimeType = 'text/html') + +## def outputData(self, data, contentLocation = None, headers = None, mimeType = None, +## modificationTime = None, successCode = 200): +## # Session must be saved before responding. Otherwise, when the server is multitasked or +## # multithreaded, it may receive a new HTTP request before the session is saved. +## if self.session is not None and self.session.isDirty: +## self.session.save() + +## if isinstance(data, basestring): +## dataFile = None +## dataSize = len(data) +## else: +## dataFile = data +## data = '' +## if hasattr(dataFile, 'fileno'): +## dataSize = os.fstat(dataFile.fileno())[6] +## else: +## # For StringIO and cStringIO classes. +## dataSize = len(dataFile.getvalue()) + +## if headers is None: +## headers = {} +## if time.time() > self.socketCreationTime + 300: +## headers['Connection'] = 'close' +## elif not self.close_connection: +## headers['Connection'] = 'Keep-Alive' +## if contentLocation is not None: +## headers['Content-Location'] = contentLocation +## if mimeType: +## headers['Content-Type'] = '%s; charset=utf-8' % mimeType +## if modificationTime: +## headers['Last-Modified'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', modificationTime) +## # TODO: Could also output Content-MD5. +## ifModifiedSince = self.headers.get('If-Modified-Since') +## if modificationTime and ifModifiedSince: +## # We don't want to use bandwith if the file was not modified. +## try: +## ifModifiedSinceTime = time.strptime(ifModifiedSince[:25], '%a, %d %b %Y %H:%M:%S') +## if modificationTime[:8] <= ifModifiedSinceTime[:8]: +## self.send_response(304, 'Not Modified.') +## for key in ('Connection', 'Content-Location'): +## if key in headers: +## self.send_header(key, headers[key]) +## self.setCookie() +## self.end_headers() +## return +## except (ValueError, KeyError): +## pass +## if dataFile is not None: +## assert not data +## data = dataFile.read(1048576) # Read first MB chunk +## if mimeType == 'text/html' and data.startswith('<?xml'): +## # Internet Explorer 6 renders the page differently when they start with <?xml...>, so +## # skip it. +## i = data.find('\n') +## if i > 0: +## data = data[i + 1:] +## else: +## i = data.find('>') +## if i > 0: +## data = data[i + 1:] +## dataSize -= i + 1 +## # Compress data if possible and if data is not too big. +## acceptEncoding = self.headers.get('Accept-Encoding', '') +## if 0 < dataSize < 1048576 and 'gzip' in acceptEncoding \ +## and 'gzip;q=0' not in acceptEncoding: +## # Since dataSize < 1 MB, the data is fully contained in string. +## zbuf = cStringIO.StringIO() +## zfile = gzip.GzipFile(mode = 'wb', fileobj = zbuf) +## zfile.write(data) +## zfile.close() +## data = zbuf.getvalue() +## dataSize = len(data) +## headers['Content-Encoding'] = 'gzip' +## headers['Content-Length'] = '%d' % dataSize +## successMessages = { +## 200: 'OK', +## 207: 'Multi-Status', +## } +## assert successCode in successMessages, 'Unknown success code %d.' % successCode +## if self.httpAuthenticationLogoutTrick and successCode == 200: +## successCode = 401 +## successMessage = 'Access Unauthorized' +## headers['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm +## else: +## successMessage = successMessages[successCode] +## self.send_response(successCode, successMessage) +## for key, value in headers.items(): +## self.send_header(key, value) +## self.setCookie() +## self.end_headers() +## if self.httpRequest.method != 'HEAD' and dataSize > 0: +## outputFile = self.wfile +## if data: +## outputFile.write(data) +## if dataFile is not None: +## while True: +## chunk = dataFile.read(1048576) # 1 MB chunk +## if not chunk: +## break +## outputFile.write(chunk) +## return + +## def outputErrorAccessForbidden(self, filePath): +## if filePath is None: +## message = 'Access Forbidden' +## else: +## message = 'Access to "%s" Forbidden.' % filePath +## logger.info(message) +## data = '<html><body>%s</body></html>' % message +## return self.send_error(403, message, data, setCookie = True) + +## def outputErrorBadRequest(self, reason): +## if reason: +## message = 'Bad Request: %s' % reason +## else: +## message = 'Bad Request' +## logger.info(message) +## data = '<html><body>%s</body></html>' % message +## return self.send_error(400, message, data) def outputErrorInternalServer(self): message = 'Internal Server Error' @@ -479,71 +654,71 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin): data = '<html><body>%s</body></html>' % message return self.send_error(500, message, data) - def outputErrorMethodNotAllowed(self, reason): - if reason: - message = 'Method Not Allowed: %s' % reason - else: - message = 'Method Not Allowed' - logger.info(message) - data = '<html><body>%s</body></html>' % message - # This error doesn't need a pretty interface. - # FIXME: Add an 'Allow' header containing a list of valid methods for the requested - # resource. - return self.send_error(405, message, data) - - def outputErrorNotFound(self, filePath): - if filePath is None: - message = 'Not Found' - else: - message = 'Path "%s" Not Found.' % filePath - logger.info(message) - data = '<html><body>%s</body></html>' % message - return self.send_error(404, message, data, setCookie = True) - - def outputErrorUnauthorized(self, filePath): - if filePath is None: - message = 'Access Unauthorized' - else: - message = 'Access to "%s" Unauthorized.' % filePath - logger.info(message) - data = '<html><body>%s</body></html>' % message - headers = {} - return self.send_error(401, message, data, headers, setCookie = True) - - def outputInformationContinue(self): - message = 'Continue' - logger.debug(message) - self.send_response(100, message) - - def outputSuccessCreated(self, filePath): - if filePath is None: - message = 'Created' - else: - message = 'File "%s" Created.' % filePath - logger.debug(message) - data = '<html><body>%s</body></html>' % message - self.send_response(201, message) - if time.time() > self.socketCreationTime + 300: - self.send_header('Connection', 'close') - elif not self.close_connection: - self.send_header('Connection', 'Keep-Alive') - self.send_header('Content-Type', 'text/html; charset=utf-8') - self.send_header('Content-Length', '%d' % len(data)) - self.setCookie() - self.end_headers() - if self.httpRequest.method != 'HEAD': - self.wfile.write(data) - - def outputSuccessNoContent(self): - message = 'No Content' - logger.debug(message) - self.send_response(204, message) - if time.time() > self.socketCreationTime + 300: - self.send_header('Connection', 'close') - elif not self.close_connection: - self.send_header('Connection', 'Keep-Alive') - self.setCookie() - self.end_headers() +## def outputErrorMethodNotAllowed(self, reason): +## if reason: +## message = 'Method Not Allowed: %s' % reason +## else: +## message = 'Method Not Allowed' +## logger.info(message) +## data = '<html><body>%s</body></html>' % message +## # This error doesn't need a pretty interface. +## # FIXME: Add an 'Allow' header containing a list of valid methods for the requested +## # resource. +## return self.send_error(405, message, data) + +## def outputErrorNotFound(self, filePath): +## if filePath is None: +## message = 'Not Found' +## else: +## message = 'Path "%s" Not Found.' % filePath +## logger.info(message) +## data = '<html><body>%s</body></html>' % message +## return self.send_error(404, message, data, setCookie = True) + +## def outputErrorUnauthorized(self, filePath): +## if filePath is None: +## message = 'Access Unauthorized' +## else: +## message = 'Access to "%s" Unauthorized.' % filePath +## logger.info(message) +## data = '<html><body>%s</body></html>' % message +## headers = {} +## return self.send_error(401, message, data, headers, setCookie = True) + +## def outputInformationContinue(self): +## message = 'Continue' +## logger.debug(message) +## self.send_response(100, message) + +## def outputSuccessCreated(self, filePath): +## if filePath is None: +## message = 'Created' +## else: +## message = 'File "%s" Created.' % filePath +## logger.debug(message) +## data = '<html><body>%s</body></html>' % message +## self.send_response(201, message) +## if time.time() > self.socketCreationTime + 300: +## self.send_header('Connection', 'close') +## elif not self.close_connection: +## self.send_header('Connection', 'Keep-Alive') +## self.send_header('Content-Type', 'text/html; charset=utf-8') +## self.send_header('Content-Length', '%d' % len(data)) +## self.setCookie() +## self.end_headers() +## if self.httpRequest.method != 'HEAD': +## self.wfile.write(data) + +## def outputSuccessNoContent(self): +## message = 'No Content' +## logger.debug(message) +## self.send_response(204, message) +## if time.time() > self.socketCreationTime + 300: +## self.send_header('Connection', 'close') +## elif not self.close_connection: +## self.send_header('Connection', 'Keep-Alive') +## self.setCookie() +## self.end_headers() def outputUnknownException(self): import traceback, cStringIO @@ -704,10 +879,7 @@ class HttpsRequestHandler(HttpRequestHandlerMixin, BaseHTTPSRequestHandler): # not enough for a web server. class HttpServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer): - uriScheme = 'http' - webServer = None - + pass class HttpsServer(SocketServer.ForkingMixIn, BaseHTTPSServer): - uriScheme = 'https' # Ob - webServer = None + pass diff --git a/python/tests/login_tests.py b/python/tests/login_tests.py index 47378f48..88034c3c 100644 --- a/python/tests/login_tests.py +++ b/python/tests/login_tests.py @@ -46,68 +46,62 @@ class LoginTestCase(unittest.TestCase): site = IdentityProvider(internet, 'https://identity-provider/') site.providerId = 'https://identity-provider/metadata' - server = lasso.Server.new( + lassoServer = lasso.Server.new( '../../examples/data/idp-metadata.xml', '../../examples/data/idp-public-key.pem', '../../examples/data/idp-private-key.pem', '../../examples/data/idp-crt.pem', lasso.signatureMethodRsaSha1) - server.add_provider( + lassoServer.add_provider( '../../examples/data/sp-metadata.xml', '../../examples/data/sp-public-key.pem', '../../examples/data/ca-crt.pem') - site.serverDump = server.dump() - failUnless(site.serverDump) - server.destroy() - - site.addWebUser('Chantereau') - site.addWebUser('Clapies') - site.addWebUser('Febvre') - site.addWebUser('Nowicki') + site.lassoServerDump = lassoServer.dump() + failUnless(site.lassoServerDump) + lassoServer.destroy() + + site.addUser('Chantereau') + site.addUser('Clapies') + site.addUser('Febvre') + site.addUser('Nowicki') # Frederic Peters has no account on identity provider. return site def generateLibertyEnabledClient(self, internet): client = LibertyEnabledClient(internet) - # FIXME: Lasso should provide a way for Liberty-enabled client to create a "server" without - # metadata, instead of using 'singleSignOnServiceUrl'. -## server = lasso.Server.new( -## None, # A LECP has no metadata. -## '../../examples/data/client-public-key.pem', -## '../../examples/data/client-private-key.pem', -## '../../examples/data/client-crt.pem', -## lasso.signatureMethodRsaSha1) -## server.add_provider( -## '../../examples/data/idp-metadata.xml', -## '../../examples/data/idp-public-key.pem', -## '../../examples/data/ca-crt.pem') -## client.server = server - client.idpSingleSignOnServiceUrl = 'https://identity-provider/singleSignOn' + lassoServer = lasso.Server.new() + lassoServer.add_provider( + '../../examples/data/idp-metadata.xml', + '../../examples/data/idp-public-key.pem', + '../../examples/data/ca-crt.pem') + client.lassoServerDump = lassoServer.dump() + failUnless(client.lassoServerDump) + lassoServer.destroy() return client def generateSpSite(self, internet): site = ServiceProvider(internet, 'https://service-provider/') site.providerId = 'https://service-provider/metadata' - server = lasso.Server.new( + lassoServer = lasso.Server.new( '../../examples/data/sp-metadata.xml', '../../examples/data/sp-public-key.pem', '../../examples/data/sp-private-key.pem', '../../examples/data/sp-crt.pem', lasso.signatureMethodRsaSha1) - server.add_provider( + lassoServer.add_provider( '../../examples/data/idp-metadata.xml', '../../examples/data/idp-public-key.pem', '../../examples/data/ca-crt.pem') - site.serverDump = server.dump() - failUnless(site.serverDump) - server.destroy() + site.lassoServerDump = lassoServer.dump() + failUnless(site.lassoServerDump) + lassoServer.destroy() - site.addWebUser('Nicolas') - site.addWebUser('Romain') - site.addWebUser('Valery') + site.addUser('Nicolas') + site.addUser('Romain') + site.addUser('Valery') # Christophe Nowicki has no account on service provider. - site.addWebUser('Frederic') + site.addUser('Frederic') return site def setUp(self): @@ -120,19 +114,6 @@ class LoginTestCase(unittest.TestCase): 'failUnlessAlmostEqual', 'failUnlessRaises', 'failUnlessEqual'): builtins.delete(name) - def test00(self): - """LECP login.""" - internet = Internet() - idpSite = self.generateIdpSite(internet) - spSite = self.generateSpSite(internet) - spSite.idpSite = idpSite - lec = self.generateLibertyEnabledClient(internet) - principal = Principal(internet, 'Romain Chantereau') - principal.keyring[idpSite.url] = 'Chantereau' - principal.keyring[spSite.url] = 'Romain' - httpResponse = lec.login(principal, spSite, '/login') - raise str((httpResponse.headers['Content-Type'], httpResponse.body)) - def test01(self): """Service provider initiated login using HTTP redirect and service provider initiated logout using SOAP.""" @@ -148,8 +129,8 @@ class LoginTestCase(unittest.TestCase): failUnlessEqual(httpResponse.statusCode, 200) httpResponse = principal.sendHttpRequestToSite(spSite, 'GET', '/logoutUsingSoap') failUnlessEqual(httpResponse.statusCode, 200) - failIf(spSite.webSessions) - failIf(idpSite.webSessions) + 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.""" @@ -264,20 +245,19 @@ class LoginTestCase(unittest.TestCase): httpResponse = principal.sendHttpRequestToSite(spSite, 'GET', '/logoutUsingSoap') failUnlessEqual(httpResponse.statusCode, 401) -## def test06(self): -## """Service provider LECP login.""" - -## # LECP has asked service provider for login. -## spServer = self.getServer() - -## # FIXME: Why doesn't lasso.Lecp.new have spServer as argument? -## # spLecp = lasso.Lecp.new(spServer) -## spLecp = lasso.Lecp.new() -## spLecp.init_authn_request_envelope(sp, ) -## lasso_lecp_init_authn_request_envelope(sp_lecp, spserver, authnRequest); -## lasso_lecp_build_authn_request_envelope_msg(sp_lecp); -## msg = g_strdup(sp_lecp->msg_body); -## lasso_lecp_destroy(sp_lecp); + def test07(self): + """LECP login.""" + internet = Internet() + idpSite = self.generateIdpSite(internet) + spSite = self.generateSpSite(internet) + spSite.idpSite = idpSite + lec = self.generateLibertyEnabledClient(internet) + lec.idpSite = idpSite + principal = Principal(internet, 'Romain Chantereau') + principal.keyring[idpSite.url] = 'Chantereau' + principal.keyring[spSite.url] = 'Romain' + httpResponse = lec.login(principal, spSite, '/login') + raise str((httpResponse.statusCode, httpResponse.statusMessage, httpResponse.headers['Content-Type'], httpResponse.body)) suite1 = unittest.makeSuite(LoginTestCase, 'test') diff --git a/python/tests/websimulator.py b/python/tests/websimulator.py index 0c923d05..411a1268 100644 --- a/python/tests/websimulator.py +++ b/python/tests/websimulator.py @@ -23,7 +23,7 @@ # FIXME: Replace principal with client in most methods. -# FIXME: Rename webUser to userAccount. +# FIXME: Rename user to userAccount. import abstractweb @@ -48,11 +48,9 @@ class HttpRequest(abstractweb.HttpRequestMixin, object): if form: self.form = form - def getFormField(self, name, default = 'none'): - if self.form and name in self.form: - return self.form[name] - if default == 'none': - raise KeyError(name) + def getFormField(self, name, default = None): + if self.form is not None: + return self.form.get(name, default) return default def getPath(self): @@ -76,6 +74,11 @@ class HttpRequest(abstractweb.HttpRequestMixin, object): def getScheme(self): return self.url.split(':', 1)[0].lower() + def hasFormField(self, name): + if self.form is None: + return False + return name in self.form + def send(self): webSite = self.client.internet.getWebSite(self.url) return webSite.handleHttpRequest(self) @@ -94,13 +97,18 @@ class HttpResponse(abstractweb.HttpResponseMixin, object): class HttpRequestHandler(abstractweb.HttpRequestHandlerMixin, object): HttpResponse = HttpResponse # Class - def __init__(self, webServer, httpRequest): - self.webServer = webServer + def __init__(self, site, httpRequest): + self.site = site self.httpRequest = httpRequest + def getSession(self): + return self.site.getSessionFromPrincipal(self.httpRequest.client) + def respondRedirectTemporarily(self, url): return self.httpRequest.client.redirect(url) + session = property(getSession) + class Internet(object): webSites = None @@ -125,13 +133,13 @@ class WebClient(object): 'User-Agent': 'LassoSimulator/0.0.0', 'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html', } - webSessionIds = None # Simulate the cookies, stored in user's navigator, and containing the + sessionTokens = None # Simulate the cookies, stored in user's navigator, and containing the # IDs of sessions already opened by the user. def __init__(self, internet): self.internet = internet self.keyring = {} - self.webSessionIds = {} + self.sessionTokens = {} def redirect(self, url): return self.sendHttpRequest('GET', url) @@ -169,104 +177,99 @@ class Principal(WebClient): self.name = name -class WebSession(object): +class WebSession(abstractweb.WebSessionMixin, object): """Simulation of session of a web site""" expirationTime = None # A sample session variable + isDirty = True loginDump = None # Used only by some identity providers - uniqueId = None # The session number - sessionDump = None - webUserId = None # ID of logged user. - - def __init__(self, uniqueId): - self.uniqueId = uniqueId - - -class WebUser(object): - """Simulation of user of a web site""" + lassoSessionDump = None + userId = None # ID of logged user. - identityDump = None - language = 'fr' # A sample user variable - uniqueId = None # The user name is used as an ID in this simulation. + def __init__(self, token): + self.token = token - def __init__(self, uniqueId): - self.uniqueId = uniqueId + def save(self): + pass -class WebSite(WebClient): +class WebSite(abstractweb.WebSiteMixin, WebClient): """Simulation of a web site""" httpResponseHeaders = { 'Server': 'Lasso Simulator Web Server', } - lastWebSessionId = 0 + lastSessionToken = 0 providerId = None # The Liberty providerID of this web site url = None # The main URL of web site - webUsers = None - webSessions = None + users = None + sessions = None def __init__(self, internet, url): WebClient.__init__(self, internet) self.url = url - self.webUserIdsByNameIdentifier = {} - self.webUsers = {} - self.webSessionIdsByNameIdentifier = {} - self.webSessions = {} + self.userIdsByNameIdentifier = {} + self.users = {} + self.sessionTokensByNameIdentifier = {} + self.sessions = {} self.internet.addWebSite(self) - def addWebUser(self, name): - self.webUsers[name] = WebUser(name) + def addUser(self, name): + self.users[name] = WebUser(name) - def createWebSession(self, client): - self.lastWebSessionId += 1 - webSession = WebSession(self.lastWebSessionId) - self.webSessions[self.lastWebSessionId] = webSession - client.webSessionIds[self.url] = self.lastWebSessionId - return webSession + def createSession(self, client): + self.lastSessionToken += 1 + session = WebSession(self.lastSessionToken) + self.sessions[self.lastSessionToken] = session + client.sessionTokens[self.url] = self.lastSessionToken + return session def getIdentityDump(self, principal): - webSession = self.getWebSession(principal) - webUser = self.getWebUserFromWebSession(webSession) - if webUser is None: - return None - return webUser.identityDump - - def getSessionDump(self, principal): - webSession = self.getWebSession(principal) - if webSession is None: + session = self.getSessionFromPrincipal(principal) + user = self.getUserFromSession(session) + if user is None: return None - return webSession.sessionDump + return user.lassoIdentityDump - def getWebSession(self, principal): - webSessionId = principal.webSessionIds.get(self.url, None) - if webSessionId is None: - # The user has no web session opened on this site. + def getLassoSessionDump(self, principal): + session = self.getSessionFromPrincipal(principal) + if session is None: return None - return self.webSessions.get(webSessionId, None) + return session.lassoSessionDump - def getWebSessionFromNameIdentifier(self, nameIdentifier): - webSessionId = self.webSessionIdsByNameIdentifier.get(nameIdentifier, None) - if webSessionId is None: + def getSessionFromNameIdentifier(self, nameIdentifier): + sessionToken = self.sessionTokensByNameIdentifier.get(nameIdentifier, None) + if sessionToken is None: # The user has no federation on this site or has no authentication assertion for this # federation. return None - return self.webSessions.get(webSessionId, None) + return self.sessions.get(sessionToken, None) + + def getSessionFromPrincipal(self, principal): + sessionToken = principal.sessionTokens.get(self.url, None) + return self.getSessionFromToken(sessionToken) - def getWebUserFromNameIdentifier(self, nameIdentifier): - webUserId = self.webUserIdsByNameIdentifier.get(nameIdentifier, None) - if webUserId is None: + def getSessionFromToken(self, sessionToken): + if sessionToken is None: + # The user has no web session opened on this site. + return None + return self.sessions.get(sessionToken, None) + + def getUserFromNameIdentifier(self, nameIdentifier): + userId = self.userIdsByNameIdentifier.get(nameIdentifier, None) + if userId is None: # The user has no federation on this site. return None - return self.webUsers.get(webUserId, None) + return self.users.get(userId, None) - def getWebUserFromWebSession(self, webSession): - if webSession is None: + def getUserFromSession(self, session): + if session is None: return None - webUserId = webSession.webUserId - if webUserId is None: + userId = session.userId + if userId is None: # The user has no account on this site. return None - return self.webUsers.get(webUserId, None) + return self.users.get(userId, None) def handleHttpRequest(self, httpRequest): httpRequestHandler = HttpRequestHandler(self, httpRequest) @@ -280,3 +283,14 @@ class WebSite(WebClient): return httpRequestHandler.respond( 404, 'Path "%s" Not Found.' % httpRequestHandler.httpRequest.path) return method(httpRequestHandler) + + +class WebUser(abstractweb.WebUserMixin, object): + """Simulation of user of a web site""" + + lassoIdentityDump = None + language = 'fr' # A sample user variable + uniqueId = None # The user name is used as an ID in this simulation. + + def __init__(self, uniqueId): + self.uniqueId = uniqueId |
