summaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorEmmanuel Raviart <eraviart@entrouvert.com>2004-08-11 09:59:58 +0000
committerEmmanuel Raviart <eraviart@entrouvert.com>2004-08-11 09:59:58 +0000
commit80a5b0009a69fd1e4d0451278dbbc5808563dc42 (patch)
tree80aa70e18baf8e9b3e537d690061ee137b316319 /python
parent2d1f06f55b37d677f4c17e1fa1c60a0bf65778b3 (diff)
downloadlasso-80a5b0009a69fd1e4d0451278dbbc5808563dc42.tar.gz
lasso-80a5b0009a69fd1e4d0451278dbbc5808563dc42.tar.xz
lasso-80a5b0009a69fd1e4d0451278dbbc5808563dc42.zip
In python/tests, there are now a sample IDP (sample-idp.py) and a sample SP
(sample-sp.py). The two applications are real servers.
Diffstat (limited to 'python')
-rw-r--r--python/tests/IdentityProvider.py11
-rw-r--r--python/tests/LibertyEnabledClientProxy.py9
-rw-r--r--python/tests/Provider.py10
-rw-r--r--python/tests/ServiceProvider.py80
-rw-r--r--python/tests/abstractweb.py22
-rw-r--r--python/tests/http.py73
-rw-r--r--python/tests/liberty.py53
-rw-r--r--python/tests/libertysimulator.py53
-rw-r--r--python/tests/login_tests.py4
-rwxr-xr-xpython/tests/sample-idp.py146
-rwxr-xr-xpython/tests/sample-sp.py147
-rw-r--r--python/tests/submissions.py292
-rw-r--r--python/tests/web.py98
-rw-r--r--python/tests/websimulator.py10
14 files changed, 939 insertions, 69 deletions
diff --git a/python/tests/IdentityProvider.py b/python/tests/IdentityProvider.py
index c48ddb5d..4e3dd158 100644
--- a/python/tests/IdentityProvider.py
+++ b/python/tests/IdentityProvider.py
@@ -24,15 +24,14 @@
import lasso
-from Provider import Provider
-from websimulator import *
+import Provider
-class IdentityProvider(Provider):
+class IdentityProviderMixin(Provider.ProviderMixin):
soapResponseMsgs = None
- def __init__(self, internet, url):
- Provider.__init__(self, internet, url)
+ def __init__(self):
+ Provider.ProviderMixin.__init__(self)
self.soapResponseMsgs = {}
def singleSignOn(self, handler):
@@ -103,6 +102,7 @@ class IdentityProvider(Provider):
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)
@@ -173,6 +173,7 @@ class IdentityProvider(Provider):
soapResponseMsg = self.soapResponseMsgs.get(artifact, None)
if soapResponseMsg is None:
raise Exception('FIXME: Handle the case when artifact is wrong')
+ del self.soapResponseMsgs[artifact]
return handler.respond(
headers = {'Content-Type': 'text/xml'}, body = soapResponseMsg)
elif requestType == lasso.requestTypeLogout:
diff --git a/python/tests/LibertyEnabledClientProxy.py b/python/tests/LibertyEnabledClientProxy.py
index 12871ca9..46a2f9dc 100644
--- a/python/tests/LibertyEnabledClientProxy.py
+++ b/python/tests/LibertyEnabledClientProxy.py
@@ -24,10 +24,10 @@
import lasso
-from websimulator import *
+import abstractweb
-class LibertyEnabledClientProxy(WebClient):
+class LibertyEnabledClientProxyMixin(abstractweb.WebClientMixin):
# A service provider MAY provide a list of identity providers it recognizes by including the
# <lib:IDPList> element in the <lib:AuthnRequestEnvelope>. The format and processing rules for
# the identity provider list MUST be as defined in [LibertyProtSchema].
@@ -58,7 +58,7 @@ class LibertyEnabledClientProxy(WebClient):
# <lib:AuthnResponse> MUST be encoded by applying a base64 transformation (refer to
# [RFC2045]) to the <lib:AuthnResponse> and all its elements.
- httpRequestHeaders = WebClient.httpRequestHeaders.copy()
+ httpRequestHeaders = abstractweb.WebClientMixin.httpRequestHeaders.copy()
httpRequestHeaders.update({
# FIXME: Is this the correct syntax for several URLs in LIBV?
'Liberty-Enabled': 'LIBV=urn:liberty:iff:2003-08,http://projectliberty.org/specs/v1',
@@ -73,9 +73,6 @@ class LibertyEnabledClientProxy(WebClient):
lassoServerDump = None
principal = None
- def __init__(self, internet):
- WebClient.__init__(self, internet)
-
def getLassoServer(self):
return lasso.Server.new_from_dump(self.lassoServerDump)
diff --git a/python/tests/Provider.py b/python/tests/Provider.py
index a0e1a08f..07a62ed0 100644
--- a/python/tests/Provider.py
+++ b/python/tests/Provider.py
@@ -24,11 +24,11 @@
import lasso
-from websimulator import *
+import abstractweb
-class Provider(WebSite):
- httpResponseHeaders = WebSite.httpResponseHeaders.copy()
+class ProviderMixin(abstractweb.WebSiteMixin):
+ httpResponseHeaders = abstractweb.WebSiteMixin.httpResponseHeaders.copy()
httpResponseHeaders.update({
'Liberty-Enabled': 'LIBV=urn:liberty:iff:2003-08,http://projectliberty.org/specs/v1',
})
@@ -37,8 +37,8 @@ class Provider(WebSite):
sessionTokensByNameIdentifier = None
userIdsByNameIdentifier = None
- def __init__(self, internet, url):
- WebSite.__init__(self, internet, url)
+ def __init__(self):
+ abstractweb.WebSiteMixin.__init__(self)
self.userIdsByNameIdentifier = {}
self.sessionTokensByNameIdentifier = {}
diff --git a/python/tests/ServiceProvider.py b/python/tests/ServiceProvider.py
index e7b2eeb9..b616a87f 100644
--- a/python/tests/ServiceProvider.py
+++ b/python/tests/ServiceProvider.py
@@ -24,11 +24,11 @@
import lasso
-from Provider import Provider
-from websimulator import *
+import Provider
-class ServiceProvider(Provider):
+class ServiceProviderMixin(Provider.ProviderMixin):
+ createNewAccountWhenNewFederationForUnknownUser = False
idpSite = None # The identity provider, this service provider will use to authenticate users.
def assertionConsumer(self, handler):
@@ -111,26 +111,81 @@ class ServiceProvider(Provider):
# If there was no web session yet, create it. Idem for the web user account.
if session is None:
session = handler.createSession()
+ session.publishToken = True
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.
- 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.')
- user = self.users[userId]
+ # 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)
session.userId = user.uniqueId
+ user.sessionToken = session.token
# Store the updated identity dump and session dump.
+ session.lassoSessionDump = lassoSessionDump
if login.is_identity_dirty():
user.lassoIdentityDump = lassoIdentityDump
+
+ self.userIdsByNameIdentifier[nameIdentifier] = user.uniqueId
+ self.sessionTokensByNameIdentifier[nameIdentifier] = session.token
+
+ # 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)
- return handler.respond()
+ def assertionConsumer_success(self, handler):
+ return handler.respond(200, headers = {'Content-Type': 'text/plain'},
+ body = 'Liberty authentication succeeded')
def login(self, handler):
libertyEnabled = handler.httpRequest.headers.get('Liberty-Enabled', None)
@@ -252,4 +307,5 @@ class ServiceProvider(Provider):
failUnless(nameIdentifier)
del self.sessionTokensByNameIdentifier[nameIdentifier]
- return handler.respond()
+ return handler.respond(200, headers = {'Content-Type': 'text/plain'},
+ body = 'Liberty logout succeeded')
diff --git a/python/tests/abstractweb.py b/python/tests/abstractweb.py
index 671c0a73..2f189c94 100644
--- a/python/tests/abstractweb.py
+++ b/python/tests/abstractweb.py
@@ -27,6 +27,7 @@
class HttpRequestMixin:
+ body = None
headers = None
method = None # 'GET' or 'POST' or 'PUT' or...
url = None
@@ -170,13 +171,23 @@ class HttpRequestHandlerMixin:
raise NotImplementedError
-class WebSessionMixin:
+class WebClientMixin:
+ httpRequestHeaders = {
+ 'User-Agent': 'LassoSimulator/0.0.0',
+ 'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html',
+ }
+
+ def __init__(self):
+ pass
+
+
+class WebSessionMixin(WebClientMixin):
isDirty = True
- publishToken = False
token = None
userId = None # ID of logged user
def __init__(self, token):
+ WebClientMixin.__init__(self)
self.token = token
def getSimpleLabel(self):
@@ -229,14 +240,15 @@ class WebSiteMixin:
def newSession(self):
self.lastSessionToken += 1
- session = self.WebSession(self.lastSessionToken)
- self.sessions[self.lastSessionToken] = session
+ sessionToken = str(self.lastSessionToken)
+ session = self.WebSession(sessionToken)
+ self.sessions[sessionToken] = session
return session
def newUser(self, name = None):
if name is None:
self.lastUserId += 1
- userId = self.lastUserId
+ userId = str(self.lastUserId)
else:
userId = name
user = self.WebUser(userId, name = name)
diff --git a/python/tests/http.py b/python/tests/http.py
index 750c4e93..30561e77 100644
--- a/python/tests/http.py
+++ b/python/tests/http.py
@@ -33,6 +33,7 @@ Features:
"""
+import base64
import BaseHTTPServer
import Cookie
import cStringIO
@@ -50,6 +51,7 @@ except ImportError:
SSL = None
import abstractweb
+import submissions
try:
@@ -116,9 +118,14 @@ class BaseHTTPSServer(SocketServer.TCPServer):
class HttpRequest(abstractweb.HttpRequestMixin, object):
handler = None
+ submission = None
def __init__(self, handler):
self.handler = handler
+ self.submission = submissions.readSubmission(self.handler)
+
+ def getBody(self):
+ return self.submission.readFile()
def getHeaders(self):
return self.handler.headers
@@ -145,6 +152,7 @@ class HttpRequest(abstractweb.HttpRequestMixin, object):
def getUrl(self):
return "%s://%s%s" % (self.scheme, self.headers.get('Host'), self.pathAndQuery)
+ body = property(getBody)
headers = property(getHeaders)
method = property(getMethod)
path = property(getPath)
@@ -276,6 +284,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
server_version = 'HttpRequestHandlerMixin/1.0'
site = None # Class variable
testCookieSupport = False
+ useHttpAuthentication = True
def createSession(self):
session = abstractweb.HttpRequestHandlerMixin.createSession(self)
@@ -285,7 +294,6 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
def handle(self):
"""Handle multiple requests if necessary."""
- self.httpRequest = HttpRequest(self)
self.socketCreationTime = time.time()
try:
try:
@@ -300,7 +308,8 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
except SSL.ZeroReturnError:
pass
except SSL.Error, exception:
- raise str((exception, exception[0]))
+ logger.debug('SSL error in handle. Error = %s, %s' % (exception, exception[0]))
+ raise # FIXME
if exception[0] == ('PEM routines', 'PEM_read_bio', 'no start line'):
pass
else:
@@ -314,13 +323,22 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
"""Handle a single HTTP request."""
self.raw_requestline = self.rfile.readline()
if not self.raw_requestline:
- self.close_connection = 1
+ self.close_connection = True
return
if not self.parse_request(): # An error code has been sent, just exit
return
logger.info(self.raw_requestline.strip())
logger.debug(str(self.headers))
+ # The server isn't forked nor threaded, so we don't want to keep connections open, to avoid
+ # dead-locks which occur for example when the connection with the navigator to the identity
+ # provider is kept open, while a service provider sends a SOAP request to the identity
+ # provider.
+ # Remove this line for forked or threaded servers.
+ self.close_connection = True
+
+ self.httpRequest = HttpRequest(self)
+
# Retrieve the session and user, if possible.
session = None
@@ -353,7 +371,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
# Handle HTTP authentication.
authorization = self.httpRequest.headers.get('authorization')
if self.httpRequest.hasQueryField('login') and not authorization \
- and rootDataHolder.getConfigBoolean('yep:useHttpAuthentication', default = False):
+ and self.useHttpAuthentication:
# Ask for HTTP authentication.
return self.outputErrorUnauthorized(httpPath)
if self.httpRequest.hasQueryField('logout') and authorization:
@@ -462,7 +480,6 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
# token (it is better not to store it in a cookie or in URLs).
if session.publishToken:
del session.publishToken
- self.canUseCookie = canUseCookie
if session is None and user is not None:
# The user has been authenticated (using HTTP or X.509 authentication), but the session
# doesn't exist yet (or was too old, or...). Create a new session.
@@ -480,6 +497,7 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
# use cookie.
canUseCookie = False
logger.debug('Session: %s' % session.simpleLabel)
+ self.canUseCookie = canUseCookie
self.user = user
if user is not None:
logger.debug('User: %s' % user.simpleLabel)
@@ -674,15 +692,17 @@ class HttpRequestHandlerMixin(abstractweb.HttpRequestHandlerMixin):
## 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 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 = {}
+ if self.useHttpAuthentication:
+ headers["WWW-Authenticate"] = 'Basic realm="%s"' % self.realm
+ return self.send_error(401, message, data, headers, setCookie = True)
## def outputInformationContinue(self):
## message = 'Continue'
@@ -878,13 +898,26 @@ class HttpsRequestHandler(HttpRequestHandlerMixin, BaseHTTPSRequestHandler):
scheme = 'https'
-# We use ForkingMixIn instead of ThreadingMixIn because the Python binding for
-# libxml2 limits the number of registered xpath functions to 10. Even if we use
-# only one xpathContext, this would limit the number of threads to 10, wich is
-# not enough for a web server.
+## # We use ForkingMixIn instead of ThreadingMixIn because the Python binding for
+## # libxml2 limits the number of registered xpath functions to 10. Even if we use
+## # only one xpathContext, this would limit the number of threads to 10, wich is
+## # not enough for a web server.
+
+
+## class HttpServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
+## pass
+
-class HttpServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
+## class HttpsServer(SocketServer.ForkingMixIn, BaseHTTPSServer):
+## pass
+
+
+# No fork nor thread.
+
+class HttpServer(BaseHTTPServer.HTTPServer):
pass
-class HttpsServer(SocketServer.ForkingMixIn, BaseHTTPSServer):
+
+class HttpsServer(BaseHTTPSServer):
pass
+
diff --git a/python/tests/liberty.py b/python/tests/liberty.py
new file mode 100644
index 00000000..46c21783
--- /dev/null
+++ b/python/tests/liberty.py
@@ -0,0 +1,53 @@
+# -*- 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
+
+
+from LibertyEnabledClientProxy import LibertyEnabledClientProxyMixin
+from IdentityProvider import IdentityProviderMixin
+from ServiceProvider import ServiceProviderMixin
+from Provider import ProviderMixin
+import web
+
+
+class LibertyEnabledClientProxy(LibertyEnabledClientProxyMixin, web.WebClient):
+ def __init__(self):
+ web.WebClient.__init__(self)
+ LibertyEnabledClientProxyMixin.__init__(self)
+
+
+class Provider(ProviderMixin, web.WebSite):
+ def __init__(self, url):
+ web.WebSite.__init__(self, url)
+ ProviderMixin.__init__(self)
+
+
+class IdentityProvider(IdentityProviderMixin, Provider):
+ def __init__(self, url):
+ Provider.__init__(self, url)
+ IdentityProviderMixin.__init__(self)
+
+
+class ServiceProvider(ServiceProviderMixin, Provider):
+ def __init__(self, url):
+ Provider.__init__(self, url)
+ ServiceProviderMixin.__init__(self)
diff --git a/python/tests/libertysimulator.py b/python/tests/libertysimulator.py
new file mode 100644
index 00000000..3cc5cb7a
--- /dev/null
+++ b/python/tests/libertysimulator.py
@@ -0,0 +1,53 @@
+# -*- 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
+
+
+from LibertyEnabledClientProxy import LibertyEnabledClientProxyMixin
+from IdentityProvider import IdentityProviderMixin
+from ServiceProvider import ServiceProviderMixin
+from Provider import ProviderMixin
+import websimulator
+
+
+class LibertyEnabledClientProxy(LibertyEnabledClientProxyMixin, websimulator.WebClient):
+ def __init__(self, internet):
+ websimulator.WebClient.__init__(self, internet)
+ LibertyEnabledClientProxyMixin.__init__(self)
+
+
+class Provider(ProviderMixin, websimulator.WebSite):
+ def __init__(self, internet, url):
+ websimulator.WebSite.__init__(self, internet, url)
+ ProviderMixin.__init__(self)
+
+
+class IdentityProvider(IdentityProviderMixin, Provider):
+ def __init__(self, internet, url):
+ Provider.__init__(self, internet, url)
+ IdentityProviderMixin.__init__(self)
+
+
+class ServiceProvider(ServiceProviderMixin, Provider):
+ def __init__(self, internet, url):
+ Provider.__init__(self, internet, url)
+ ServiceProviderMixin.__init__(self)
diff --git a/python/tests/login_tests.py b/python/tests/login_tests.py
index 5dd09b76..68c511f7 100644
--- a/python/tests/login_tests.py
+++ b/python/tests/login_tests.py
@@ -35,9 +35,7 @@ if not '../.libs' in sys.path:
import lasso
import builtins
-from IdentityProvider import IdentityProvider
-from LibertyEnabledClientProxy import LibertyEnabledClientProxy
-from ServiceProvider import ServiceProvider
+from libertysimulator import *
from websimulator import *
diff --git a/python/tests/sample-idp.py b/python/tests/sample-idp.py
new file mode 100755
index 00000000..6db7c18f
--- /dev/null
+++ b/python/tests/sample-idp.py
@@ -0,0 +1,146 @@
+#! /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.IdentityProvider('https://identity-provider/')
+ site.providerId = 'https://identity-provider/metadata'
+
+ 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)
+ lassoServer.add_provider(
+ '../../examples/data/sp-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('Chantereau')
+ site.newUser('Clapies')
+ site.newUser('Febvre')
+ site.newUser('Nowicki')
+ # Frederic Peters has no account on identity provider.
+
+ HttpRequestHandlerMixin.site = site # Directly a site, not a server => no virtual host.
+## httpServer = http.HttpServer(('127.0.0.2', 80), HttpRequestHandler)
+## logger.info('Serving HTTP on %s port %s...' % httpServer.socket.getsockname())
+ httpServer = http.HttpsServer(
+ ('127.0.0.2', 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.py b/python/tests/sample-sp.py
new file mode 100755
index 00000000..321b114e
--- /dev/null
+++ b/python/tests/sample-sp.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/')
+ site.providerId = 'https://service-provider/metadata'
+ site.idpSite = liberty.IdentityProvider('https://identity-provider/')
+ site.idpSite.providerId = 'https://identity-provider/metadata'
+
+ 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)
+ lassoServer.add_provider(
+ '../../examples/data/idp-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.3', 80), HttpRequestHandler)
+## logger.info('Serving HTTP on %s port %s...' % httpServer.socket.getsockname())
+ httpServer = http.HttpsServer(
+ ('127.0.0.3', 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/submissions.py b/python/tests/submissions.py
new file mode 100644
index 00000000..a9f9504c
--- /dev/null
+++ b/python/tests/submissions.py
@@ -0,0 +1,292 @@
+# -*- coding: UTF-8 -*-
+
+
+# HTTP Client and Server Enhanced Classes
+# By: Frederic Peters <fpeters@entrouvert.com>
+# Emmanuel Raviart <eraviart@entrouvert.com>
+#
+# Copyright (C) 2004 Entr'ouvert
+# http://www.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.
+
+
+"""Wrapper for HTML form submissions, simulating Web Forms 2 behaviour
+
+See http://whatwg.org/specs/web-forms/2004-06-27-call-for-comments/#x-www-form-xml
+"""
+
+
+import cgi
+
+
+class AbstractSubmission(object):
+ httpRequestHandler = None
+ length = None
+ mimeType = None
+
+ def __init__(self, httpRequestHandler, contentLength):
+ assert httpRequestHandler
+ self.httpRequestHandler = httpRequestHandler
+ assert isinstance(contentLength, int)
+ self.length = contentLength
+
+ def getField(self, name, index = 0, default = None):
+ # Return either a string or a sequence of strings.
+ fieldList = self.getFieldList(name, index)
+ if not fieldList:
+ return default
+ elif len(fieldList) == 1:
+ return fieldList[0]
+ else:
+ return fieldList
+
+ def getFieldList(self, name, index = 0):
+ # Return a sequence of strings.
+ raise NotImplementedError
+
+ def getFile(self, name, index = 0, default = None):
+ # Return either an instance of FileUpload or a sequence of FileUpload instances.
+ fileList = self.getFileList(name, index)
+ if not fileList:
+ return default
+ elif len(fileList) == 1:
+ return fileList[0]
+ else:
+ return fileList
+
+ def getFileList(self, name, index = 0):
+ # Return a sequence of FileUpload instances.
+ raise NotImplementedError
+
+## def getRepeat(self, template):
+## raise NotImplementedError
+
+ def hasField(self, name, index = 0):
+ raise NotImplementedError
+
+ def hasFile(self, name, index = 0):
+ raise NotImplementedError
+
+ def readFile(self):
+ raise NotImplementedError
+
+
+class FakeSubmission(AbstractSubmission):
+ _fields = None
+
+ def __init__(self, fields = None, query = None):
+ self._fields = {}
+ if fields:
+ for name, value in fields.items():
+ self._fields[name] = [value]
+ if query:
+ for name, value in cgi.parse_qsl(query, keep_blank_values = True):
+ if name in self._fields:
+ self._fields[name].append(value)
+ else:
+ self._fields[name] = [value]
+
+ def getFieldList(self, name, index = 0):
+ if index == 0 and name in self._fields:
+ return self._fields[name]
+ return []
+
+ def getFileList(self, name, index = 0):
+ return []
+
+ def hasField(self, name, index = 0):
+ return index == 0 and name in self._fields
+
+ def hasFile(self, name, index = 0):
+ return False
+
+ def readFile(self):
+ return None
+
+
+class FieldStorageSubmission(AbstractSubmission):
+ """Submission wrapper for all encoding types handled by module 'cgi':
+ 'application/x-www-form-urlencoded', 'multipart/form-data'...
+
+ This submission method discards the control index and repetition block parts of the form data
+ set. So, for these encoding types, control index is always 0.
+ """
+
+ fieldStorage = None
+
+ def __init__(self, httpRequestHandler, contentType, contentLength, contentTypeHeader):
+ super(FieldStorageSubmission, self).__init__(httpRequestHandler, contentLength)
+ assert contentType
+ self.mimeType = contentType
+ # The use of environ seems to be required by cgi.FieldStorage.
+ # It also needs to add "content-type" in headers.
+ fakeHeaders = {}
+ for key, value in httpRequestHandler.headers.items():
+ fakeHeaders[key] = value
+ environ = {
+ "CONTENT_TYPE": contentTypeHeader,
+ "REQUEST_METHOD": httpRequestHandler.command,
+ }
+ if not "content-type" in fakeHeaders:
+ fakeHeaders["content-type"] = environ["CONTENT_TYPE"]
+ if contentLength:
+ environ["CONTENT_LENGTH"] = str(contentLength)
+ splitedPath = httpRequestHandler.path.split("?")
+ if len(splitedPath) >= 2:
+ httpQuery = splitedPath[1]
+ if httpQuery:
+ environ["QUERY_STRING"] = httpQuery
+ self.fieldStorage = cgi.FieldStorage(
+ environ = environ,
+ fp = httpRequestHandler.rfile,
+ headers = fakeHeaders,
+ keep_blank_values = True)
+
+ def getFieldList(self, name, index = 0):
+ if index > 0:
+ return []
+ return [item.value
+ for item in self.fieldStorage.list
+ if item.name == name and item.filename is None]
+
+ def getFileList(self, name, index = 0):
+ if index > 0:
+ return []
+ return [FileUpload(item.filename, item.type, item.file)
+ for item in self.fieldStorage.list
+ if item.name == name and item.filename is not None]
+
+ def hasField(self, name, index = 0):
+ if index == 0:
+ for item in self.fieldStorage.list:
+ if item.name == name and item.filename is None:
+ return True
+ return False
+
+ def hasFile(self, name, index = 0):
+ if index == 0:
+ for item in self.fieldStorage.list:
+ if item.name == name and item.filename is not None:
+ return True
+ return False
+
+ def readFile(self):
+ return None
+
+
+class FileUpload(object):
+ file = None
+ filename = None # Optional
+ mimeType = None # Optional: MIME type with optional parameters.
+
+ def __init__(self, filename, mimeType, file):
+ if filename is not None:
+ self.filename = filename
+ if mimeType is not None:
+ self.mimeType = mimeType
+ assert file is not None
+ self.file = file
+
+
+class FileUploadSubmission(AbstractSubmission):
+ """Submission for exactly one file
+
+ If the enctype attribute is not specified in the form (or is set to the empty string), and the
+ form consists of exactly one file upload control with exactly one file selected, then the user
+ agent use this submission method.
+ Also used for HTTP PUT...
+
+ Note: FileUploadSubmission contains all the FileUpload interface, so that it can be used as a
+ FileUpload.
+ """
+
+ file = None
+ filename = None # Always None
+
+ def __init__(self, httpRequestHandler, contentType, contentLength):
+ super(FileUploadSubmission, self).__init__(httpRequestHandler, contentLength)
+ assert contentType
+ self.mimeType = contentType
+ self.file = httpRequestHandler.rfile
+
+ def getFieldList(self, name, index = 0):
+ return []
+
+ def getFileList(self, name, index = 0):
+ return []
+
+ def hasField(self, name, index = 0):
+ return False
+
+ def hasFile(self, name, index = 0):
+ return False
+
+ def readFile(self):
+ if self.length == 0:
+ return None
+ return self.file.read(self.length)
+
+
+class XmlFormSubmission(AbstractSubmission):
+ """Submission for encoding type 'application/x-www-form+xml'"""
+
+ file = None
+ mimeType = "application/x-www-form+xml"
+
+ def __init__(self, httpRequestHandler, contentType, contentLength):
+ super(XmlFormSubmission, self).__init__(httpRequestHandler, contentLength)
+ assert contentType == self.mimeType
+ self.file = httpRequestHandler.rfile
+
+ def getFieldList(self, name, index = 0):
+ raise NotImplementedError
+
+ def getFileList(self, name, index = 0):
+ return NotImplementedError
+
+ def hasField(self, name, index = 0):
+ raise NotImplementedError
+
+ def hasFile(self, name, index = 0):
+ raise NotImplementedError
+
+ def readFile(self):
+ return None
+
+
+def readSubmission(httpRequestHandler):
+ # Get query, headers and form variables.
+ if httpRequestHandler.headers.typeheader is None:
+ if httpRequestHandler.command in ("GET", "HEAD", "POST"):
+ contentTypeHeader = "application/x-www-form-urlencoded"
+ else:
+ contentTypeHeader = httpRequestHandler.headers.type
+ else:
+ contentTypeHeader = httpRequestHandler.headers.typeheader
+ contentType, contentTypeOptions = cgi.parse_header(contentTypeHeader)
+ contentLength = httpRequestHandler.headers.get("content-length")
+ try:
+ contentLength = int(contentLength)
+ except (TypeError, ValueError):
+ contentLength = 0
+ if contentType == "application/x-www-form+xml":
+ submission = XmlFormSubmission(httpRequestHandler, contentType, contentLength)
+ elif contentType in ("application/x-www-form-urlencoded", "multipart/form-data"):
+ submission = FieldStorageSubmission(
+ httpRequestHandler, contentType, contentLength, contentTypeHeader)
+ else:
+ submission = FileUploadSubmission(httpRequestHandler, contentType, contentLength)
+ return submission
diff --git a/python/tests/web.py b/python/tests/web.py
index 90d74a73..d2db8ed4 100644
--- a/python/tests/web.py
+++ b/python/tests/web.py
@@ -33,7 +33,84 @@ Features:
"""
+import urlparse
+
+from OpenSSL import SSL
+
import abstractweb
+import http
+
+
+class ReceivedHttpResponse(object):
+ body = None
+ headers = None
+ statusCode = None # 200 or...
+ statusMessage = None
+
+ def __init__(self, statusCode = 200, statusMessage = None, headers = None, body = None):
+ if statusCode:
+ self.statusCode = statusCode
+ if statusMessage:
+ self.statusMessage = statusMessage
+ if headers:
+ self.headers = headers
+ if body:
+ self.body = body
+
+
+class WebClient(abstractweb.WebClientMixin, object):
+ certificateAbsolutePath = None
+ privateKeyAbsolutePath = None
+ peerCaCertificateAbsolutePath = None
+
+ def sendHttpRequest(self, method, url, headers = None, body = None):
+ parsedUrl = urlparse.urlparse(url)
+ addressingScheme, hostName, path = parsedUrl[:3]
+ if addressingScheme == 'https':
+ connection = http.HttpsConnection(
+ hostName, None, self.privateKeyAbsolutePath, self.certificateAbsolutePath,
+ self.peerCaCertificateAbsolutePath)
+ else:
+ connection = httplib.HTTPConnection(hostName)
+ if headers:
+ httpRequestHeaders = self.httpRequestHeaders.copy()
+ for name, value in headers.iteritems():
+ httpRequestHeaders[name] = value
+ else:
+ httpRequestHeaders = self.httpRequestHeaders
+ failUnless('Content-Type' in httpRequestHeaders)
+ try:
+ connection.request('POST', path, body, httpRequestHeaders)
+ except SSL.Error, error:
+ if error.args and error.args[0] and error.args[0][0] \
+ and error.args[0][0][0] == 'SSL routines':
+ logger.debug('SSL Error in sendHttpRequest. Error = %s' % repr(error))
+ raise
+ response = connection.getresponse()
+ try:
+ body = response.read()
+ except SSL.SysCallError, error:
+ logger.debug('No SOAP answer in sendHttpRequest. Error = %s' % repr(error))
+ raise
+ httpResponse = ReceivedHttpResponse(response.status, response.reason, response.msg, body)
+ return httpResponse
+
+
+class WebSession(abstractweb.WebSessionMixin, object):
+ """Simulation of session of a web site"""
+
+ expirationTime = None # A sample session variable
+ lassoLoginDump = None # Used only by some identity providers
+ lassoSessionDump = None
+ publishToken = False
+
+
+class WebUser(abstractweb.WebUserMixin, object):
+ """Simulation of user of a web site"""
+
+ lassoIdentityDump = None
+ language = 'fr' # A sample user variable
+ password = None
class WebSite(abstractweb.WebSiteMixin, WebClient):
@@ -42,14 +119,19 @@ class WebSite(abstractweb.WebSiteMixin, WebClient):
WebSession = WebSession
WebUser = WebUser
- def __init__(self, internet, url):
- WebClient.__init__(self, internet)
+ def __init__(self, url):
+ WebClient.__init__(self)
abstractweb.WebSiteMixin.__init__(self)
self.url = url
- self.internet.addWebSite(self)
def authenticate(self, handler, callback, *arguments, **keywordArguments):
- FIXME: TODO.
+ user = handler.user
+ if user is None:
+ failUnless(handler.useHttpAuthentication)
+ return handler.outputErrorUnauthorized(handler.httpRequest.path)
+ else:
+ # The user is already authenticated using HTTP authentication.
+ userAuthenticated = True
import lasso
authenticationMethod = lasso.samlAuthenticationMethodPassword # FIXME
@@ -57,6 +139,9 @@ class WebSite(abstractweb.WebSiteMixin, WebClient):
session = handler.session
if session is None:
session = handler.createSession()
+ # No need to publish token, because we are using HTTP authentication.
+ if session.publishToken:
+ del session.publishToken
user = handler.user
if user is None:
user = handler.createUser()
@@ -64,3 +149,8 @@ class WebSite(abstractweb.WebSiteMixin, WebClient):
user.sessionToken = session.token
return callback(handler, userAuthenticated, authenticationMethod, *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)
diff --git a/python/tests/websimulator.py b/python/tests/websimulator.py
index f5fb843f..cd75195d 100644
--- a/python/tests/websimulator.py
+++ b/python/tests/websimulator.py
@@ -22,10 +22,6 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-# FIXME: Replace principal with client in most methods.
-# FIXME: Rename user to userAccount.
-
-
import abstractweb
@@ -126,13 +122,9 @@ class Internet(object):
raise Exception('Unknown web site: %s' % url)
-class WebClient(object):
+class WebClient(abstractweb.WebClientMixin, object):
internet = None
keyring = None
- httpRequestHeaders = {
- 'User-Agent': 'LassoSimulator/0.0.0',
- 'Accept': 'text/xml,application/xml,application/xhtml+xml,text/html',
- }
sessionTokens = None # Simulate the cookies, stored in user's navigator, and containing the
# IDs of sessions already opened by the user.