summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipsilon/login/common.py19
-rw-r--r--ipsilon/providers/saml2/sessions.py11
-rw-r--r--ipsilon/providers/saml2idp.py41
-rwxr-xr-xtests/testlogout.py77
4 files changed, 145 insertions, 3 deletions
diff --git a/ipsilon/login/common.py b/ipsilon/login/common.py
index 9beb741..d616882 100644
--- a/ipsilon/login/common.py
+++ b/ipsilon/login/common.py
@@ -273,11 +273,28 @@ class Login(Page):
class Logout(Page):
+ def __init__(self, *args, **kwargs):
+ super(Logout, self).__init__(*args, **kwargs)
+ self.handlers = {}
def root(self, *args, **kwargs):
- UserSession().logout(self.user)
+ us = UserSession()
+
+ for provider in self.handlers:
+ self.debug("Calling logout for provider %s" % provider)
+ obj = self.handlers[provider]
+ obj()
+
+ us.logout(self.user)
return self._template('logout.html', title='Logout')
+ def add_handler(self, provider, handler):
+ """
+ Providers can register a logout handler here that is called
+ when the IdP logout link is accessed.
+ """
+ self.handlers[provider] = handler
+
class Cancel(Page):
diff --git a/ipsilon/providers/saml2/sessions.py b/ipsilon/providers/saml2/sessions.py
index fb1f646..5931734 100644
--- a/ipsilon/providers/saml2/sessions.py
+++ b/ipsilon/providers/saml2/sessions.py
@@ -140,12 +140,16 @@ class SAMLSessionsContainer(Log):
self.sessions_logging_out[session.provider_id] = session
- def get_next_logout(self):
+ def get_next_logout(self, remove=True):
"""
Get the next session in the logged-in state and move
it to the logging_out state. Return the session that is
found.
+ :param remove: for IdP-initiated logout we can't remove the
+ session otherwise when the request comes back
+ in the user won't be seen as being logged-on.
+
Return None if no more sessions in login state.
"""
try:
@@ -153,7 +157,10 @@ class SAMLSessionsContainer(Log):
except IndexError:
return None
- session = self.sessions.pop(provider_id)
+ if remove:
+ session = self.sessions.pop(provider_id)
+ else:
+ session = self.sessions.itervalues().next()
if provider_id in self.sessions_logging_out:
self.sessions_logging_out.pop(provider_id)
diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py
index 8ff512c..9bc75b3 100644
--- a/ipsilon/providers/saml2idp.py
+++ b/ipsilon/providers/saml2idp.py
@@ -298,6 +298,8 @@ Provides SAML 2.0 authentication infrastructure. """
self._debug('Failed to init SAML2 provider: %r' % e)
return None
+ self._root.logout.add_handler(self.name, self.idp_initiated_logout)
+
# Import all known applications
data = self.get_data()
for idval in data:
@@ -320,6 +322,45 @@ Provides SAML 2.0 authentication infrastructure. """
if self.admin:
self.admin.add_sps()
+ def idp_initiated_logout(self):
+ """
+ Logout all SP sessions when the logout comes from the IdP.
+
+ For the current user only.
+ """
+ self._debug("IdP-initiated SAML2 logout")
+ us = UserSession()
+
+ saml_sessions = us.get_provider_data('saml2')
+ if saml_sessions is None:
+ self._debug("No SAML2 sessions to logout")
+ return
+ session = saml_sessions.get_next_logout(remove=False)
+ if session is None:
+ return
+
+ # Add a fake session to indicate where the user should
+ # be redirected to when all SP's are logged out.
+ idpurl = self._root.instance_base_url()
+ saml_sessions.add_session("_idp_initiated_logout",
+ idpurl,
+ "")
+ init_session = saml_sessions.find_session_by_provider(idpurl)
+ init_session.set_logoutstate(idpurl, "idp_initiated_logout", None)
+ saml_sessions.start_logout(init_session)
+
+ logout = self.idp.get_logout_handler()
+ logout.setSessionFromDump(session.session.dump())
+ logout.initRequest(session.provider_id)
+ try:
+ logout.buildRequestMsg()
+ except lasso.Error, e:
+ self.error('failure to build logout request msg: %s' % e)
+ raise cherrypy.HTTPRedirect(400, 'Failed to log out user: %s '
+ % e)
+
+ raise cherrypy.HTTPRedirect(logout.msgUrl)
+
class IdpMetadataGenerator(object):
diff --git a/tests/testlogout.py b/tests/testlogout.py
index b192739..5018066 100755
--- a/tests/testlogout.py
+++ b/tests/testlogout.py
@@ -291,3 +291,80 @@ if __name__ == '__main__':
print >> sys.stderr, " ERROR: %s" % repr(e)
sys.exit(1)
print " SUCCESS"
+
+ # Test IdP-initiated logout
+ print "testlogout: Access SP Protected Area of SP1...",
+ try:
+ page = sess.fetch_page(idpname, 'http://127.0.0.11:45081/sp/')
+ page.expected_value('text()', 'WORKS!')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: Access SP Protected Area of SP2...",
+ try:
+ page = sess.fetch_page(idpname, 'http://127.0.0.10:45082/sp/')
+ page.expected_value('text()', 'WORKS!')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: Access the IdP...",
+ try:
+ page = sess.fetch_page(idpname, 'http://127.0.0.10:45080/%s' % idpname)
+ page.expected_value('//div[@id="welcome"]/p/text()',
+ 'Welcome %s!' % user)
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: IdP-initiated logout ...",
+ try:
+ page = sess.fetch_page(idpname,
+ 'http://127.0.0.10:45080/%s/logout' % idpname)
+ page.expected_value('//div[@id="content"]/p/a/text()', 'Log In')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: Ensure logout of SP1 ...",
+ try:
+ ensure_logout(sess, idpname, 'http://127.0.0.11:45081/sp/')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: Ensure logout of SP2 ...",
+ try:
+ ensure_logout(sess, idpname, 'http://127.0.0.10:45082/sp/')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: Access the IdP...",
+ try:
+ page = sess.fetch_page(idpname,
+ 'http://127.0.0.10:45080/%s/login' % idpname)
+ page.expected_value('//div[@id="welcome"]/p/text()',
+ 'Welcome %s!' % user)
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"
+
+ print "testlogout: IdP-initiated logout with no SP sessions...",
+ try:
+ page = sess.fetch_page(idpname,
+ 'http://127.0.0.10:45080/%s/logout' % idpname)
+ page.expected_value('//div[@id="logout"]/p//text()',
+ 'Successfully logged out.')
+ except ValueError, e:
+ print >> sys.stderr, " ERROR: %s" % repr(e)
+ sys.exit(1)
+ print " SUCCESS"