diff --git a/ckan/config/deployment.ini_tmpl b/ckan/config/deployment.ini_tmpl index a3fe8c36b7d..dfbf54ae305 100644 --- a/ckan/config/deployment.ini_tmpl +++ b/ckan/config/deployment.ini_tmpl @@ -40,7 +40,9 @@ app_instance_uuid = ${app_instance_uuid} who.config_file = %(here)s/who.ini who.log_level = warning who.log_file = %(cache_dir)s/who_log.ini - +# Session timeout (user logged out after period of inactivity, in seconds). +# Inactive by default, so the session doesn't expire. +# who.timeout = 86400 ## Database Settings sqlalchemy.url = postgresql://ckan_default:pass@localhost/ckan_default diff --git a/ckan/config/middleware.py b/ckan/config/middleware.py index 4ddf9d87dd5..4bd3f4f760f 100644 --- a/ckan/config/middleware.py +++ b/ckan/config/middleware.py @@ -30,6 +30,7 @@ log = logging.getLogger(__name__) + def make_app(conf, full_stack=True, static_files=True, **app_conf): """Create a Pylons WSGI application and return it @@ -126,22 +127,23 @@ def make_app(conf, full_stack=True, static_files=True, **app_conf): OpenIdIdentificationPlugin._redirect_to_loginform = repoze_patch._redirect_to_loginform OpenIdIdentificationPlugin.challenge = repoze_patch.challenge - who_parser.identifiers = [i for i in who_parser.identifiers if \ - not isinstance(i, OpenIdIdentificationPlugin)] - who_parser.challengers = [i for i in who_parser.challengers if \ - not isinstance(i, OpenIdIdentificationPlugin)] - - app = PluggableAuthenticationMiddleware(app, - who_parser.identifiers, - who_parser.authenticators, - who_parser.challengers, - who_parser.mdproviders, - who_parser.request_classifier, - who_parser.challenge_decider, - logging.getLogger('repoze.who'), - logging.WARN, # ignored - who_parser.remote_user_key, - ) + who_parser.identifiers = [i for i in who_parser.identifiers if + not isinstance(i, OpenIdIdentificationPlugin)] + who_parser.challengers = [i for i in who_parser.challengers if + not isinstance(i, OpenIdIdentificationPlugin)] + + app = PluggableAuthenticationMiddleware( + app, + who_parser.identifiers, + who_parser.authenticators, + who_parser.challengers, + who_parser.mdproviders, + who_parser.request_classifier, + who_parser.challenge_decider, + logging.getLogger('repoze.who'), + logging.WARN, # ignored + who_parser.remote_user_key + ) # Establish the Registry for this application app = RegistryManager(app) @@ -154,7 +156,7 @@ def make_app(conf, full_stack=True, static_files=True, **app_conf): else int(config.get('ckan.static_max_age', 3600)) static_app = StaticURLParser(config['pylons.paths']['static_files'], - cache_max_age=static_max_age) + cache_max_age=static_max_age) static_parsers = [static_app, app] storage_directory = uploader.get_storage_path() @@ -167,8 +169,7 @@ def make_app(conf, full_stack=True, static_files=True, **app_conf): if e.errno != 17: raise - storage_app = StaticURLParser(path, - cache_max_age=static_max_age) + storage_app = StaticURLParser(path, cache_max_age=static_max_age) static_parsers.insert(0, storage_app) # Configurable extra static file paths @@ -177,7 +178,7 @@ def make_app(conf, full_stack=True, static_files=True, **app_conf): if public_path.strip(): extra_static_parsers.append( StaticURLParser(public_path.strip(), - cache_max_age=static_max_age) + cache_max_age=static_max_age) ) app = Cascade(extra_static_parsers + static_parsers) diff --git a/ckan/config/who.ini b/ckan/config/who.ini index bd7642e0541..913f0e72fbe 100644 --- a/ckan/config/who.ini +++ b/ckan/config/who.ini @@ -59,4 +59,3 @@ plugins = openid friendlyform;browser # basicauth - diff --git a/ckan/lib/auth_tkt.py b/ckan/lib/auth_tkt.py index cc68a4dde55..526d5e26f7a 100644 --- a/ckan/lib/auth_tkt.py +++ b/ckan/lib/auth_tkt.py @@ -1,3 +1,4 @@ +import math import os from pylons import config @@ -41,13 +42,19 @@ def make_plugin(secret=None, userid_checker=None): from repoze.who.utils import resolveDotted - # ckan specific: get secret from beaker setting if necessary + # ckan specifics: + # Get secret from beaker setting if necessary if secret is None or secret == 'somesecret': secret = config['beaker.session.secret'] - + # Session timeout and reissue time for auth cookie + if timeout is None and config.get('who.timeout'): + timeout = config.get('who.timeout') + if reissue_time is None and config.get('who.reissue_time'): + reissue_time = config.get('who.reissue_time') + if timeout is not None and reissue_time is None: + reissue_time = int(math.ceil(int(timeout) * 0.1)) # Set httponly based on config value. Default is True httponly = config.get('who.httponly', True) - # Set secure based on config value. Default is False secure = config.get('who.secure', False) diff --git a/ckan/new_tests/config/__init__.py b/ckan/new_tests/config/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ckan/new_tests/lib/test_auth_tkt.py b/ckan/new_tests/lib/test_auth_tkt.py index 6e729fd0ab4..644f23f6f56 100644 --- a/ckan/new_tests/lib/test_auth_tkt.py +++ b/ckan/new_tests/lib/test_auth_tkt.py @@ -1,24 +1,18 @@ +from nose import tools as nose_tools + from ckan.new_tests import helpers -from ckan.lib.auth_tkt import CkanAuthTktCookiePlugin, make_plugin +from ckan.lib.auth_tkt import make_plugin -class TestCkanAuthTktCookiePlugin(object): +class TestCkanAuthTktCookiePlugin(helpers.FunctionalTestBase): ''' Test the added methods used by this subclass of repoze.who.plugins.auth_tkt.AuthTktCookiePlugin - ''' - def _make_plugin(self, httponly): - '''Only httponly needs to be set.''' - return CkanAuthTktCookiePlugin(httponly=httponly, - secret=None, - cookie_name='auth_tkt', - secure=False, - include_ip=False, - timeout=None, - reissue_time=None, - userid_checker=None) + Subclassing FunctionalTestBase ensures the original config is restored + after each test. + ''' @helpers.change_config('who.httponly', True) def test_httponly_expected_cookies_with_config_httponly_true(self): @@ -109,3 +103,36 @@ def test_secure_expected_cookies_without_config_secure(self): ('Set-Cookie', 'auth_tkt="HELLO"; Path=/; Domain=.0.0.0.0; HttpOnly') ] assert cookies == expected_cookies + + def test_timeout_not_set_in_config(self): + ''' + Creating a CkanAuthTktCookiePlugin instance without setting timeout in + config sets correct values in CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, None) + nose_tools.assert_equal(plugin.reissue_time, None) + + @helpers.change_config('who.timeout', 9000) + def test_timeout_set_in_config(self): + ''' + Setting who.timeout in config sets correct values in + CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, 9000) + nose_tools.assert_equal(plugin.reissue_time, 900) + + @helpers.change_config('who.timeout', 9000) + @helpers.change_config('who.reissue_time', 200) + def test_reissue_set_in_config(self): + ''' + Setting who.reissue in config sets correct values in + CkanAuthTktCookiePlugin instance. + ''' + plugin = make_plugin(secret='sosecret') + + nose_tools.assert_equal(plugin.timeout, 9000) + nose_tools.assert_equal(plugin.reissue_time, 200) diff --git a/doc/maintaining/configuration.rst b/doc/maintaining/configuration.rst index c7e3a3a6544..dfa0f67db1b 100644 --- a/doc/maintaining/configuration.rst +++ b/doc/maintaining/configuration.rst @@ -63,6 +63,21 @@ files, and enables CKAN templates' debugging features. Repoze.who Settings ------------------- +.. _who.timeout: + +who.timeout +^^^^^^^^^^^ + +Example:: + + who.timeout = 3600 + +Default value: None + +This defines how long (in seconds) until a user is logged out after a period +of inactivity. If the setting isn't defined, the session doesn't expire. Not +active by default. + .. _who.httponly: who.httponly