forked from jayantsi/nevadaet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cookieFunctions.py
executable file
·478 lines (375 loc) · 15.9 KB
/
cookieFunctions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# -*- coding: utf-8 -*-
"""
webapp2_extras.sessions
=======================
Lightweight but flexible session support for webapp2.
:copyright: 2011 by tipfy.org.
:license: Apache Sotware License, see LICENSE for details.
"""
import re
import webapp2
from webapp2_extras import securecookie
from webapp2_extras import security
#: Default configuration values for this module. Keys are:
#:
#: secret_key
#: Secret key to generate session cookies. Set this to something random
#: and unguessable. This is the only required configuration key:
#: an exception is raised if it is not defined.
#:
#: cookie_name
#: Name of the cookie to save a session or session id. Default is
#: `session`.
#:
#: session_max_age:
#: Default session expiration time in seconds. Limits the duration of the
#: contents of a cookie, even if a session cookie exists. If None, the
#: contents lasts as long as the cookie is valid. Default is None.
#:
#: cookie_args
#: Default keyword arguments used to set a cookie. Keys are:
#:
#: - max_age: Cookie max age in seconds. Limits the duration
#: of a session cookie. If None, the cookie lasts until the client
#: is closed. Default is None.
#:
#: - domain: Domain of the cookie. To work accross subdomains the
#: domain must be set to the main domain with a preceding dot, e.g.,
#: cookies set for `.mydomain.org` will work in `foo.mydomain.org` and
#: `bar.mydomain.org`. Default is None, which means that cookies will
#: only work for the current subdomain.
#:
#: - path: Path in which the authentication cookie is valid.
#: Default is `/`.
#:
#: - secure: Make the cookie only available via HTTPS.
#:
#: - httponly: Disallow JavaScript to access the cookie.
#:
#: backends
#: A dictionary of available session backend classes used by
#: :meth:`SessionStore.get_session`.
default_config = {
'secret_key': None,
'cookie_name': 'session',
'session_max_age': None,
'cookie_args': {
'max_age': None,
'domain': None,
'path': '/',
'secure': None,
'httponly': False,
},
'backends': {
'securecookie': 'webapp2_extras.sessions.SecureCookieSessionFactory',
'datastore': 'webapp2_extras.appengine.sessions_ndb.' \
'DatastoreSessionFactory',
'memcache': 'webapp2_extras.appengine.sessions_memcache.' \
'MemcacheSessionFactory',
},
}
_default_value = object()
class _UpdateDictMixin(object):
"""Makes dicts call `self.on_update` on modifications.
From werkzeug.datastructures.
"""
on_update = None
def calls_update(name):
def oncall(self, *args, **kw):
rv = getattr(super(_UpdateDictMixin, self), name)(*args, **kw)
if self.on_update is not None:
self.on_update()
return rv
oncall.__name__ = name
return oncall
__setitem__ = calls_update('__setitem__')
__delitem__ = calls_update('__delitem__')
clear = calls_update('clear')
pop = calls_update('pop')
popitem = calls_update('popitem')
setdefault = calls_update('setdefault')
update = calls_update('update')
del calls_update
class SessionDict(_UpdateDictMixin, dict):
"""A dictionary for session data."""
__slots__ = ('container', 'new', 'modified')
def __init__(self, container, data=None, new=False):
self.container = container
self.new = new
self.modified = False
dict.update(self, data or ())
def pop(self, key, *args):
# Only pop if key doesn't exist, do not alter the dictionary.
if key in self:
return super(SessionDict, self).pop(key, *args)
if args:
return args[0]
raise KeyError(key)
def on_update(self):
self.modified = True
def get_flashes(self, key='_flash'):
"""Returns a flash message. Flash messages are deleted when first read.
:param key:
Name of the flash key stored in the session. Default is '_flash'.
:returns:
The data stored in the flash, or an empty list.
"""
return self.pop(key, [])
def add_flash(self, value, level=None, key='_flash'):
"""Adds a flash message. Flash messages are deleted when first read.
:param value:
Value to be saved in the flash message.
:param level:
An optional level to set with the message. Default is `None`.
:param key:
Name of the flash key stored in the session. Default is '_flash'.
"""
self.setdefault(key, []).append((value, level))
class BaseSessionFactory(object):
"""Base class for all session factories."""
#: Name of the session.
name = None
#: A reference to :class:`SessionStore`.
session_store = None
#: Keyword arguments to save the session.
session_args = None
#: The session data, a :class:`SessionDict` instance.
session = None
def __init__(self, name, session_store):
self.name = name
self.session_store = session_store
self.session_args = session_store.config['cookie_args'].copy()
self.session = None
def get_session(self, max_age=_default_value):
raise NotImplementedError()
def save_session(self, response):
raise NotImplementedError()
class SecureCookieSessionFactory(BaseSessionFactory):
"""A session factory that stores data serialized in a signed cookie.
Signed cookies can't be forged because the HMAC signature won't match.
This is the default factory passed as the `factory` keyword to
:meth:`SessionStore.get_session`.
.. warning::
The values stored in a signed cookie will be visible in the cookie,
so do not use secure cookie sessions if you need to store data that
can't be visible to users. For this, use datastore or memcache sessions.
"""
def get_session(self, max_age=_default_value):
if self.session is None:
data = self.session_store.get_secure_cookie(self.name,
max_age=max_age)
new = data is None
self.session = SessionDict(self, data=data, new=new)
return self.session
def save_session(self, response):
if self.session is None or not self.session.modified:
return
self.session_store.save_secure_cookie(
response, self.name, dict(self.session), **self.session_args)
class CustomBackendSessionFactory(BaseSessionFactory):
"""Base class for sessions that use custom backends, e.g., memcache."""
#: The session unique id.
sid = None
#: Used to validate session ids.
_sid_re = re.compile(r'^\w{22}$')
def get_session(self, max_age=_default_value):
if self.session is None:
data = self.session_store.get_secure_cookie(self.name,
max_age=max_age)
sid = data.get('_sid') if data else None
self.session = self._get_by_sid(sid)
return self.session
def _get_by_sid(self, sid):
raise NotImplementedError()
def _is_valid_sid(self, sid):
"""Check if a session id has the correct format."""
return sid and self._sid_re.match(sid) is not None
def _get_new_sid(self):
return security.generate_random_string(entropy=128)
class SessionStore(object):
"""A session provider for a single request.
The session store can provide multiple sessions using different keys,
even using different backends in the same request, through the method
:meth:`get_session`. By default it returns a session using the default key.
To use, define a base handler that extends the dispatch() method to start
the session store and save all sessions at the end of a request::
import webapp2
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
@webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session()
Then just use the session as a dictionary inside a handler::
# To set a value:
self.session['foo'] = 'bar'
# To get a value:
foo = self.session.get('foo')
A configuration dict can be passed to :meth:`__init__`, or the application
must be initialized with the ``secret_key`` configuration defined. The
configuration is a simple dictionary::
config = {}
config['webapp2_extras.sessions'] = {
'secret_key': 'my-super-secret-key',
}
app = webapp2.WSGIApplication([
('/', HomeHandler),
], config=config)
Other configuration keys are optional.
"""
#: Configuration key.
config_key = __name__
def __init__(self, request, config=None):
"""Initializes the session store.
:param request:
A :class:`webapp2.Request` instance.
:param config:
A dictionary of configuration values to be overridden. See
the available keys in :data:`default_config`.
"""
self.request = request
# Base configuration.
self.config = request.app.config.load_config(
self.config_key, default_values=default_config,
user_values=config, required_keys=('secret_key',))
# Tracked sessions.
self.sessions = {}
@webapp2.cached_property
def serializer(self):
# Serializer and deserializer for signed cookies.
return securecookie.SecureCookieSerializer(self.config['secret_key'])
def get_backend(self, name):
"""Returns a configured session backend, importing it if needed.
:param name:
The backend keyword.
:returns:
A :class:`BaseSessionFactory` subclass.
"""
backends = self.config['backends']
backend = backends[name]
if isinstance(backend, basestring):
backend = backends[name] = webapp2.import_string(backend)
return backend
# Backend based sessions --------------------------------------------------
def _get_session_container(self, name, factory):
if name not in self.sessions:
self.sessions[name] = factory(name, self)
return self.sessions[name]
def get_session(self, name=None, max_age=_default_value, factory=None,
backend='securecookie'):
"""Returns a session for a given name. If the session doesn't exist, a
new session is returned.
:param name:
Cookie name. If not provided, uses the ``cookie_name``
value configured for this module.
:param max_age:
A maximum age in seconds for the session to be valid. Sessions
store a timestamp to invalidate them if needed. If `max_age` is
None, the timestamp won't be checked.
:param factory:
A session factory that creates the session using the preferred
backend. For convenience, use the `backend` argument instead,
which defines a backend keyword based on the configured ones.
:param backend:
A configured backend keyword. Available ones are:
- ``securecookie``: uses secure cookies. This is the default
backend.
- ``datastore``: uses App Engine's datastore.
- ``memcache``: uses App Engine's memcache.
:returns:
A dictionary-like session object.
"""
factory = factory or self.get_backend(backend)
name = name or self.config['cookie_name']
if max_age is _default_value:
max_age = self.config['session_max_age']
container = self._get_session_container(name, factory)
return container.get_session(max_age=max_age)
# Signed cookies ----------------------------------------------------------
def get_secure_cookie(self, name, max_age=_default_value):
"""Returns a deserialized secure cookie value.
:param name:
Cookie name.
:param max_age:
Maximum age in seconds for a valid cookie. If the cookie is older
than this, returns None.
:returns:
A secure cookie value or None if it is not set.
"""
if max_age is _default_value:
max_age = self.config['session_max_age']
value = self.request.cookies.get(name)
if value:
return self.serializer.deserialize(name, value, max_age=max_age)
def set_secure_cookie(self, name, value, **kwargs):
"""Sets a secure cookie to be saved.
:param name:
Cookie name.
:param value:
Cookie value. Must be a dictionary.
:param kwargs:
Options to save the cookie. See :meth:`get_session`.
"""
assert isinstance(value, dict), 'Secure cookie values must be a dict.'
container = self._get_session_container(name,
SecureCookieSessionFactory)
container.get_session().update(value)
container.session_args.update(kwargs)
# Saving to a response object ---------------------------------------------
def save_sessions(self, response):
"""Saves all sessions in a response object.
:param response:
A :class:`webapp.Response` object.
"""
for session in self.sessions.values():
session.save_session(response)
def save_secure_cookie(self, response, name, value, **kwargs):
value = self.serializer.serialize(name, value)
response.set_cookie(name, value, **kwargs)
# Factories -------------------------------------------------------------------
#: Key used to store :class:`SessionStore` in the request registry.
_registry_key = 'webapp2_extras.sessions.SessionStore'
def get_store(factory=SessionStore, key=_registry_key, request=None):
"""Returns an instance of :class:`SessionStore` from the request registry.
It'll try to get it from the current request registry, and if it is not
registered it'll be instantiated and registered. A second call to this
function will return the same instance.
:param factory:
The callable used to build and register the instance if it is not yet
registered. The default is the class :class:`SessionStore` itself.
:param key:
The key used to store the instance in the registry. A default is used
if it is not set.
:param request:
A :class:`webapp2.Request` instance used to store the instance. The
active request is used if it is not set.
"""
request = request or webapp2.get_request()
store = request.registry.get(key)
if not store:
store = request.registry[key] = factory(request)
return store
def set_store(store, key=_registry_key, request=None):
"""Sets an instance of :class:`SessionStore` in the request registry.
:param store:
An instance of :class:`SessionStore`.
:param key:
The key used to retrieve the instance from the registry. A default
is used if it is not set.
:param request:
A :class:`webapp2.Request` instance used to retrieve the instance. The
active request is used if it is not set.
"""
request = request or webapp2.get_request()
request.registry[key] = store
# Don't need to import it. :)
default_config['backends']['securecookie'] = SecureCookieSessionFactory