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 SessionDirectoryRepository 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 = SessionDirectoryRepository(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 del self.repository[session_id] 90 91 def get_session(self, create): 92 93 """ 94 Get the session for the given transaction, creating a new session if 95 'create' is set to 1 (rather than 0). Where new sessions are created, an 96 appropriate session identifier cookie will be created. 97 Returns a session object or None if no session exists and none is then 98 created. 99 """ 100 101 if self.store is not None: 102 return self.store 103 104 # No existing store - get the token and the actual session. 105 106 cookie = self.trans.get_cookie(self.session_cookie_name) 107 if cookie: 108 return self._get_session(cookie.value, create) 109 elif create: 110 session_id = self._get_session_identifier() 111 self.trans.set_cookie_value(self.session_cookie_name, session_id) 112 return self._get_session(session_id, create) 113 else: 114 return None 115 116 def _get_session(self, session_id, create): 117 118 """ 119 Get a session with the given 'session_id' and whether new sessions 120 should be created ('create' set to 1). 121 Returns a dictionary-like object representing the session. 122 """ 123 124 try: 125 store_filename = self.repository.lock(session_id, create=create, opener=shelve.open) 126 self.current_session_id = session_id 127 except KeyError: 128 return None 129 130 # NOTE: Using inside knowledge about the repository structure. 131 132 self.store = shelve.open(os.path.join(store_filename, "data")) 133 return Wrapper(self.store) 134 135 def _get_session_identifier(self): 136 137 "Return a session identifier as a string." 138 139 g = random.Random() 140 return str(g.randint(0, sys.maxint - 1)) 141 142 class Wrapper: 143 144 "A wrapper around shelf objects." 145 146 def __init__(self, store): 147 self.store = store 148 149 def __getattr__(self, name): 150 if hasattr(self.store, name): 151 return getattr(self.store, name) 152 else: 153 raise AttributeError, name 154 155 def __getitem__(self, name): 156 # Convert to UTF-8 to avoid bsddb limitations. 157 return self.store[name.encode("utf-8")] 158 159 def __delitem__(self, name): 160 # Convert to UTF-8 to avoid bsddb limitations. 161 del self.store[name.encode("utf-8")] 162 163 def __setitem__(self, name, value): 164 # Convert to UTF-8 to avoid bsddb limitations. 165 self.store[name.encode("utf-8")] = value 166 167 def keys(self): 168 l = [] 169 for key in self.store.keys(): 170 # Convert from UTF-8 to avoid bsddb limitations. 171 l.append(unicode(key, "utf-8")) 172 return l 173 174 def items(self): 175 l = [] 176 for key in self.keys(): 177 l.append((key, self[key])) 178 return l 179 180 def values(self): 181 l = [] 182 for key in self.keys(): 183 l.append(self[key]) 184 return l 185 186 # vim: tabstop=4 expandtab shiftwidth=4