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
|
.. 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.
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)
# ...
.. index::
single: session.new_csrf_token
Checking CSRF Tokens with a View Predicate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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`.
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()
|