Bert JW Regeer
2016-04-13 d26e3af4b220d3794c8e40103eb8bd86fd68372d
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
.. index::
   single: session
 
.. _sessions_chapter:
 
Sessions
========
 
A :term:`session` is a namespace which is valid for some period of continual
activity that can be used to represent a user's interaction with a web
application.
 
This chapter describes how to configure sessions, what session implementations
:app:`Pyramid` provides out of the box, how to store and retrieve data from
sessions, and two session-specific features: flash messages, and cross-site
request forgery attack prevention.
 
.. index::
   single: session factory (default)
 
.. _using_the_default_session_factory:
 
Using the Default Session Factory
---------------------------------
 
In order to use sessions, you must set up a :term:`session factory` during your
:app:`Pyramid` configuration.
 
A very basic, insecure sample session factory implementation is provided in the
:app:`Pyramid` core.  It uses a cookie to store session information.  This
implementation has the following limitations:
 
- The session information in the cookies used by this implementation is *not*
  encrypted, so it can be viewed by anyone with access to the cookie storage of
  the user's browser or anyone with access to the network along which the
  cookie travels.
 
- The maximum number of bytes that are storable in a serialized representation
  of the session is fewer than 4000.  This is suitable only for very small data
  sets.
 
It is digitally signed, however, and thus its data cannot easily be tampered
with.
 
You can configure this session factory in your :app:`Pyramid` application by
using the :meth:`pyramid.config.Configurator.set_session_factory` method.
 
.. code-block:: python
   :linenos:
 
   from pyramid.session import SignedCookieSessionFactory
   my_session_factory = SignedCookieSessionFactory('itsaseekreet')
 
   from pyramid.config import Configurator
   config = Configurator()
   config.set_session_factory(my_session_factory)
 
.. warning::
 
   By default the :func:`~pyramid.session.SignedCookieSessionFactory`
   implementation is *unencrypted*.  You should not use it when you keep
   sensitive information in the session object, as the information can be
   easily read by both users of your application and third parties who have
   access to your users' network traffic.  And, if you use this sessioning
   implementation, and you inadvertently create a cross-site scripting
   vulnerability in your application, because the session data is stored
   unencrypted in a cookie, it will also be easier for evildoers to obtain the
   current user's cross-site scripting token.  In short, use a different
   session factory implementation (preferably one which keeps session data on
   the server) for anything but the most basic of applications where "session
   security doesn't matter", and you are sure your application has no
   cross-site scripting vulnerabilities.
 
.. index::
   single: session object
 
Using a Session Object
----------------------
 
Once a session factory has been configured for your application, you can access
session objects provided by the session factory via the ``session`` attribute
of any :term:`request` object.  For example:
 
.. code-block:: python
   :linenos:
 
   from pyramid.response import Response
 
   def myview(request):
       session = request.session
       if 'abc' in session:
           session['fred'] = 'yes'
       session['abc'] = '123'
       if 'fred' in session:
           return Response('Fred was in the session')
       else:
           return Response('Fred was not in the session')
 
The first time this view is invoked produces ``Fred was not in the session``.
Subsequent invocations produce ``Fred was in the session``, assuming of course
that the client side maintains the session's identity across multiple requests.
 
You can use a session much like a Python dictionary.  It supports all
dictionary methods, along with some extra attributes and methods.
 
Extra attributes:
 
``created``
  An integer timestamp indicating the time that this session was created.
 
``new``
  A boolean.  If ``new`` is True, this session is new.  Otherwise, it has been
  constituted from data that was already serialized.
 
Extra methods:
 
``changed()``
  Call this when you mutate a mutable value in the session namespace. See the
  gotchas below for details on when and why you should call this.
 
``invalidate()``
  Call this when you want to invalidate the session (dump all data, and perhaps
  set a clearing cookie).
 
The formal definition of the methods and attributes supported by the session
object are in the :class:`pyramid.interfaces.ISession` documentation.
 
Some gotchas:
 
- Keys and values of session data must be *pickleable*.  This means, typically,
  that they are instances of basic types of objects, such as strings, lists,
  dictionaries, tuples, integers, etc.  If you place an object in a session
  data key or value that is not pickleable, an error will be raised when the
  session is serialized.
 
- If you place a mutable value (for example, a list or a dictionary) in a
  session object, and you subsequently mutate that value, you must call the
  ``changed()`` method of the session object. In this case, the session has no
  way to know that it was modified.  However, when you modify a session object
  directly, such as setting a value (i.e., ``__setitem__``), or removing a key
  (e.g., ``del`` or ``pop``), the session will automatically know that it needs
  to re-serialize its data, thus calling ``changed()`` is unnecessary. There is
  no harm in calling ``changed()`` in either case, so when in doubt, call it
  after you've changed sessioning data.
 
.. index::
   single: pyramid_redis_sessions
   single: session factory (alternates)
 
.. _using_alternate_session_factories:
 
Using Alternate Session Factories
---------------------------------
 
The following session factories exist at the time of this writing.
 
======================= ======= =============================
Session Factory         Backend   Description
======================= ======= =============================
pyramid_redis_sessions_ Redis_  Server-side session library
                                for Pyramid, using Redis for
                                storage.
pyramid_beaker_         Beaker_ Session factory for Pyramid
                                backed by the Beaker
                                sessioning system.
======================= ======= =============================
 
.. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions
.. _Redis: http://redis.io/
 
.. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker
.. _Beaker: http://beaker.readthedocs.org/en/latest/
 
.. index::
   single: session factory (custom)
 
Creating Your Own Session Factory
---------------------------------
 
If none of the default or otherwise available sessioning implementations for
:app:`Pyramid` suit you, you may create your own session object by implementing
a :term:`session factory`.  Your session factory should return a
:term:`session`.  The interfaces for both types are available in
:class:`pyramid.interfaces.ISessionFactory` and
:class:`pyramid.interfaces.ISession`. You might use the cookie implementation
in the :mod:`pyramid.session` module as inspiration.
 
.. index::
   single: flash messages
 
.. _flash_messages:
 
Flash Messages
--------------
 
"Flash messages" are simply a queue of message strings stored in the
:term:`session`.  To use flash messaging, you must enable a :term:`session
factory` as described in :ref:`using_the_default_session_factory` or
:ref:`using_alternate_session_factories`.
 
Flash messaging has two main uses: to display a status message only once to the
user after performing an internal redirect, and to allow generic code to log
messages for single-time display without having direct access to an HTML
template. The user interface consists of a number of methods of the
:term:`session` object.
 
.. index::
   single: session.flash
 
Using the ``session.flash`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
To add a message to a flash message queue, use a session object's ``flash()``
method:
 
.. code-block:: python
 
   request.session.flash('mymessage')
 
The ``flash()`` method appends a message to a flash queue, creating the queue
if necessary.
 
``flash()`` accepts three arguments:
 
.. method:: flash(message, queue='', allow_duplicate=True)
 
The ``message`` argument is required.  It represents a message you wish to
later display to a user.  It is usually a string but the ``message`` you
provide is not modified in any way.
 
The ``queue`` argument allows you to choose a queue to which to append the
message you provide.  This can be used to push different kinds of messages into
flash storage for later display in different places on a page.  You can pass
any name for your queue, but it must be a string. Each queue is independent,
and can be popped by ``pop_flash()`` or examined via ``peek_flash()``
separately.  ``queue`` defaults to the empty string.  The empty string
represents the default flash message queue.
 
.. code-block:: python
 
   request.session.flash(msg, 'myappsqueue')
 
The ``allow_duplicate`` argument defaults to ``True``.  If this is ``False``,
and you attempt to add a message value which is already present in the queue,
it will not be added.
 
.. index::
   single: session.pop_flash
 
Using the ``session.pop_flash`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Once one or more messages have been added to a flash queue by the
``session.flash()`` API, the ``session.pop_flash()`` API can be used to pop an
entire queue and return it for use.
 
To pop a particular queue of messages from the flash object, use the session
object's ``pop_flash()`` method. This returns a list of the messages that were
added to the flash queue, and empties the queue.
 
.. method:: pop_flash(queue='')
 
>>> request.session.flash('info message')
>>> request.session.pop_flash()
['info message']
 
Calling ``session.pop_flash()`` again like above without a corresponding call
to ``session.flash()`` will return an empty list, because the queue has already
been popped.
 
>>> request.session.flash('info message')
>>> request.session.pop_flash()
['info message']
>>> request.session.pop_flash()
[]
 
.. index::
   single: session.peek_flash
 
Using the ``session.peek_flash`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Once one or more messages have been added to a flash queue by the
``session.flash()`` API, the ``session.peek_flash()`` API can be used to "peek"
at that queue.  Unlike ``session.pop_flash()``, the queue is not popped from
flash storage.
 
.. method:: peek_flash(queue='')
 
>>> request.session.flash('info message')
>>> request.session.peek_flash()
['info message']
>>> request.session.peek_flash()
['info message']
>>> request.session.pop_flash()
['info message']
>>> request.session.peek_flash()
[]
 
.. index::
   single: preventing cross-site request forgery attacks
   single: cross-site request forgery attacks, prevention
 
Preventing Cross-Site Request Forgery Attacks
---------------------------------------------
 
`Cross-site request forgery
<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
phenomenon whereby a user who is logged in to your website might inadvertantly
load a URL because it is linked from, or embedded in, an attacker's website.
If the URL is one that may modify or delete data, the consequences can be dire.
 
You can avoid most of these attacks by issuing a unique token to the browser
and then requiring that it be present in all potentially unsafe requests.
:app:`Pyramid` sessions provide facilities to create and check CSRF tokens.
 
To use CSRF tokens, you must first enable a :term:`session factory` as
described in :ref:`using_the_default_session_factory` or
:ref:`using_alternate_session_factories`.
 
.. index::
   single: session.get_csrf_token
 
Using the ``session.get_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
To get the current CSRF token from the session, use the
``session.get_csrf_token()`` method.
 
.. code-block:: python
 
   token = request.session.get_csrf_token()
 
The ``session.get_csrf_token()`` method accepts no arguments.  It returns a
CSRF *token* string. If ``session.get_csrf_token()`` or
``session.new_csrf_token()`` was invoked previously for this session, then the
existing token will be returned.  If no CSRF token previously existed for this
session, then a new token will be set into the session and returned.  The newly
created token will be opaque and randomized.
 
You can use the returned token as the value of a hidden field in a form that
posts to a method that requires elevated privileges, or supply it as a request
header in AJAX requests.
 
For example, include the CSRF token as a hidden field:
 
.. code-block:: html
 
    <form method="post" action="/myview">
      <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">
      <input type="submit" value="Delete Everything">
    </form>
 
Or include it as a header in a jQuery AJAX request:
 
.. code-block:: javascript
 
    var csrfToken = ${request.session.get_csrf_token()};
    $.ajax({
      type: "POST",
      url: "/myview",
      headers: { 'X-CSRF-Token': csrfToken }
    }).done(function() {
      alert("Deleted");
    });
 
The handler for the URL that receives the request should then require that the
correct CSRF token is supplied.
 
.. index::
   single: session.new_csrf_token
 
Using the ``session.new_csrf_token`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
method.  This differs only from ``session.get_csrf_token()`` inasmuch as it
clears any existing CSRF token, creates a new CSRF token, sets the token into
the session, and returns the token.
 
.. code-block:: python
 
   token = request.session.new_csrf_token()
 
Checking CSRF Tokens Manually
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
In request handling code, you can check the presence and validity of a CSRF
token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it
will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally,
you can specify ``raises=False`` to have the check return ``False`` instead of
raising an exception.
 
By default, it checks for a GET or POST parameter named ``csrf_token`` or a
header named ``X-CSRF-Token``.
 
.. code-block:: python
 
    from pyramid.session import check_csrf_token
 
    def myview(request):
        # Require CSRF Token
        check_csrf_token(request)
 
        # ...
 
.. _auto_csrf_checking:
 
Checking CSRF Tokens Automatically
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
.. versionadded:: 1.7
 
:app:`Pyramid` supports automatically checking CSRF tokens on POST requests.
Any other request may be checked manually. This feature can be turned on
globally for an application using the ``pyramid.require_default_csrf`` setting.
 
If the ``pyramid.required_default_csrf`` setting is a :term:`truthy string` or
``True`` then the default CSRF token parameter will be ``csrf_token``. If a
different token is desired, it may be passed as the value. Finally, a
:term:`falsey string` or ``False`` will turn off automatic CSRF checking
globally on every POST request.
 
No matter what, CSRF checking may be explicitly enabled or disabled on a
per-view basis using the ``require_csrf`` view option. This option is of the
same format as the ``pyramid.require_default_csrf`` setting, accepting strings
or boolean values.
 
If ``require_csrf`` is ``True`` but does not explicitly define a token to
check, then the token name is pulled from whatever was set in the
``pyramid.require_default_csrf`` setting. Finally, if that setting does not
explicitly define a token, then ``csrf_token`` is the token required. This token
name will be required in ``request.params`` which is a combination of the
query string and a submitted form body.
 
It is always possible to pass the token in the ``X-CSRF-Token`` header as well.
There is currently no way to define an alternate name for this header without
performing CSRF checking manually.
 
If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` exception
will be raised. This exception may be caught and handled by an
:term:`exception view` but, by default, will result in a ``400 Bad Request``
response being sent to the client.
 
Checking CSRF Tokens with a View Predicate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
.. deprecated:: 1.7
   Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
   to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
 
A convenient way to require a valid CSRF token for a particular view is to
include ``check_csrf=True`` as a view predicate. See
:meth:`pyramid.config.Configurator.add_view`.
 
.. code-block:: python
 
    @view_config(request_method='POST', check_csrf=True, ...)
    def myview(request):
        ...
 
.. note::
   A mismatch of a CSRF token is treated like any other predicate miss, and the
   predicate system, when it doesn't find a view, raises ``HTTPNotFound``
   instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
   from calling :func:`pyramid.session.check_csrf_token`.