diff options
author | John Dennis <jdennis@redhat.com> | 2012-02-19 10:02:38 -0500 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2012-02-27 05:55:15 -0500 |
commit | 059a90702e454b99490031bd37541304e65d35d2 (patch) | |
tree | 4b0c896c19cbac6c3f15e9fabfe1a7558b1c5f94 /ipaserver/rpcserver.py | |
parent | 9753fd423059e8d5725ead9a90a7cf1b9e0b9b85 (diff) | |
download | freeipa-059a90702e454b99490031bd37541304e65d35d2.tar.gz freeipa-059a90702e454b99490031bd37541304e65d35d2.tar.xz freeipa-059a90702e454b99490031bd37541304e65d35d2.zip |
Implement session activity timeout
Previously sessions expired after session_auth_duration had elapsed
commencing from the start of the session. We new support a "rolling"
expiration where the expiration is advanced by session_auth_duration
everytime the session is accessed, this is equivalent to a inactivity
timeout. The expiration is still constrained by the credential
expiration in all cases. The session expiration behavior is
configurable based on the session_auth_duration_type.
* Reduced the default session_auth_duration from 1 hour to 20 minutes.
* Replaced the sesssion write_timestamp with the access_timestamp and
update the access_timestamp whenever the session data is created,
retrieved, or written.
* Modify set_session_expiration_time to handle both an inactivity
timeout and a fixed duration.
* Introduce KerberosSession as a mixin class to share session
duration functionality with all classes manipulating session data
with Kerberos auth. This is both the non-RPC login class and the RPC
classes.
* Update make-lint to handle new classes.
* Added session_auth_duration_type config item.
* Updated default.conf.5 man page for new session_auth_duration_type item.
* Removed these unused config items: mount_xmlserver,
mount_jsonserver, webui_assets_dir
https://fedorahosted.org/freeipa/ticket/2392
Diffstat (limited to 'ipaserver/rpcserver.py')
-rw-r--r-- | ipaserver/rpcserver.py | 96 |
1 files changed, 71 insertions, 25 deletions
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index d0bb605f5..0b8aa4088 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -32,7 +32,7 @@ from ipalib.request import context, Connection, destroy_context from ipalib.rpc import xml_dumps, xml_loads from ipalib.util import make_repr, parse_time_duration from ipapython.compat import json -from ipalib.session import session_mgr, AuthManager, read_krbccache_file, store_krbccache_file, delete_krbccache_file, fmt_time, default_max_session_lifetime +from ipalib.session import session_mgr, AuthManager, read_krbccache_file, store_krbccache_file, delete_krbccache_file, fmt_time, default_max_session_duration from ipalib.backend import Backend from ipalib.krb_utils import krb5_parse_ccache, KRB5_CCache, krb_ticket_expiration_threshold from wsgiref.util import shift_path_info @@ -566,7 +566,60 @@ class AuthManagerKerb(AuthManager): self.error('AuthManager.logout.%s: session_data does not contain ccache_data', self.name) -class jsonserver_session(jsonserver): +class KerberosSession(object): + ''' + Functionally shared by all RPC handlers using both sessions and + Kerberos. This class must be implemented as a mixin class rather + than the more obvious technique of subclassing because the classes + needing this do not share a common base class. + ''' + + def kerb_session_on_finalize(self): + ''' + Initialize values from the Env configuration. + + Why do it this way and not simply reference + api.env.session_auth_duration? Because that config item cannot + be used directly, it must be parsed and converted to an + integer. It would be inefficient to reparse it on every + request. So we parse it once and store the result in the class + instance. + ''' + # Set the session expiration time + try: + seconds = parse_time_duration(self.api.env.session_auth_duration) + self.session_auth_duration = int(seconds) + self.debug("session_auth_duration: %s", datetime.timedelta(seconds=self.session_auth_duration)) + except Exception, e: + self.session_auth_duration = default_max_session_duration + self.error('unable to parse session_auth_duration, defaulting to %d: %s', + self.session_auth_duration, e) + + def update_session_expiration(self, session_data, krb_endtime): + ''' + Each time a session is created or accessed we need to update + it's expiration time. The expiration time is set inside the + session_data. + + :parameters: + session_data + The session data whose expiration is being updatded. + krb_endtime + The UNIX timestamp for when the Kerberos credentials expire. + :returns: + None + ''' + + # Account for clock skew and/or give us some time leeway + krb_expiration = krb_endtime - krb_ticket_expiration_threshold + + # Set the session expiration time + session_mgr.set_session_expiration_time(session_data, + duration=self.session_auth_duration, + max_age=krb_expiration, + duration_type=self.api.env.session_duration_type) + +class jsonserver_session(jsonserver, KerberosSession): """ JSON RPC server protected with session auth. """ @@ -578,6 +631,10 @@ class jsonserver_session(jsonserver): auth_mgr = AuthManagerKerb(self.__class__.__name__) session_mgr.auth_mgr.register(auth_mgr.name, auth_mgr) + def _on_finalize(self): + super(jsonserver_session, self)._on_finalize() + self.kerb_session_on_finalize() + def need_login(self, start_response): status = '401 Unauthorized' headers = [] @@ -598,10 +655,10 @@ class jsonserver_session(jsonserver): session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE')) session_id = session_data['session_id'] - self.debug('jsonserver_session.__call__: session_id=%s start_timestamp=%s write_timestamp=%s expiration_timestamp=%s', + self.debug('jsonserver_session.__call__: session_id=%s start_timestamp=%s access_timestamp=%s expiration_timestamp=%s', session_id, fmt_time(session_data['session_start_timestamp']), - fmt_time(session_data['session_write_timestamp']), + fmt_time(session_data['session_access_timestamp']), fmt_time(session_data['session_expiration_timestamp'])) ccache_data = session_data.get('ccache_data') @@ -620,6 +677,10 @@ class jsonserver_session(jsonserver): delete_krbccache_file(krbccache_pathname) return self.need_login(start_response) + # Update the session expiration based on the Kerberos expiration + endtime = cc.endtime(self.api.env.host, self.api.env.realm) + self.update_session_expiration(session_data, endtime) + # Store the session data in the per-thread context setattr(context, 'session_data', session_data) @@ -676,7 +737,7 @@ class jsonserver_kerb(jsonserver): return response -class krblogin(Backend): +class krblogin(Backend, KerberosSession): key = '/login' def __init__(self): @@ -685,17 +746,7 @@ class krblogin(Backend): def _on_finalize(self): super(krblogin, self)._on_finalize() self.api.Backend.wsgi_dispatch.mount(self, self.key) - - # Set the session expiration time - try: - seconds = parse_time_duration(self.api.env.session_auth_duration) - self.session_auth_duration = int(seconds) - self.debug("session_auth_duration: %s", datetime.timedelta(seconds=self.session_auth_duration)) - except Exception, e: - self.session_auth_duration = default_max_session_lifetime - self.error('unable to parse session_auth_duration, defaulting to %d: %s', - self.session_auth_duration, e) - + self.kerb_session_on_finalize() def __call__(self, environ, start_response): headers = [] @@ -720,17 +771,10 @@ class krblogin(Backend): # Copy the ccache file contents into the session data session_data['ccache_data'] = read_krbccache_file(ccache_location) - # Compute when the session will expire + # Set when the session will expire cc = KRB5_CCache(ccache) endtime = cc.endtime(self.api.env.host, self.api.env.realm) - - # Account for clock skew and/or give us some time leeway - krb_expiration = endtime - krb_ticket_expiration_threshold - - # Set the session expiration time - session_mgr.set_session_expiration_time(session_data, - lifetime=self.session_auth_duration, - max_age=krb_expiration) + self.update_session_expiration(session_data, endtime) # Store the session data now that it's been updated with the ccache session_mgr.store_session_data(session_data) @@ -747,3 +791,5 @@ class krblogin(Backend): start_response(status, headers) return [response] + + |