Remove expired SAML2 sessions
authorRob Crittenden <rcritten@redhat.com>
Mon, 20 Apr 2015 20:44:41 +0000 (16:44 -0400)
committerPatrick Uiterwijk <puiterwijk@redhat.com>
Mon, 11 May 2015 22:39:28 +0000 (00:39 +0200)
Run a cherrypy background task to sift through the sessions
database and find expired entries and remove them.

From my testing if a previous execution of the background task
is still executing when the next one is scheduled to run, it will
skip it. In other words, you can't end up with multiple expirations
running at the same time.

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-by: Patrick Uiterwijk <puiterwijk@redhat.com>
ipsilon/providers/saml2/sessions.py
ipsilon/providers/saml2idp.py

index d01bb6e..6b3d860 100644 (file)
@@ -1,7 +1,9 @@
 # Copyright (C) 2015 Ipsilon project Contributors, for license see COPYING
 
+from cherrypy import config as cherrypy_config
 from ipsilon.util.log import Log
 from ipsilon.util.data import SAML2SessionStore
+import datetime
 
 LOGGED_IN = 1
 INIT_LOGOUT = 2
@@ -9,6 +11,23 @@ LOGGING_OUT = 4
 LOGGED_OUT = 8
 
 
+def expire_sessions():
+    """
+    Find all expired sessions and remove them. This is executed as a
+    background cherrypy task.
+    """
+    ss = SAML2SessionStore()
+    data = ss.get_data()
+    now = datetime.datetime.now()
+    for idval in data:
+        r = data[idval]
+        exp = r.get('expiration_time', None)
+        if exp is not None:
+            exp = datetime.datetime.strptime(exp, '%Y-%m-%d %H:%M:%S.%f')
+            if exp < now:
+                ss.remove_session(idval)
+
+
 class SAMLSession(Log):
     """
     A SAML login session.
@@ -26,10 +45,12 @@ class SAMLSession(Log):
                     logout response will include an InResponseTo value
                     which matches this.
        logout_request - the Logout request object
+       expiration_time - the time the login session expires
     """
     def __init__(self, uuidval, session_id, provider_id, user,
                  login_session, logoutstate=None, relaystate=None,
-                 logout_request=None, request_id=None):
+                 logout_request=None, request_id=None,
+                 expiration_time=None):
 
         self.uuidval = uuidval
         self.session_id = session_id
@@ -40,6 +61,7 @@ class SAMLSession(Log):
         self.relaystate = relaystate
         self.request_id = request_id
         self.logout_request = logout_request
+        self.expiration_time = expiration_time
 
     def set_logoutstate(self, relaystate=None, request=None, request_id=None):
         """
@@ -76,6 +98,7 @@ class SAMLSession(Log):
         data['relaystate'] = self.relaystate
         data['logout_request'] = self.logout_request
         data['request_id'] = self.request_id
+        data['expiration_time'] = self.expiration_time
 
         return {self.uuidval: data}
 
@@ -111,7 +134,8 @@ class SAMLSessionFactory(Log):
                            data.get('logoutstate'),
                            data.get('relaystate'),
                            data.get('logout_request'),
-                           data.get('request_id'))
+                           data.get('request_id'),
+                           data.get('expiration_time'))
 
     def add_session(self, session_id, provider_id, user, login_session,
                     request_id=None):
@@ -120,11 +144,16 @@ class SAMLSessionFactory(Log):
         """
         self.user = user
 
+        timeout = cherrypy_config['tools.sessions.timeout']
+        t = datetime.timedelta(seconds=timeout * 60)
+        expiration_time = datetime.datetime.now() + t
+
         data = {'session_id': session_id,
                 'provider_id': provider_id,
                 'user': user,
                 'login_session': login_session,
-                'logoutstate': LOGGED_IN}
+                'logoutstate': LOGGED_IN,
+                'expiration_time': expiration_time}
         if request_id:
             data['request_id'] = request_id
 
@@ -132,7 +161,8 @@ class SAMLSessionFactory(Log):
 
         return SAMLSession(uuidval, session_id, provider_id, user,
                            login_session, LOGGED_IN,
-                           request_id=request_id)
+                           request_id=request_id,
+                           expiration_time=expiration_time)
 
     def get_session_by_id(self, session_id):
         """
@@ -254,13 +284,12 @@ class SAMLSessionFactory(Log):
             count += 1
 
 if __name__ == '__main__':
-    import cherrypy
-
     provider1 = "http://127.0.0.10/saml2"
     provider2 = "http://127.0.0.11/saml2"
 
-    # temporary database location for testing
-    cherrypy.config['saml2.sessions.db'] = '/tmp/saml2sessions.sqlite'
+    # temporary values to simulate cherrypy
+    cherrypy_config['saml2.sessions.db'] = '/tmp/saml2sessions.sqlite'
+    cherrypy_config['tools.sessions.timeout'] = 60
 
     factory = SAMLSessionFactory()
     factory.wipe_data()
index 6528fdf..efaf67e 100644 (file)
@@ -8,6 +8,7 @@ from ipsilon.providers.saml2.admin import Saml2AdminPage
 from ipsilon.providers.saml2.rest import Saml2RestBase
 from ipsilon.providers.saml2.provider import IdentityProvider
 from ipsilon.providers.saml2.sessions import SAMLSessionFactory
+from ipsilon.providers.saml2.sessions import expire_sessions
 from ipsilon.tools.certs import Certificate
 from ipsilon.tools import saml2metadata as metadata
 from ipsilon.tools import files
@@ -280,6 +281,9 @@ Provides SAML 2.0 authentication infrastructure. """
             logger.addHandler(lh)
             logger.setLevel(logging.DEBUG)
 
+        bt = cherrypy.process.plugins.BackgroundTask(60, expire_sessions)
+        bt.start()
+
     @property
     def allow_self_registration(self):
         return self.get_config_value('allow self registration')