From 05ba490f7c4185fedbf15b87dc04de346e52a735 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 4 Feb 2015 10:33:07 -0500 Subject: [PATCH] Initial version of session_timeout. Still needs client side work to handle API/web.json requests better but that turned out to be a large project -- see TODOs for more details. --- config/galaxy.ini.sample | 7 +++ lib/galaxy/config.py | 1 + lib/galaxy/model/__init__.py | 3 +- .../migrate/versions/0128_session_timeout.py | 50 +++++++++++++++++++ lib/galaxy/web/framework/webapp.py | 27 ++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 lib/galaxy/model/migrate/versions/0128_session_timeout.py diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index 9f2e39a019e..814ef564443 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -392,6 +392,13 @@ paste.app_factory = galaxy.web.buildapp:app_factory #inactivity_box_content = Your account has not been activated yet. Feel free to browse around and see what's available, but you won't be able to upload data or run jobs until you have verified your email address. + +# Galaxy Session Timeout +# This provides a timeout (in minutes) after which a user will have to log back in. +# A duration of 0 disables this feature. +#session_duration = 0 + + # -- Analytics # You can enter tracking code here to track visitor's behavior diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 91b03492857..4606fad5c8a 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -171,6 +171,7 @@ class Configuration( object ): self.instance_resource_url = kwargs.get( 'instance_resource_url', None ) self.registration_warning_message = kwargs.get( 'registration_warning_message', None ) self.ga_code = kwargs.get( 'ga_code', None ) + self.session_duration = int(kwargs.get( 'session_duration', 0 )) # Get the disposable email domains blacklist file and its contents self.blacklist_location = kwargs.get( 'blacklist_file', None ) self.blacklist_content = None diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index c4853730e7c..037bb94dbb5 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1335,7 +1335,7 @@ class Dataset( object ): FAILED_METADATA = 'failed_metadata', RESUBMITTED = 'resubmitted' ) # failed_metadata and resubmitted are only valid as DatasetInstance states currently - + non_ready_states = ( states.UPLOAD, states.QUEUED, @@ -2997,6 +2997,7 @@ class GalaxySession( object ): self.is_valid = is_valid self.prev_session_id = prev_session_id self.histories = [] + self.last_action = galaxy.model.orm.now.now() def add_history( self, history, association=None ): if association is None: diff --git a/lib/galaxy/model/migrate/versions/0128_session_timeout.py b/lib/galaxy/model/migrate/versions/0128_session_timeout.py new file mode 100644 index 00000000000..ab5d9d154a9 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0128_session_timeout.py @@ -0,0 +1,50 @@ +""" +Migration script to add session update time (used for timeouts) +""" +from sqlalchemy import * +from sqlalchemy.orm import * +from migrate import * +from migrate.changeset import * +from galaxy.model.custom_types import * + +import datetime +now = datetime.datetime.utcnow + +import logging +log = logging.getLogger( __name__ ) + +metadata = MetaData() + + +def upgrade(migrate_engine): + metadata.bind = migrate_engine + print __doc__ + metadata.reflect() + + lastaction_column = Column( "last_action", DateTime, default=now ) + __add_column( lastaction_column, "galaxy_session", metadata ) + + +def downgrade(migrate_engine): + metadata.bind = migrate_engine + metadata.reflect() + + __drop_column( "last_action", "galaxy_session", metadata ) + + +def __add_column(column, table_name, metadata, **kwds): + try: + table = Table( table_name, metadata, autoload=True ) + column.create( table, **kwds ) + except Exception as e: + print str(e) + log.exception( "Adding column %s failed." % column) + + +def __drop_column( column_name, table_name, metadata ): + try: + table = Table( table_name, metadata, autoload=True ) + getattr( table.c, column_name ).drop() + except Exception as e: + print str(e) + log.exception( "Dropping column %s failed." % column_name ) diff --git a/lib/galaxy/web/framework/webapp.py b/lib/galaxy/web/framework/webapp.py index 1688f09ab40..1810afa9f7c 100644 --- a/lib/galaxy/web/framework/webapp.py +++ b/lib/galaxy/web/framework/webapp.py @@ -1,5 +1,6 @@ """ """ +import datetime import inspect import os import hashlib @@ -210,6 +211,32 @@ class GalaxyWebTransaction( base.DefaultWebTransaction, self.response.send_redirect( url_for( '/static/user_disabled.html' ) ) if config.require_login: self._ensure_logged_in_user( environ, session_cookie ) + if config.session_duration and not self.environ.get('is_api_request', False): + # TODO DBTODO Session-based API requests need to be handled + # correctly here. Disabled for now. The issue is that API + # request response error codes aren't handled in a consistent + # way on the client side. All ajax calls from the client need + # to go through a single point of control where we can do things + # like redirect/etc. This is API calls as well as something + # like 40 @web.json requests that might not get handled well on + # the clientside. + # + # Make sure we're not past the duration, and either log out or + # update timestamp. + now = datetime.datetime.now() + expiration_time = self.galaxy_session.update_time + datetime.timedelta(minutes=config.session_duration) + if expiration_time < now: + # Expiration time has passed. + self.handle_user_logout() + self.response.send_redirect( url_for( controller='user', + action='login', + message="You have been logged out due to inactivity. Please log in again to continue using Galaxy.", + status='info', + use_panels=True ) ) + else: + self.galaxy_session.update_time = datetime.datetime.now() + self.sa_session.add(self.galaxy_session) + self.sa_session.flush() def setup_i18n( self ): locales = []