commit | author | age
|
04ebd5
|
1 |
.. index:: |
CM |
2 |
single: session |
|
3 |
|
|
4 |
.. _sessions_chapter: |
|
5 |
|
b0b9f7
|
6 |
Sessions |
CD |
7 |
======== |
04ebd5
|
8 |
|
CM |
9 |
A :term:`session` is a namespace which is valid for some period of |
|
10 |
continual activity that can be used to represent a user's interaction |
|
11 |
with a web application. |
|
12 |
|
089c63
|
13 |
This chapter describes how to configure sessions, what session |
826fd7
|
14 |
implementations :app:`Pyramid` provides out of the box, how to store and |
CD |
15 |
retrieve data from sessions, and two session-specific features: flash |
|
16 |
messages, and cross-site request forgery attack prevention. |
089c63
|
17 |
|
6ce1e0
|
18 |
.. index:: |
CM |
19 |
single: session factory (default) |
|
20 |
|
4df636
|
21 |
.. _using_the_default_session_factory: |
CM |
22 |
|
04ebd5
|
23 |
Using The Default Session Factory |
CM |
24 |
--------------------------------- |
|
25 |
|
|
26 |
In order to use sessions, you must set up a :term:`session factory` |
fd5ae9
|
27 |
during your :app:`Pyramid` configuration. |
04ebd5
|
28 |
|
CM |
29 |
A very basic, insecure sample session factory implementation is |
fd5ae9
|
30 |
provided in the :app:`Pyramid` core. It uses a cookie to store |
04ebd5
|
31 |
session information. This implementation has the following |
0a9a29
|
32 |
limitations: |
04ebd5
|
33 |
|
CM |
34 |
- The session information in the cookies used by this implementation |
|
35 |
is *not* encrypted, so it can be viewed by anyone with access to the |
|
36 |
cookie storage of the user's browser or anyone with access to the |
|
37 |
network along which the cookie travels. |
|
38 |
|
|
39 |
- The maximum number of bytes that are storable in a serialized |
52546a
|
40 |
representation of the session is fewer than 4000. This is |
CD |
41 |
suitable only for very small data sets. |
04ebd5
|
42 |
|
15c45d
|
43 |
It is digitally signed, however, and thus its data cannot easily be |
04ebd5
|
44 |
tampered with. |
CM |
45 |
|
8df7a7
|
46 |
You can configure this session factory in your :app:`Pyramid` application |
99d7c4
|
47 |
by using the :meth:`pyramid.config.Configurator.set_session_factory` method. |
04ebd5
|
48 |
|
CM |
49 |
.. code-block:: python |
|
50 |
:linenos: |
|
51 |
|
8df7a7
|
52 |
from pyramid.session import SignedCookieSessionFactory |
MM |
53 |
my_session_factory = SignedCookieSessionFactory('itsaseekreet') |
|
54 |
|
d7f259
|
55 |
from pyramid.config import Configurator |
8df7a7
|
56 |
config = Configurator() |
MM |
57 |
config.set_session_factory(my_session_factory) |
04ebd5
|
58 |
|
CM |
59 |
.. warning:: |
|
60 |
|
8df7a7
|
61 |
By default the :func:`~pyramid.session.SignedCookieSessionFactory` |
MM |
62 |
implementation is *unencrypted*. You should not use it |
098e16
|
63 |
when you keep sensitive information in the session object, as the |
CM |
64 |
information can be easily read by both users of your application and third |
76430b
|
65 |
parties who have access to your users' network traffic. And if you use this |
CM |
66 |
sessioning implementation, and you inadvertently create a cross-site |
|
67 |
scripting vulnerability in your application, because the session data is |
|
68 |
stored unencrypted in a cookie, it will also be easier for evildoers to |
|
69 |
obtain the current user's cross-site scripting token. In short, use a |
|
70 |
different session factory implementation (preferably one which keeps session |
|
71 |
data on the server) for anything but the most basic of applications where |
|
72 |
"session security doesn't matter", and you are sure your application has no |
|
73 |
cross-site scripting vulnerabilities. |
6ce1e0
|
74 |
|
CM |
75 |
.. index:: |
|
76 |
single: session object |
04ebd5
|
77 |
|
CM |
78 |
Using a Session Object |
|
79 |
---------------------- |
|
80 |
|
|
81 |
Once a session factory has been configured for your application, you |
9508dc
|
82 |
can access session objects provided by the session factory via |
CD |
83 |
the ``session`` attribute of any :term:`request` object. For |
04ebd5
|
84 |
example: |
CM |
85 |
|
|
86 |
.. code-block:: python |
|
87 |
:linenos: |
|
88 |
|
94b889
|
89 |
from pyramid.response import Response |
04ebd5
|
90 |
|
CM |
91 |
def myview(request): |
|
92 |
session = request.session |
|
93 |
if 'abc' in session: |
|
94 |
session['fred'] = 'yes' |
|
95 |
session['abc'] = '123' |
|
96 |
if 'fred' in session: |
|
97 |
return Response('Fred was in the session') |
|
98 |
else: |
|
99 |
return Response('Fred was not in the session') |
|
100 |
|
190b56
|
101 |
The first time this view is invoked produces ``Fred was not in the |
KOP |
102 |
session``. Subsequent invocations produce ``Fred was in the |
b31cdc
|
103 |
session``, assuming of course that the client side maintains the |
KOP |
104 |
session's identity across multiple requests. |
190b56
|
105 |
|
04ebd5
|
106 |
You can use a session much like a Python dictionary. It supports all |
1f7c02
|
107 |
dictionary methods, along with some extra attributes, and methods. |
04ebd5
|
108 |
|
CM |
109 |
Extra attributes: |
|
110 |
|
|
111 |
``created`` |
|
112 |
An integer timestamp indicating the time that this session was created. |
|
113 |
|
|
114 |
``new`` |
|
115 |
A boolean. If ``new`` is True, this session is new. Otherwise, it has |
|
116 |
been constituted from data that was already serialized. |
|
117 |
|
|
118 |
Extra methods: |
|
119 |
|
|
120 |
``changed()`` |
|
121 |
Call this when you mutate a mutable value in the session namespace. |
8d2478
|
122 |
See the gotchas below for details on when, and why you should |
CD |
123 |
call this. |
04ebd5
|
124 |
|
CM |
125 |
``invalidate()`` |
|
126 |
Call this when you want to invalidate the session (dump all data, |
|
127 |
and -- perhaps -- set a clearing cookie). |
|
128 |
|
|
129 |
The formal definition of the methods and attributes supported by the |
|
130 |
session object are in the :class:`pyramid.interfaces.ISession` |
|
131 |
documentation. |
|
132 |
|
|
133 |
Some gotchas: |
|
134 |
|
|
135 |
- Keys and values of session data must be *pickleable*. This means, |
461482
|
136 |
typically, that they are instances of basic types of objects, |
04ebd5
|
137 |
such as strings, lists, dictionaries, tuples, integers, etc. If you |
CM |
138 |
place an object in a session data key or value that is not |
|
139 |
pickleable, an error will be raised when the session is serialized. |
|
140 |
|
|
141 |
- If you place a mutable value (for example, a list or a dictionary) |
729fd3
|
142 |
in a session object, and you subsequently mutate that value, you must |
CD |
143 |
call the ``changed()`` method of the session object. In this case, the |
|
144 |
session has no way to know that is was modified. However, when you |
|
145 |
modify a session object directly, such as setting a value (i.e., |
|
146 |
``__setitem__``), or removing a key (e.g., ``del`` or ``pop``), the |
|
147 |
session will automatically know that it needs to re-serialize its |
e667ef
|
148 |
data, thus calling ``changed()`` is unnecessary. There is no harm in |
CD |
149 |
calling ``changed()`` in either case, so when in doubt, call it after |
|
150 |
you've changed sessioning data. |
04ebd5
|
151 |
|
7d4a81
|
152 |
.. index:: |
a5da4d
|
153 |
single: pyramid_redis_sessions |
6ce1e0
|
154 |
single: session factory (alternates) |
7d4a81
|
155 |
|
4df636
|
156 |
.. _using_alternate_session_factories: |
CM |
157 |
|
04ebd5
|
158 |
Using Alternate Session Factories |
CM |
159 |
--------------------------------- |
|
160 |
|
5ac519
|
161 |
The following session factories exist at the time of this writing. |
SP |
162 |
|
|
163 |
======================= ======= ============================= |
|
164 |
Session Factory Backend Description |
|
165 |
======================= ======= ============================= |
|
166 |
pyramid_redis_sessions_ Redis_ Server-side session library |
|
167 |
for Pyramid, using Redis for |
|
168 |
storage. |
|
169 |
pyramid_beaker_ Beaker_ Session factory for Pyramid |
|
170 |
backed by the Beaker |
|
171 |
sessioning system. |
|
172 |
======================= ======= ============================= |
|
173 |
|
|
174 |
.. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions |
|
175 |
.. _Redis: http://redis.io/ |
|
176 |
|
|
177 |
.. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker |
|
178 |
.. _Beaker: http://beaker.readthedocs.org/en/latest/ |
04ebd5
|
179 |
|
7d4a81
|
180 |
.. index:: |
6ce1e0
|
181 |
single: session factory (custom) |
7d4a81
|
182 |
|
04ebd5
|
183 |
Creating Your Own Session Factory |
CM |
184 |
--------------------------------- |
|
185 |
|
|
186 |
If none of the default or otherwise available sessioning |
fd5ae9
|
187 |
implementations for :app:`Pyramid` suit you, you may create your own |
04ebd5
|
188 |
session object by implementing a :term:`session factory`. Your |
CM |
189 |
session factory should return a :term:`session`. The interfaces for |
|
190 |
both types are available in |
|
191 |
:class:`pyramid.interfaces.ISessionFactory` and |
2a1dc5
|
192 |
:class:`pyramid.interfaces.ISession`. You might use the cookie |
04ebd5
|
193 |
implementation in the :mod:`pyramid.session` module as inspiration. |
CM |
194 |
|
b0b9f7
|
195 |
.. index:: |
CD |
196 |
single: flash messages |
|
197 |
|
49d634
|
198 |
.. _flash_messages: |
PE |
199 |
|
b0b9f7
|
200 |
Flash Messages |
CD |
201 |
-------------- |
|
202 |
|
|
203 |
"Flash messages" are simply a queue of message strings stored in the |
|
204 |
:term:`session`. To use flash messaging, you must enable a :term:`session |
|
205 |
factory` as described in :ref:`using_the_default_session_factory` or |
|
206 |
:ref:`using_alternate_session_factories`. |
|
207 |
|
|
208 |
Flash messaging has two main uses: to display a status message only once to |
|
209 |
the user after performing an internal redirect, and to allow generic code to |
|
210 |
log messages for single-time display without having direct access to an HTML |
|
211 |
template. The user interface consists of a number of methods of the |
|
212 |
:term:`session` object. |
6ce1e0
|
213 |
|
CM |
214 |
.. index:: |
|
215 |
single: session.flash |
b0b9f7
|
216 |
|
CD |
217 |
Using the ``session.flash`` Method |
|
218 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
219 |
|
fd3988
|
220 |
To add a message to a flash message queue, use a session object's ``flash()`` |
b0b9f7
|
221 |
method: |
CD |
222 |
|
|
223 |
.. code-block:: python |
|
224 |
|
|
225 |
request.session.flash('mymessage') |
|
226 |
|
fd3988
|
227 |
The ``flash()`` method appends a message to a flash queue, creating the queue |
b0b9f7
|
228 |
if necessary. |
CD |
229 |
|
fd3988
|
230 |
``flash()`` accepts three arguments: |
b0b9f7
|
231 |
|
CD |
232 |
.. method:: flash(message, queue='', allow_duplicate=True) |
|
233 |
|
|
234 |
The ``message`` argument is required. It represents a message you wish to |
|
235 |
later display to a user. It is usually a string but the ``message`` you |
|
236 |
provide is not modified in any way. |
|
237 |
|
fd3988
|
238 |
The ``queue`` argument allows you to choose a queue to which to append |
CD |
239 |
the message you provide. This can be used to push different kinds of |
|
240 |
messages into flash storage for later display in different places on a |
|
241 |
page. You can pass any name for your queue, but it must be a string. |
|
242 |
Each queue is independent, and can be popped by ``pop_flash()`` or |
|
243 |
examined via ``peek_flash()`` separately. ``queue`` defaults to the |
|
244 |
empty string. The empty string represents the default flash message |
|
245 |
queue. |
b0b9f7
|
246 |
|
CD |
247 |
.. code-block:: python |
|
248 |
|
|
249 |
request.session.flash(msg, 'myappsqueue') |
|
250 |
|
|
251 |
The ``allow_duplicate`` argument defaults to ``True``. If this is |
2eefe0
|
252 |
``False``, and you attempt to add a message value which is already |
b0b9f7
|
253 |
present in the queue, it will not be added. |
CD |
254 |
|
6ce1e0
|
255 |
.. index:: |
CM |
256 |
single: session.pop_flash |
|
257 |
|
b0b9f7
|
258 |
Using the ``session.pop_flash`` Method |
CD |
259 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
260 |
|
|
261 |
Once one or more messages have been added to a flash queue by the |
fd3988
|
262 |
``session.flash()`` API, the ``session.pop_flash()`` API can be used to |
abedea
|
263 |
pop an entire queue and return it for use. |
b0b9f7
|
264 |
|
CD |
265 |
To pop a particular queue of messages from the flash object, use the session |
abedea
|
266 |
object's ``pop_flash()`` method. This returns a list of the messages |
CD |
267 |
that were added to the flash queue, and empties the queue. |
b0b9f7
|
268 |
|
CD |
269 |
.. method:: pop_flash(queue='') |
|
270 |
|
88cafd
|
271 |
>>> request.session.flash('info message') |
TL |
272 |
>>> request.session.pop_flash() |
|
273 |
['info message'] |
b0b9f7
|
274 |
|
CD |
275 |
Calling ``session.pop_flash()`` again like above without a corresponding call |
fd3988
|
276 |
to ``session.flash()`` will return an empty list, because the queue has already |
b0b9f7
|
277 |
been popped. |
CD |
278 |
|
88cafd
|
279 |
>>> request.session.flash('info message') |
TL |
280 |
>>> request.session.pop_flash() |
|
281 |
['info message'] |
|
282 |
>>> request.session.pop_flash() |
|
283 |
[] |
6ce1e0
|
284 |
|
CM |
285 |
.. index:: |
|
286 |
single: session.peek_flash |
b0b9f7
|
287 |
|
CD |
288 |
Using the ``session.peek_flash`` Method |
|
289 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
290 |
|
|
291 |
Once one or more messages has been added to a flash queue by the |
fd3988
|
292 |
``session.flash()`` API, the ``session.peek_flash()`` API can be used to |
CD |
293 |
"peek" at that queue. Unlike ``session.pop_flash()``, the queue is not |
|
294 |
popped from flash storage. |
b0b9f7
|
295 |
|
CD |
296 |
.. method:: peek_flash(queue='') |
|
297 |
|
88cafd
|
298 |
>>> request.session.flash('info message') |
TL |
299 |
>>> request.session.peek_flash() |
|
300 |
['info message'] |
|
301 |
>>> request.session.peek_flash() |
|
302 |
['info message'] |
|
303 |
>>> request.session.pop_flash() |
|
304 |
['info message'] |
|
305 |
>>> request.session.peek_flash() |
|
306 |
[] |
b0b9f7
|
307 |
|
CD |
308 |
.. index:: |
|
309 |
single: preventing cross-site request forgery attacks |
|
310 |
single: cross-site request forgery attacks, prevention |
|
311 |
|
|
312 |
Preventing Cross-Site Request Forgery Attacks |
|
313 |
--------------------------------------------- |
|
314 |
|
|
315 |
`Cross-site request forgery |
|
316 |
<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a |
d95a27
|
317 |
phenomenon whereby a user who is logged in to your website might inadvertantly |
LC |
318 |
load a URL because it is linked from, or embedded in, an attacker's website. |
|
319 |
If the URL is one that may modify or delete data, the consequences can be dire. |
b0b9f7
|
320 |
|
d95a27
|
321 |
You can avoid most of these attacks by issuing a unique token to the browser |
LC |
322 |
and then requiring that it be present in all potentially unsafe requests. |
|
323 |
:app:`Pyramid` sessions provide facilities to create and check CSRF tokens. |
|
324 |
|
|
325 |
To use CSRF tokens, you must first enable a :term:`session factory` |
b0b9f7
|
326 |
as described in :ref:`using_the_default_session_factory` or |
CD |
327 |
:ref:`using_alternate_session_factories`. |
|
328 |
|
6ce1e0
|
329 |
.. index:: |
CM |
330 |
single: session.get_csrf_token |
|
331 |
|
b0b9f7
|
332 |
Using the ``session.get_csrf_token`` Method |
CD |
333 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
334 |
|
|
335 |
To get the current CSRF token from the session, use the |
e5f66f
|
336 |
``session.get_csrf_token()`` method. |
b0b9f7
|
337 |
|
CD |
338 |
.. code-block:: python |
|
339 |
|
|
340 |
token = request.session.get_csrf_token() |
|
341 |
|
46d30f
|
342 |
The ``session.get_csrf_token()`` method accepts no arguments. It returns a |
CM |
343 |
CSRF *token* string. If ``session.get_csrf_token()`` or |
d95a27
|
344 |
``session.new_csrf_token()`` was invoked previously for this session, then the |
46d30f
|
345 |
existing token will be returned. If no CSRF token previously existed for |
d95a27
|
346 |
this session, then a new token will be will be set into the session and returned. |
46d30f
|
347 |
The newly created token will be opaque and randomized. |
CM |
348 |
|
|
349 |
You can use the returned token as the value of a hidden field in a form that |
d95a27
|
350 |
posts to a method that requires elevated privileges, or supply it as a request |
009f84
|
351 |
header in AJAX requests. |
LC |
352 |
|
|
353 |
For example, include the CSRF token as a hidden field: |
|
354 |
|
|
355 |
.. code-block:: html |
|
356 |
|
|
357 |
<form method="post" action="/myview"> |
|
358 |
<input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}"> |
|
359 |
<input type="submit" value="Delete Everything"> |
|
360 |
</form> |
|
361 |
|
|
362 |
Or, include it as a header in a jQuery AJAX request: |
|
363 |
|
|
364 |
.. code-block:: javascript |
|
365 |
|
|
366 |
var csrfToken = ${request.session.get_csrf_token()}; |
|
367 |
$.ajax({ |
|
368 |
type: "POST", |
|
369 |
url: "/myview", |
|
370 |
headers: { 'X-CSRF-Token': csrfToken } |
|
371 |
}).done(function() { |
|
372 |
alert("Deleted"); |
|
373 |
}); |
|
374 |
|
|
375 |
|
|
376 |
The handler for the URL that receives the request |
d95a27
|
377 |
should then require that the correct CSRF token is supplied. |
b0b9f7
|
378 |
|
3acee3
|
379 |
Checking CSRF Tokens Manually |
MM |
380 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
b0b9f7
|
381 |
|
d95a27
|
382 |
In request handling code, you can check the presence and validity of a CSRF |
47e852
|
383 |
token with :func:`pyramid.session.check_csrf_token`. If the token is |
3acee3
|
384 |
valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. |
MM |
385 |
Optionally, you can specify ``raises=False`` to have the check return ``False`` |
|
386 |
instead of raising an exception. |
d95a27
|
387 |
|
LC |
388 |
By default, it checks for a GET or POST parameter named ``csrf_token`` or a |
|
389 |
header named ``X-CSRF-Token``. |
b0b9f7
|
390 |
|
009f84
|
391 |
.. code-block:: python |
LC |
392 |
|
3acee3
|
393 |
from pyramid.session import check_csrf_token |
MM |
394 |
|
009f84
|
395 |
def myview(request): |
LC |
396 |
# Require CSRF Token |
3acee3
|
397 |
check_csrf_token(request) |
009f84
|
398 |
|
3acee3
|
399 |
# ... |
009f84
|
400 |
|
6ce1e0
|
401 |
.. index:: |
CM |
402 |
single: session.new_csrf_token |
|
403 |
|
d95a27
|
404 |
Checking CSRF Tokens With A View Predicate |
LC |
405 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
406 |
|
|
407 |
A convenient way to require a valid CSRF Token for a particular view is to |
|
408 |
include ``check_csrf=True`` as a view predicate. |
|
409 |
See :meth:`pyramid.config.Configurator.add_route`. |
|
410 |
|
009f84
|
411 |
.. code-block:: python |
LC |
412 |
|
|
413 |
@view_config(request_method='POST', check_csrf=True, ...) |
|
414 |
def myview(request): |
|
415 |
... |
|
416 |
|
d95a27
|
417 |
|
46d30f
|
418 |
Using the ``session.new_csrf_token`` Method |
CM |
419 |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
420 |
|
d95a27
|
421 |
To explicitly create a new CSRF token, use the |
46d30f
|
422 |
``session.new_csrf_token()`` method. This differs only from |
CM |
423 |
``session.get_csrf_token()`` inasmuch as it clears any existing CSRF token, |
|
424 |
creates a new CSRF token, sets the token into the session, and returns the |
|
425 |
token. |
|
426 |
|
|
427 |
.. code-block:: python |
|
428 |
|
|
429 |
token = request.session.new_csrf_token() |
|
430 |
|
|
431 |
|