From: Rob Crittenden Date: Mon, 11 May 2015 22:14:42 +0000 (-0400) Subject: Use plugin-specific configuration, better expiration X-Git-Tag: v1.0.0~7 X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=commitdiff_plain;h=8445b3297cd0b25989f2575c21bf3426aee7c5ad Use plugin-specific configuration, better expiration Use a SAML2 plugin specific option to specify the database uri for sessions. Use a much more robust method to find sessions that need expiration (thanks Patrick). https://fedorahosted.org/ipsilon/ticket/90 Signed-off-by: Rob Crittenden Reviewed-by: Patrick Uiterwijk --- diff --git a/ipsilon/providers/saml2/auth.py b/ipsilon/providers/saml2/auth.py index 495e5a9..c46d604 100644 --- a/ipsilon/providers/saml2/auth.py +++ b/ipsilon/providers/saml2/auth.py @@ -5,7 +5,6 @@ from ipsilon.providers.common import AuthenticationError, InvalidRequest from ipsilon.providers.saml2.provider import ServiceProvider from ipsilon.providers.saml2.provider import InvalidProviderId from ipsilon.providers.saml2.provider import NameIdNotAllowed -from ipsilon.providers.saml2.sessions import SAMLSessionFactory from ipsilon.tools import saml2metadata as metadata from ipsilon.util.policy import Policy from ipsilon.util.user import UserSession @@ -275,7 +274,7 @@ class AuthenticateRequest(ProviderPageBase): self.debug('Assertion: %s' % login.assertion.dump()) - saml_sessions = SAMLSessionFactory() + saml_sessions = self.cfg.idp.sessionfactory lasso_session = lasso.Session() lasso_session.addAssertion(login.remoteProviderId, login.assertion) diff --git a/ipsilon/providers/saml2/logout.py b/ipsilon/providers/saml2/logout.py index d20370a..cc9b777 100644 --- a/ipsilon/providers/saml2/logout.py +++ b/ipsilon/providers/saml2/logout.py @@ -2,7 +2,6 @@ from ipsilon.providers.common import ProviderPageBase from ipsilon.providers.common import InvalidRequest -from ipsilon.providers.saml2.sessions import SAMLSessionFactory from ipsilon.providers.saml2.auth import UnknownProvider from ipsilon.util.user import UserSession import cherrypy @@ -204,7 +203,7 @@ class LogoutRequest(ProviderPageBase): us = UserSession() - saml_sessions = SAMLSessionFactory() + saml_sessions = self.cfg.idp.sessionfactory if lasso.SAML2_FIELD_REQUEST in message: self._handle_logout_request(us, logout, saml_sessions, message) diff --git a/ipsilon/providers/saml2/provider.py b/ipsilon/providers/saml2/provider.py index c8425bb..3dea631 100644 --- a/ipsilon/providers/saml2/provider.py +++ b/ipsilon/providers/saml2/provider.py @@ -266,12 +266,13 @@ class ServiceProviderCreator(object): class IdentityProvider(Log): - def __init__(self, config): + def __init__(self, config, sessionfactory): self.server = lasso.Server(config.idp_metadata_file, config.idp_key_file, None, config.idp_certificate_file) self.server.role = lasso.PROVIDER_ROLE_IDP + self.sessionfactory = sessionfactory def add_provider(self, sp): self.server.addProviderFromBuffer(lasso.PROVIDER_ROLE_SP, diff --git a/ipsilon/providers/saml2/sessions.py b/ipsilon/providers/saml2/sessions.py index 6b3d860..1000a87 100644 --- a/ipsilon/providers/saml2/sessions.py +++ b/ipsilon/providers/saml2/sessions.py @@ -11,23 +11,6 @@ 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. @@ -118,8 +101,8 @@ class SAMLSessionFactory(Log): Returns a SAMLSession object representing the new session. """ - def __init__(self): - self._ss = SAML2SessionStore() + def __init__(self, database_url): + self._ss = SAML2SessionStore(database_url=database_url) self.user = None def _data_to_samlsession(self, uuidval, data): @@ -288,10 +271,9 @@ if __name__ == '__main__': provider2 = "http://127.0.0.11/saml2" # temporary values to simulate cherrypy - cherrypy_config['saml2.sessions.db'] = '/tmp/saml2sessions.sqlite' cherrypy_config['tools.sessions.timeout'] = 60 - factory = SAMLSessionFactory() + factory = SAMLSessionFactory('/tmp/saml2sessions.sqlite') factory.wipe_data() sess1 = factory.add_session('_123456', provider1, "admin", "") diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py index efaf67e..f771ef7 100644 --- a/ipsilon/providers/saml2idp.py +++ b/ipsilon/providers/saml2idp.py @@ -8,7 +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.util.data import SAML2SessionStore from ipsilon.tools.certs import Certificate from ipsilon.tools import saml2metadata as metadata from ipsilon.tools import files @@ -215,6 +215,7 @@ class IdpProvider(ProviderBase): self.rest = None self.page = None self.idp = None + self.sessionfactory = None self.description = """ Provides SAML 2.0 authentication infrastructure. """ @@ -272,6 +273,10 @@ Provides SAML 2.0 authentication infrastructure. """ 'default allowed attributes', 'Defines a list of allowed attributes, applied after mapping', ['*']), + pconfig.String( + 'session database url', + 'Database URL for SAML2 sessions', + 'saml2.sessions.db.sqlite'), ) if cherrypy.config.get('debug', False): import logging @@ -281,7 +286,12 @@ Provides SAML 2.0 authentication infrastructure. """ logger.addHandler(lh) logger.setLevel(logging.DEBUG) - bt = cherrypy.process.plugins.BackgroundTask(60, expire_sessions) + store = SAML2SessionStore( + database_url=self.get_config_value('session database url') + ) + bt = cherrypy.process.plugins.BackgroundTask( + 60, store.remove_expired_sessions + ) bt.start() @property @@ -344,9 +354,13 @@ Provides SAML 2.0 authentication infrastructure. """ def init_idp(self): idp = None + self.sessionfactory = SAMLSessionFactory( + database_url=self.get_config_value('session database url') + ) # Init IDP data try: - idp = IdentityProvider(self) + idp = IdentityProvider(self, + sessionfactory=self.sessionfactory) except Exception, e: # pylint: disable=broad-except self.debug('Failed to init SAML2 provider: %r' % e) return None @@ -385,7 +399,7 @@ Provides SAML 2.0 authentication infrastructure. """ us = UserSession() user = us.get_user() - saml_sessions = SAMLSessionFactory() + saml_sessions = self.sessionfactory session = saml_sessions.get_next_logout() if session is None: return @@ -459,6 +473,8 @@ class Installer(ProviderInstaller): help=('Metadata validity period in days ' '(default - %d)' % METADATA_DEFAULT_VALIDITY_PERIOD)) + group.add_argument('--saml2-session-dburl', + help='session database URL') def configure(self, opts, changes): if opts['saml2'] != 'yes': @@ -497,7 +513,11 @@ class Installer(ProviderInstaller): 'idp certificate file': cert.cert, 'idp key file': cert.key, 'idp nameid salt': uuid.uuid4().hex, - 'idp metadata validity': opts['saml2_metadata_validity']} + 'idp metadata validity': opts['saml2_metadata_validity'], + 'session database url': opts['saml2_session_dburl'] or + opts['database_url'] % { + 'datadir': opts['data_dir'], + 'dbname': 'saml2.sessions.db'}} po.save_plugin_config(config) # Update global config to add login plugin diff --git a/ipsilon/util/data.py b/ipsilon/util/data.py index f90519d..53a1756 100644 --- a/ipsilon/util/data.py +++ b/ipsilon/util/data.py @@ -1,11 +1,12 @@ # Copyright (C) 2013 Ipsilon project Contributors, for license see COPYING import cherrypy +import datetime from ipsilon.util.log import Log from sqlalchemy import create_engine from sqlalchemy import MetaData, Table, Column, Text from sqlalchemy.pool import QueuePool, SingletonThreadPool -from sqlalchemy.sql import select +from sqlalchemy.sql import select, and_ import ConfigParser import os import uuid @@ -513,9 +514,12 @@ class TranStore(Store): class SAML2SessionStore(Store): - def __init__(self, path=None): - super(SAML2SessionStore, self).__init__('saml2.sessions.db') + def __init__(self, database_url): + super(SAML2SessionStore, self).__init__(database_url=database_url) self.table = 'sessions' + # pylint: disable=protected-access + table = SqlQuery(self._db, self.table, UNIQUE_DATA_COLUMNS)._table + table.create(checkfirst=True) def _get_unique_id_from_column(self, name, value): """ @@ -533,6 +537,16 @@ class SAML2SessionStore(Store): raise ValueError("Multiple entries returned") return data.keys()[0] + def remove_expired_sessions(self): + # pylint: disable=protected-access + table = SqlQuery(self._db, self.table, UNIQUE_DATA_COLUMNS)._table + sel = select([table.columns.uuid]). \ + where(and_(table.c.name == 'expiration_time', + table.c.value <= datetime.datetime.now())) + # pylint: disable=no-value-for-parameter + d = table.delete().where(table.c.uuid.in_(sel)) + d.execute() + def get_data(self, idval=None, name=None, value=None): return self.get_unique_data(self.table, idval, name, value) diff --git a/templates/install/ipsilon.conf b/templates/install/ipsilon.conf index c688f0b..b57aa55 100644 --- a/templates/install/ipsilon.conf +++ b/templates/install/ipsilon.conf @@ -10,7 +10,6 @@ base.dir = "${staticdir}" admin.config.db = "${admindb}" user.prefs.db = "${usersdb}" transactions.db = "${transdb}" -saml2.sessions.db = "${samlsessionsdb}" tools.sessions.on = True tools.sessions.name = "${instance}_ipsilon_session_id" diff --git a/tests/helpers/common.py b/tests/helpers/common.py index 93d0f17..cbd516b 100755 --- a/tests/helpers/common.py +++ b/tests/helpers/common.py @@ -187,7 +187,7 @@ class IpsilonTestBase(object): self.processes.append(p) p.wait() for d in ['adminconfig', 'users', 'transactions', 'sessions', - 'saml2sessions']: + 'saml2.sessions.db']: cmd = ['/usr/bin/createdb', '-h', addr, '-p', port, d] subprocess.check_call(cmd, env=env)