1 #!/usr/bin/env python 2 3 """ 4 Session helper functions. 5 6 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 """ 22 23 import shelve 24 import random 25 import sys 26 from WebStack.Repositories.Directory import DirectoryRepository 27 28 class SessionStore: 29 30 "A class representing a session store." 31 32 def __init__(self, trans, session_directory, session_cookie_name="SID", delay=1): 33 34 """ 35 Initialise the session store, specifying the transaction 'trans' within 36 which all session access will occur, a base 'session_directory', and the 37 optional 'session_cookie_name' where the session identifier is held for 38 each user. 39 40 The optional 'delay' argument specifies the time in seconds between each 41 poll of the session file when that file is found to be locked for 42 editing. (This is part of the underlying repository's behaviour.) 43 """ 44 45 self.trans = trans 46 self.session_cookie_name = session_cookie_name 47 self.repository = DirectoryRepository(session_directory, delay=delay) 48 49 # Internal state. 50 51 self.store = None 52 self.current_session_id = None 53 self.to_expire = None 54 55 def close(self): 56 57 "Close the store, tidying up files and filenames." 58 59 if self.store is not None: 60 self.store.close() 61 self.store = None 62 if self.current_session_id is not None: 63 self.repository.unlock(self.current_session_id) 64 self.current_session_id = None 65 66 # Handle expiry appropriately. 67 68 if self.to_expire is not None: 69 self._expire_session(self.to_expire) 70 self.trans.delete_cookie(self.session_cookie_name) 71 72 def expire_session(self): 73 74 """ 75 Expire the session in the given transaction. 76 """ 77 78 # Perform expiry. 79 80 cookie = self.trans.get_cookie(self.session_cookie_name) 81 if cookie: 82 self.to_expire = cookie.value 83 84 def _expire_session(self, session_id): 85 86 "Expire the session with the given 'session_id'." 87 88 del self.repository[session_id] 89 90 def get_session(self, create): 91 92 """ 93 Get the session for the given transaction, creating a new session if 94 'create' is set to 1 (rather than 0). Where new sessions are created, an 95 appropriate session identifier cookie will be created. 96 Returns a session object or None if no session exists and none is then 97 created. 98 """ 99 100 if self.store is not None: 101 return self.store 102 103 # No existing store - get the token and the actual session. 104 105 cookie = self.trans.get_cookie(self.session_cookie_name) 106 if cookie: 107 return self._get_session(cookie.value, create) 108 elif create: 109 session_id = self._get_session_identifier() 110 self.trans.set_cookie_value(self.session_cookie_name, session_id) 111 return self._get_session(session_id, create) 112 else: 113 return None 114 115 def _get_session(self, session_id, create): 116 117 """ 118 Get a session with the given 'session_id' and whether new sessions 119 should be created ('create' set to 1). 120 Returns a dictionary-like object representing the session. 121 """ 122 123 try: 124 store_filename = self.repository.lock(session_id, create=create, opener=shelve.open) 125 self.current_session_id = session_id 126 except KeyError: 127 return None 128 129 self.store = shelve.open(store_filename) 130 return Wrapper(self.store) 131 132 def _get_session_identifier(self): 133 134 "Return a session identifier as a string." 135 136 g = random.Random() 137 return str(g.randint(0, sys.maxint - 1)) 138 139 class Wrapper: 140 141 "A wrapper around shelf objects." 142 143 def __init__(self, store): 144 self.store = store 145 146 def __getattr__(self, name): 147 if hasattr(self.store, name): 148 return getattr(self.store, name) 149 else: 150 raise AttributeError, name 151 152 def __getitem__(self, name): 153 # Convert to UTF-8 to avoid bsddb limitations. 154 return self.store[name.encode("utf-8")] 155 156 def __delitem__(self, name): 157 # Convert to UTF-8 to avoid bsddb limitations. 158 del self.store[name.encode("utf-8")] 159 160 def __setitem__(self, name, value): 161 # Convert to UTF-8 to avoid bsddb limitations. 162 self.store[name.encode("utf-8")] = value 163 164 def keys(self): 165 l = [] 166 for key in self.store.keys(): 167 # Convert from UTF-8 to avoid bsddb limitations. 168 l.append(unicode(key, "utf-8")) 169 return l 170 171 def items(self): 172 l = [] 173 for key in self.keys(): 174 l.append((key, self[key])) 175 return l 176 177 def values(self): 178 l = [] 179 for key in self.keys(): 180 l.append(self[key]) 181 return l 182 183 # vim: tabstop=4 expandtab shiftwidth=4