1 #$Id: sessions.py,v 1.10 2004-02-26 04:20:45 drkorg Exp $
2 """This module defines a very basic store that's used by the CGI interface
3 to store session and one-time-key information.
5 Yes, it's called "sessions" - because originally it only defined a session
6 class. It's now also used for One Time Key handling too.
7 """
8 __docformat__ = 'restructuredtext'
10 import anydbm, whichdb, os, marshal, time
12 class BasicDatabase:
13 ''' Provide a nice encapsulation of an anydbm store.
15 Keys are id strings, values are automatically marshalled data.
16 '''
17 _db_type = None
19 def __init__(self, config):
20 self.config = config
21 self.dir = config.DATABASE
22 # ensure files are group readable and writable
23 os.umask(0002)
25 def clear(self):
26 path = os.path.join(self.dir, self.name)
27 if os.path.exists(path):
28 os.remove(path)
29 elif os.path.exists(path+'.db'): # dbm appends .db
30 os.remove(path+'.db')
32 def cache_db_type(self, path):
33 ''' determine which DB wrote the class file, and cache it as an
34 attribute of __class__ (to allow for subclassed DBs to be
35 different sorts)
36 '''
37 db_type = ''
38 if os.path.exists(path):
39 db_type = whichdb.whichdb(path)
40 if not db_type:
41 raise hyperdb.DatabaseError, "Couldn't identify database type"
42 elif os.path.exists(path+'.db'):
43 # if the path ends in '.db', it's a dbm database, whether
44 # anydbm says it's dbhash or not!
45 db_type = 'dbm'
46 self.__class__._db_type = db_type
48 def get(self, infoid, value):
49 db = self.opendb('c')
50 try:
51 if db.has_key(infoid):
52 values = marshal.loads(db[infoid])
53 else:
54 return None
55 return values.get(value, None)
56 finally:
57 db.close()
59 def getall(self, infoid):
60 db = self.opendb('c')
61 try:
62 try:
63 return marshal.loads(db[infoid])
64 except KeyError:
65 raise KeyError, 'No such One Time Key "%s"'%infoid
66 finally:
67 db.close()
69 def set(self, infoid, **newvalues):
70 db = self.opendb('c')
71 try:
72 if db.has_key(infoid):
73 values = marshal.loads(db[infoid])
74 else:
75 values = {}
76 values.update(newvalues)
77 db[infoid] = marshal.dumps(values)
78 finally:
79 db.close()
81 def list(self):
82 db = self.opendb('r')
83 try:
84 return db.keys()
85 finally:
86 db.close()
88 def destroy(self, infoid):
89 db = self.opendb('c')
90 try:
91 if db.has_key(infoid):
92 del db[infoid]
93 finally:
94 db.close()
96 def opendb(self, mode):
97 '''Low-level database opener that gets around anydbm/dbm
98 eccentricities.
99 '''
100 # figure the class db type
101 path = os.path.join(os.getcwd(), self.dir, self.name)
102 if self._db_type is None:
103 self.cache_db_type(path)
105 db_type = self._db_type
107 # new database? let anydbm pick the best dbm
108 if not db_type:
109 return anydbm.open(path, 'c')
111 # open the database with the correct module
112 dbm = __import__(db_type)
113 return dbm.open(path, mode)
115 def commit(self):
116 pass
118 def updateTimestamp(self, sessid):
119 self.set(sessid, **{self.timestamp: time.time()})
121 def clean(self, now):
122 """Age sessions, remove when they haven't been used for a week.
123 """
124 week = 60*60*24*7
125 for sessid in self.list():
126 interval = now - self.get(sessid, self.timestamp)
127 if interval > week:
128 self.destroy(sessid)
130 class Sessions(BasicDatabase):
131 name = 'sessions'
132 timestamp = 'last_use'
134 class OneTimeKeys(BasicDatabase):
135 name = 'otks'
136 timestamp = '__time'