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 import os 27 from WebStack.Repositories.Directory import DirectoryRepository 28 29 class SessionStore: 30 31 "A class representing a session store." 32 33 def __init__(self, trans, session_directory, session_cookie_name="SID", delay=1): 34 35 """ 36 Initialise the session store, specifying the transaction 'trans' within 37 which all session access will occur, a base 'session_directory', and the 38 optional 'session_cookie_name' where the session identifier is held for 39 each user. 40 41 The optional 'delay' argument specifies the time in seconds between each 42 poll of the session file when that file is found to be locked for 43 editing. (This is part of the underlying repository's behaviour.) 44 """ 45 46 self.trans = trans 47 self.session_cookie_name = session_cookie_name 48 self.repository = DirectoryRepository(session_directory, delay=delay) 49 50 # Internal state. 51 52 self.store = None 53 self.current_session_id = None 54 self.to_expire = None 55 56 def close(self): 57 58 "Close the store, tidying up files and filenames." 59 60 if self.store is not None: 61 self.store.close() 62 self.store = None 63 if self.current_session_id is not None: 64 self.repository.unlock(self.current_session_id) 65 self.current_session_id = None 66 67 # Handle expiry appropriately. 68 69 if self.to_expire is not None: 70 self._expire_session(self.to_expire) 71 self.trans.delete_cookie(self.session_cookie_name) 72 73 def expire_session(self): 74 75 """ 76 Expire the session in the given transaction. 77 """ 78 79 # Perform expiry. 80 81 cookie = self.trans.get_cookie(self.session_cookie_name) 82 if cookie: 83 self.to_expire = cookie.value 84 85 def _expire_session(self, session_id): 86 87 "Expire the session with the given 'session_id'." 88 89 self.repository.lock(session_id) 90 del self.repository[session_id] 91 92 def get_session(self, create): 93 94 """ 95 Get the session for the given transaction, creating a new session if 96 'create' is set to 1 (rather than 0). Where new sessions are created, an 97 appropriate session identifier cookie will be created. 98 Returns a session object or None if no session exists and none is then 99 created. 100 """ 101 102 if self.store is not None: 103 return self.store 104 105 # No existing store - get the token and the actual session. 106 107 cookie = self.trans.get_cookie(self.session_cookie_name) 108 if cookie: 109 return self._get_session(cookie.value, create) 110 elif create: 111 session_id = self._get_session_identifier() 112 self.trans.set_cookie_value(self.session_cookie_name, session_id) 113 return self._get_session(session_id, create) 114 else: 115 return None 116 117 def _get_session(self, session_id, create): 118 119 """ 120 Get a session with the given 'session_id' and whether new sessions 121 should be created ('create' set to 1). 122 Returns a dictionary-like object representing the session. 123 """ 124 125 try: 126 store_filename = self.repository.lock(session_id, create=create, opener=shelve.open) 127 self.current_session_id = session_id 128 except KeyError: 129 return None 130 131 # NOTE: Using inside knowledge about the repository structure. 132 133 self.store = shelve.open(os.path.join(store_filename, "data")) 134 return Wrapper(self.store) 135 136 def _get_session_identifier(self): 137 138 "Return a session identifier as a string." 139 140 g = random.Random() 141 return str(g.randint(0, sys.maxint - 1)) 142 143 class Wrapper: 144 145 "A wrapper around shelf objects." 146 147 def __init__(self, store): 148 self.store = store 149 150 def __getattr__(self, name): 151 if hasattr(self.store, name): 152 return getattr(self.store, name) 153 else: 154 raise AttributeError, name 155 156 def __getitem__(self, name): 157 # Convert to UTF-8 to avoid bsddb limitations. 158 return self.store[name.encode("utf-8")] 159 160 def __delitem__(self, name): 161 # Convert to UTF-8 to avoid bsddb limitations. 162 del self.store[name.encode("utf-8")] 163 164 def __setitem__(self, name, value): 165 # Convert to UTF-8 to avoid bsddb limitations. 166 self.store[name.encode("utf-8")] = value 167 168 def keys(self): 169 l = [] 170 for key in self.store.keys(): 171 # Convert from UTF-8 to avoid bsddb limitations. 172 l.append(unicode(key, "utf-8")) 173 return l 174 175 def items(self): 176 l = [] 177 for key in self.keys(): 178 l.append((key, self[key])) 179 return l 180 181 def values(self): 182 l = [] 183 for key in self.keys(): 184 l.append(self[key]) 185 return l 186 187 # vim: tabstop=4 expandtab shiftwidth=4