Mercurial > p > roundup > code
diff roundup/cgi/client.py @ 3989:0112e9e1d068
improvements to session management
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Mon, 18 Aug 2008 05:04:02 +0000 |
| parents | 57ad3e2c2545 |
| children | 39ad32d47cfb |
line wrap: on
line diff
--- a/roundup/cgi/client.py Thu Aug 07 22:02:30 2008 +0000 +++ b/roundup/cgi/client.py Mon Aug 18 05:04:02 2008 +0000 @@ -1,4 +1,4 @@ -# $Id: client.py,v 1.238 2007-09-22 21:20:57 jpend Exp $ +# $Id: client.py,v 1.239 2008-08-18 05:04:02 richard Exp $ """WWW request handler (also used in the stand-alone server). """ @@ -88,6 +88,112 @@ dict.__setitem__(self, key, None) +class Session: + ''' + Needs DB to be already opened by client + + Session attributes at instantiation: + + - "client" - reference to client for add_cookie function + - "session_db" - session DB manager + - "cookie_name" - name of the cookie with session id + - "_sid" - session id for current user + - "_data" - session data cache + + session = Session(client) + session.set(name=value) + value = session.get(name) + + session.destroy() # delete current session + session.clean_up() # clean up session table + + session.update(set_cookie=True, expire=3600*24*365) + # refresh session expiration time, setting persistent + # cookie if needed to last for 'expire' seconds + + ''' + + def __init__(self, client): + self._data = {} + self._sid = None + + self.client = client + self.session_db = client.db.getSessionManager() + + # parse cookies for session id + self.cookie_name = 'roundup_session_%s' % \ + re.sub('[^a-zA-Z]', '', client.instance.config.TRACKER_NAME) + cookies = LiberalCookie(client.env.get('HTTP_COOKIE', '')) + if self.cookie_name in cookies: + if not self.session_db.exists(cookies[self.cookie_name].value): + self._sid = None + # remove old cookie + self.client.add_cookie(self.cookie_name, None) + else: + self._sid = cookies[self.cookie_name].value + self._data = self.session_db.getall(self._sid) + + def _gen_sid(self): + ''' generate a unique session key ''' + while 1: + s = '%s%s'%(time.time(), random.random()) + s = binascii.b2a_base64(s).strip() + if not self.session_db.exists(s): + break + + # clean up the base64 + if s[-1] == '=': + if s[-2] == '=': + s = s[:-2] + else: + s = s[:-1] + return s + + def clean_up(self): + '''Remove expired sessions''' + self.session_db.clean() + + def destroy(self): + self.client.add_cookie(self.cookie_name, None) + self._data = {} + self.session_db.destroy(self._sid) + self.client.db.commit() + + def get(self, name, default=None): + return self._data.get(name, default) + + def set(self, **kwargs): + self._data.update(kwargs) + if not self._sid: + self._sid = self._gen_sid() + self.session_db.set(self._sid, **self._data) + # add session cookie + self.update(set_cookie=True) + + # XXX added when patching 1.4.4 for backward compatibility + # XXX remove + self.client.session = self._sid + else: + self.session_db.set(self._sid, **self._data) + self.client.db.commit() + + def update(self, set_cookie=False, expire=None): + ''' update timestamp in db to avoid expiration + + if 'set_cookie' is True, set cookie with 'expire' seconds lifetime + if 'expire' is None - session will be closed with the browser + + XXX the session can be purged within a week even if a cookie + lifetime is longer + ''' + self.session_db.updateTimestamp(self._sid) + self.client.db.commit() + + if set_cookie: + self.client.add_cookie(self.cookie_name, self._sid, expire=expire) + + + class Client: '''Instantiate to handle one CGI request. @@ -106,9 +212,11 @@ During the processing of a request, the following attributes are used: + - "db" - "error_message" holds a list of error messages - "ok_message" holds a list of OK messages - - "session" is the current user session id + - "session" is deprecated in favor of session_api (XXX remove) + - "session_api" is the interface to store data in session - "user" is the current user's name - "userid" is the current user's id - "template" is the current :template context @@ -116,12 +224,12 @@ - "nodeid" is the current context item id User Identification: - If the user has no login cookie, then they are anonymous and are logged + Users that are absent in session data are anonymous and are logged in as that user. This typically gives them all Permissions assigned to the Anonymous Role. - Once a user logs in, they are assigned a session. The Client instance - keeps the nodeid of the session as the "session" attribute. + Every user is assigned a session. "session_api" is the interface to work + with session data. Special form variables: Note that in various places throughout this code, special form @@ -186,11 +294,9 @@ # this is the "cookie path" for this tracker (ie. the path part of # the "base" url) self.cookie_path = urlparse.urlparse(self.base)[2] - self.cookie_name = 'roundup_session_' + re.sub('[^a-zA-Z]', '', - self.instance.config.TRACKER_NAME) # cookies to set in http responce # {(path, name): (value, expire)} - self.add_cookies = {} + self._cookies = {} # see if we need to re-parse the environment for the form (eg Zope) if form is None: @@ -216,7 +322,7 @@ # default character set self.charset = self.STORAGE_CHARSET - # parse cookies (used in charset and session lookups) + # parse cookies (used for charset lookups) # use our own LiberalCookie to handle bad apps on the same # server that have set cookies that are out of spec self.cookie = LiberalCookie(self.env.get('HTTP_COOKIE', '')) @@ -384,25 +490,28 @@ return self.write_html(self._(error_message)) def clean_sessions(self): - """Age sessions, remove when they haven't been used for a week. - - Do it only once an hour. - - Note: also cleans One Time Keys, and other "session" based stuff. + """Deprecated + XXX remove """ - sessions = self.db.getSessionManager() - last_clean = sessions.get('last_clean', 'last_use', 0) + self.clean_up() - # time to clean? - #week = 60*60*24*7 + def clean_up(self): + """Remove expired sessions and One Time Keys. + + Do it only once an hour. + """ hour = 60*60 now = time.time() + + # XXX: hack - use OTK table to store last_clean time information + # 'last_clean' string is used instead of otk key + last_clean = self.db.getOTKManager().get('last_clean', 'last_use', 0) if now - last_clean < hour: return - sessions.clean(now) - self.db.getOTKManager().clean(now) - sessions.set('last_clean', last_use=time.time()) + self.session_api.clean_up() + self.db.getOTKManager().clean() + self.db.getOTKManager().set('last_clean', last_use=now) self.db.commit(fail_ok=True) def determine_charset(self): @@ -495,9 +604,12 @@ """Determine who the user is""" self.opendb('admin') - # make sure we have the session Class - self.clean_sessions() - sessions = self.db.getSessionManager() + # get session data from db + # XXX: rename + self.session_api = Session(self) + + # take the opportunity to cleanup expired sessions and otks + self.clean_up() user = None # first up, try http authorization if enabled @@ -526,22 +638,14 @@ user = username - # if user was not set by http authorization, try session cookie - if (not user and self.cookie.has_key(self.cookie_name) - and (self.cookie[self.cookie_name].value != 'deleted')): - # get the session key from the cookie - self.session = self.cookie[self.cookie_name].value - # get the user from the session - try: - # update the lifetime datestamp - sessions.updateTimestamp(self.session) - self.db.commit() - user = sessions.get(self.session, 'user') - except KeyError: - # not valid, ignore id - pass + # if user was not set by http authorization, try session lookup + if not user: + user = self.session_api.get('user') + if user: + # update session lifetime datestamp + self.session_api.update() - # if no user name set by http authorization or session cookie + # if no user name set by http authorization or session lookup # the user is anonymous if not user: user = 'anonymous' @@ -954,7 +1058,7 @@ headers = headers.items() - for ((path, name), (value, expire)) in self.add_cookies.items(): + for ((path, name), (value, expire)) in self._cookies.items(): cookie = "%s=%s; Path=%s;"%(name, value, path) if expire is not None: cookie += " expires=%s;"%Cookie._getdate(expire) @@ -989,36 +1093,18 @@ path = self.cookie_path if not value: expire = -1 - self.add_cookies[(path, name)] = (value, expire) + self._cookies[(path, name)] = (value, expire) def set_cookie(self, user, expire=None): - """Set up a session cookie for the user. - - Also store away the user's login info against the session. - """ - sessions = self.db.getSessionManager() + """Deprecated. Use session_api calls directly - # generate a unique session key - while 1: - s = '%s%s'%(time.time(), random.random()) - s = binascii.b2a_base64(s).strip() - if not sessions.exists(s): - break - self.session = s + XXX remove + """ - # clean up the base64 - if self.session[-1] == '=': - if self.session[-2] == '=': - self.session = self.session[:-2] - else: - self.session = self.session[:-1] - - # insert the session in the sessiondb - sessions.set(self.session, user=user) - self.db.commit() - - # add session cookie - self.add_cookie(self.cookie_name, self.session, expire=expire) + # insert the session in the session db + self.session_api.set(user=user) + # refresh session cookie + self.session_api.update(set_cookie=True, expire=expire) def make_user_anonymous(self): ''' Make us anonymous
