0001-misc-use-database-to-store-form-tokens-71455.patch
tests/test_sessions.py | ||
---|---|---|
239 | 239 |
resp = resp.form.submit('submit') |
240 | 240 | |
241 | 241 |
assert sql.Session.count() == 1 |
242 |
assert sql.TransientData.count() == 1 |
|
243 |
transient_data = sql.TransientData.select()[0] |
|
242 |
assert sql.TransientData.count() == 2 |
|
243 |
transient_data1, transient_data2 = sql.TransientData.select(order_by='last_update_time') |
|
244 |
assert transient_data1.data is None # form_token |
|
245 |
assert transient_data2.data == {'1': 'test'} # magictoken |
|
244 | 246 | |
245 | 247 |
app.get('/logout') |
246 | 248 |
assert sql.Session.count() == 0 |
247 | 249 |
assert sql.TransientData.count() == 0 |
248 | 250 | |
249 |
transient_data.store() # session_id not found, should not fail |
|
251 |
transient_data2.store() # session_id not found, should not fail
|
|
250 | 252 | |
251 | 253 | |
252 | 254 |
def test_magictoken_migration(pub, app): |
wcs/qommon/publisher.py | ||
---|---|---|
131 | 131 |
app_dir = None |
132 | 132 |
_i18n_catalog = None |
133 | 133 | |
134 |
@property |
|
135 |
def form_tokens_dir(self): |
|
136 |
return os.path.join(self.app_dir, 'form_tokens') |
|
137 | ||
138 | 134 |
def get_root_url(self): |
139 | 135 |
if self.get_request(): |
140 | 136 |
return self.get_request().environ['SCRIPT_NAME'] + '/' |
... | ... | |
479 | 475 |
raise ImmediateRedirectException(self.missing_appdir_redirect) |
480 | 476 |
raise Http404() |
481 | 477 | |
482 |
try: |
|
483 |
os.mkdir(self.form_tokens_dir) |
|
484 |
except OSError: # already exists |
|
485 |
pass |
|
486 | ||
487 | 478 |
def init_publish(self, request): |
488 | 479 |
self.set_app_dir(request) |
489 | 480 | |
... | ... | |
615 | 606 |
except KeyError: |
616 | 607 |
pass |
617 | 608 |
continue |
618 |
# also delete obsolete form_tokens that would have be missed when |
|
619 |
# cleaning sessions. |
|
620 |
form_tokens_dir = self.form_tokens_dir |
|
621 |
if os.path.exists(form_tokens_dir): |
|
622 |
for filename in os.listdir(form_tokens_dir): |
|
623 |
try: |
|
624 |
if os.stat(os.path.join(form_tokens_dir, filename)).st_mtime < creation_limit: |
|
625 |
os.unlink(os.path.join(form_tokens_dir, filename)) |
|
626 |
except FileNotFoundError: |
|
627 |
pass |
|
628 | 609 |
except locket.LockError: |
629 | 610 |
pass |
630 | 611 |
wcs/qommon/sessions.py | ||
---|---|---|
166 | 166 | |
167 | 167 |
session_id = property(get_session_id, set_session_id) |
168 | 168 | |
169 |
def get_form_token_filepath(self, token): |
|
170 |
return os.path.join(get_publisher().form_tokens_dir, token) |
|
171 | ||
172 |
def create_form_token(self): |
|
173 |
token = super().create_form_token() |
|
174 |
with open(self.get_form_token_filepath(token), 'wb'): |
|
175 |
# create empty file |
|
176 |
pass |
|
177 |
return token |
|
178 | ||
179 |
def has_form_token(self, token): |
|
180 |
if not token: |
|
181 |
return False |
|
182 |
has_form_token = super().has_form_token(token) |
|
183 |
if not os.path.exists(self.get_form_token_filepath(token)): |
|
184 |
has_form_token = False |
|
185 |
return has_form_token |
|
186 | ||
187 |
def remove_form_token(self, token): |
|
188 |
super().remove_form_token(token) |
|
189 |
self.store() |
|
190 |
try: |
|
191 |
os.unlink(self.get_form_token_filepath(token)) |
|
192 |
except OSError: |
|
193 |
pass |
|
194 | ||
195 |
def clean_form_tokens(self): |
|
196 |
dirname = os.path.join(get_publisher().app_dir, 'form_tokens') |
|
197 |
for token in self._form_tokens: |
|
198 |
try: |
|
199 |
os.unlink(os.path.join(dirname, token)) |
|
200 |
except OSError: |
|
201 |
pass |
|
202 | ||
203 | 169 |
def has_user(self): |
204 | 170 |
user_id = QuixoteSession.get_user(self) |
205 | 171 |
return bool(user_id) |
... | ... | |
400 | 366 |
def __delitem__(self, session_id): |
401 | 367 |
if not session_id: |
402 | 368 |
return |
403 |
try: |
|
404 |
session = self.session_class.get(session_id) |
|
405 |
except KeyError: |
|
406 |
session = None |
|
407 |
try: |
|
408 |
self.session_class.remove_object(session_id) |
|
409 |
except OSError: |
|
410 |
raise KeyError |
|
411 | ||
412 |
if session: |
|
413 |
session.clean_form_tokens() |
|
369 |
self.session_class.remove_object(session_id) |
|
414 | 370 | |
415 | 371 |
def get_sessions_for_saml(self, name_identifier=Ellipsis, session_indexes=()): |
416 | 372 |
return self.session_class.get_sessions_for_saml(name_identifier, session_indexes) |
wcs/sql.py | ||
---|---|---|
20 | 20 |
import itertools |
21 | 21 |
import json |
22 | 22 |
import re |
23 |
import secrets |
|
23 | 24 |
import time |
24 | 25 | |
25 | 26 |
import psycopg2 |
... | ... | |
3518 | 3519 | |
3519 | 3520 | |
3520 | 3521 |
class TransientData(SqlMixin): |
3521 |
# table to keep some transient submission data out of global session dictionary |
|
3522 |
# table to keep some transient submission data and form tokens out of global session dictionary
|
|
3522 | 3523 |
_table_name = 'transient_data' |
3523 | 3524 |
_table_static_fields = [ |
3524 | 3525 |
('id', 'varchar'), |
... | ... | |
3538 | 3539 |
sql_dict = { |
3539 | 3540 |
'id': self.id, |
3540 | 3541 |
'session_id': self.session_id, |
3541 |
'data': bytearray(pickle.dumps(self.data, protocol=2)), |
|
3542 |
'data': bytearray(pickle.dumps(self.data, protocol=2)) if self.data is not None else None,
|
|
3542 | 3543 |
'last_update_time': now(), |
3543 | 3544 |
} |
3544 | 3545 | |
... | ... | |
3565 | 3566 |
o = cls.__new__(cls) |
3566 | 3567 |
o.id = str_encode(row[0]) |
3567 | 3568 |
o.session_id = row[1] |
3568 |
o.data = pickle_loads(row[2]) |
|
3569 |
o.data = pickle_loads(row[2]) if row[2] else None
|
|
3569 | 3570 |
return o |
3570 | 3571 | |
3571 | 3572 |
@classmethod |
... | ... | |
3720 | 3721 |
super().remove_magictoken(token) |
3721 | 3722 |
TransientData.remove_object(token) |
3722 | 3723 | |
3724 |
def create_form_token(self): |
|
3725 |
token = TransientData(id=secrets.token_urlsafe(16), session_id=self.id, data=None) |
|
3726 |
token.store() |
|
3727 |
return token.id |
|
3728 | ||
3729 |
def has_form_token(self, token): |
|
3730 |
return TransientData.exists([Equal('id', token)]) |
|
3731 | ||
3732 |
def remove_form_token(self, token): |
|
3733 |
TransientData.remove_object(token) |
|
3734 | ||
3723 | 3735 | |
3724 | 3736 |
class TrackingCode(SqlMixin, wcs.tracking_code.TrackingCode): |
3725 | 3737 |
_table_name = 'tracking_codes' |
3726 |
- |