Trac patch for fetching sessions outside of a request

18 March 2008
17:25

By mgood as Diff

1 diff --git a/trac/web/session.py b/trac/web/session.py
2 index c8f724b..cb3b268 100644
3 --- a/trac/web/session.py
4 +++ b/trac/web/session.py
5 @@ -28,17 +28,104 @@ PURGE_AGE = 3600*24*90 # Purge session after 90 days idle
6 COOKIE_KEY = 'trac_session'
7
8
9 -class Session(dict):
10 - """Basic session handling and per-session storage."""
11 -
12 - def __init__(self, env, req):
13 +class DetachedSession(dict):
14 + def __init__(self, env, sid):
15 dict.__init__(self)
16 self.env = env
17 - self.req = req
18 self.sid = None
19 self.last_visit = 0
20 self._new = True
21 self._old = {}
22 + if sid:
23 + self.get_session(sid, authenticated=True)
24 + else:
25 + self.authenticated = False
26 +
27 + def get_session(self, sid, authenticated=False):
28 + self.env.log.debug('Retrieving session for ID %r', sid)
29 +
30 + db = self.env.get_db_cnx()
31 + cursor = db.cursor()
32 +
33 + self.sid = sid
34 + self.authenticated = authenticated
35 +
36 + cursor.execute("SELECT last_visit FROM session "
37 + "WHERE sid=%s AND authenticated=%s",
38 + (sid, int(authenticated)))
39 + row = cursor.fetchone()
40 + if not row:
41 + return
42 + self._new = False
43 + self.last_visit = int(row[0])
44 +
45 + cursor.execute("SELECT name,value FROM session_attribute "
46 + "WHERE sid=%s and authenticated=%s",
47 + (sid, int(authenticated)))
48 + for name, value in cursor:
49 + self[name] = value
50 + self._old.update(self)
51 +
52 + def save(self):
53 + if not self._old and not self.items():
54 + # The session doesn't have associated data, so there's no need to
55 + # persist it
56 + return
57 +
58 + db = self.env.get_db_cnx()
59 + cursor = db.cursor()
60 + authenticated = int(self.authenticated)
61 +
62 + if self._new:
63 + self._new = False
64 + cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
65 + " VALUES(%s,%s,%s)",
66 + (self.sid, self.last_visit, authenticated))
67 + if self._old != self:
68 + attrs = [(self.sid, authenticated, k, v) for k, v in self.items()]
69 + cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
70 + (self.sid,))
71 + self._old = dict(self.items())
72 + if attrs:
73 + cursor.executemany("INSERT INTO session_attribute "
74 + "(sid,authenticated,name,value) "
75 + "VALUES(%s,%s,%s,%s)", attrs)
76 + elif not authenticated:
77 + # No need to keep around empty unauthenticated sessions
78 + cursor.execute("DELETE FROM session "
79 + "WHERE sid=%s AND authenticated=0", (self.sid,))
80 + return
81 +
82 + now = int(time.time())
83 + # Update the session last visit time if it is over an hour old,
84 + # so that session doesn't get purged
85 + if now - self.last_visit > UPDATE_INTERVAL:
86 + self.last_visit = now
87 + self.env.log.info("Refreshing session %s" % self.sid)
88 + cursor.execute('UPDATE session SET last_visit=%s '
89 + 'WHERE sid=%s AND authenticated=%s',
90 + (self.last_visit, self.sid, authenticated))
91 + # Purge expired sessions. We do this only when the session was
92 + # changed as to minimize the purging.
93 + mintime = now - PURGE_AGE
94 + self.env.log.debug('Purging old, expired, sessions.')
95 + cursor.execute("DELETE FROM session_attribute "
96 + "WHERE authenticated=0 AND sid "
97 + "IN (SELECT sid FROM session WHERE "
98 + "authenticated=0 AND last_visit < %s)",
99 + (mintime,))
100 + cursor.execute("DELETE FROM session WHERE "
101 + "authenticated=0 AND last_visit < %s",
102 + (mintime,))
103 + db.commit()
104 +
105 +
106 +class Session(DetachedSession):
107 + """Basic session handling and per-session storage."""
108 +
109 + def __init__(self, env, req):
110 + DetachedSession.__init__(self, env, None)
111 + self.req = req
112 if req.authname == 'anonymous':
113 if not req.incookie.has_key(COOKIE_KEY):
114 self.sid = hex_entropy(24)
115 @@ -59,34 +146,15 @@ class Session(dict):
116 self.req.outcookie[COOKIE_KEY]['expires'] = expires
117
118 def get_session(self, sid, authenticated=False):
119 - self.env.log.debug('Retrieving session for ID %r', sid)
120 -
121 - db = self.env.get_db_cnx()
122 - cursor = db.cursor()
123 refresh_cookie = False
124
125 if self.sid and sid != self.sid:
126 refresh_cookie = True
127 - self.sid = sid
128
129 - cursor.execute("SELECT last_visit FROM session "
130 - "WHERE sid=%s AND authenticated=%s",
131 - (sid, int(authenticated)))
132 - row = cursor.fetchone()
133 - if not row:
134 - return
135 - self._new = False
136 - self.last_visit = int(row[0])
137 + DetachedSession.get_session(self, sid, authenticated)
138 if self.last_visit and time.time() - self.last_visit > UPDATE_INTERVAL:
139 refresh_cookie = True
140
141 - cursor.execute("SELECT name,value FROM session_attribute "
142 - "WHERE sid=%s and authenticated=%s",
143 - (sid, int(authenticated)))
144 - for name, value in cursor:
145 - self[name] = value
146 - self._old.update(self)
147 -
148 # Refresh the session cookie if this is the first visit since over a day
149 if not authenticated and refresh_cookie:
150 self.bake_cookie()
151 @@ -157,56 +225,3 @@ class Session(dict):
152
153 self.sid = sid
154 self.bake_cookie(0) # expire the cookie
155 -
156 - def save(self):
157 - if not self._old and not self.items():
158 - # The session doesn't have associated data, so there's no need to
159 - # persist it
160 - return
161 -
162 - db = self.env.get_db_cnx()
163 - cursor = db.cursor()
164 - authenticated = int(self.req.authname != 'anonymous')
165 -
166 - if self._new:
167 - self._new = False
168 - cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"
169 - " VALUES(%s,%s,%s)",
170 - (self.sid, self.last_visit, authenticated))
171 - if self._old != self:
172 - attrs = [(self.sid, authenticated, k, v) for k, v in self.items()]
173 - cursor.execute("DELETE FROM session_attribute WHERE sid=%s",
174 - (self.sid,))
175 - self._old = dict(self.items())
176 - if attrs:
177 - cursor.executemany("INSERT INTO session_attribute "
178 - "(sid,authenticated,name,value) "
179 - "VALUES(%s,%s,%s,%s)", attrs)
180 - elif not authenticated:
181 - # No need to keep around empty unauthenticated sessions
182 - cursor.execute("DELETE FROM session "
183 - "WHERE sid=%s AND authenticated=0", (self.sid,))
184 - return
185 -
186 - now = int(time.time())
187 - # Update the session last visit time if it is over an hour old,
188 - # so that session doesn't get purged
189 - if now - self.last_visit > UPDATE_INTERVAL:
190 - self.last_visit = now
191 - self.env.log.info("Refreshing session %s" % self.sid)
192 - cursor.execute('UPDATE session SET last_visit=%s '
193 - 'WHERE sid=%s AND authenticated=%s',
194 - (self.last_visit, self.sid, authenticated))
195 - # Purge expired sessions. We do this only when the session was
196 - # changed as to minimize the purging.
197 - mintime = now - PURGE_AGE
198 - self.env.log.debug('Purging old, expired, sessions.')
199 - cursor.execute("DELETE FROM session_attribute "
200 - "WHERE authenticated=0 AND sid "
201 - "IN (SELECT sid FROM session WHERE "
202 - "authenticated=0 AND last_visit < %s)",
203 - (mintime,))
204 - cursor.execute("DELETE FROM session WHERE "
205 - "authenticated=0 AND last_visit < %s",
206 - (mintime,))
207 - db.commit()
208 diff --git a/trac/web/tests/session.py b/trac/web/tests/session.py
209 index ddb31d1..13b5fcd 100644
210 --- a/trac/web/tests/session.py
211 +++ b/trac/web/tests/session.py
212 @@ -6,7 +6,7 @@ from trac.core import TracError
213 from trac.log import logger_factory
214 from trac.test import EnvironmentStub, Mock
215 from trac.web.href import Href
216 -from trac.web.session import Session, PURGE_AGE, UPDATE_INTERVAL
217 +from trac.web.session import DetachedSession, Session, PURGE_AGE, UPDATE_INTERVAL
218
219
220 class SessionTestCase(unittest.TestCase):
221 @@ -286,6 +286,42 @@ class SessionTestCase(unittest.TestCase):
222 "authenticated=0")
223 self.assertAlmostEqual(now, int(cursor.fetchone()[0]), -1)
224
225 + def test_modify_detached_session(self):
226 + """
227 + Verify that a modifying a variable in a session not associated with a
228 + request updates the database accordingly.
229 + """
230 + cursor = self.db.cursor()
231 + cursor.execute("INSERT INTO session VALUES ('john', 1, 0)")
232 + cursor.execute("INSERT INTO session_attribute VALUES "
233 + "('john', 1, 'foo', 'bar')")
234 +
235 + session = DetachedSession(self.env, 'john')
236 + self.assertEqual('bar', session['foo'])
237 + session['foo'] = 'baz'
238 + session.save()
239 + cursor.execute("SELECT value FROM session_attribute "
240 + "WHERE sid='john' AND name='foo'")
241 + self.assertEqual('baz', cursor.fetchone()[0])
242 +
243 + def test_delete_detached_session_var(self):
244 + """
245 + Verify that removing a variable in a session not associated with a
246 + request deletes the variable from the database.
247 + """
248 + cursor = self.db.cursor()
249 + cursor.execute("INSERT INTO session VALUES ('john', 1, 0)")
250 + cursor.execute("INSERT INTO session_attribute VALUES "
251 + "('john', 1, 'foo', 'bar')")
252 +
253 + session = DetachedSession(self.env, 'john')
254 + self.assertEqual('bar', session['foo'])
255 + del session['foo']
256 + session.save()
257 + cursor.execute("SELECT COUNT(*) FROM session_attribute "
258 + "WHERE sid='john' AND name='foo'")
259 + self.assertEqual(0, cursor.fetchone()[0])
260 +
261
262 def suite():
263 return unittest.makeSuite(SessionTestCase, 'test')

Download Raw Source

Comments

No comments so far.