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