Pavlo Kapyshin
2015-01-07 47e85294779814f14e02327eb4d378197bbaeb29
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