Michael Merickel
2018-10-26 035f6cf8238211d097c991677fde6b5bc046a57b
commit | author | age
968209 1 import base64
319793 2 import binascii
10c685 3 import hashlib
319793 4 import hmac
CM 5 import os
10c685 6 import time
c31883 7 import warnings
968209 8
c151ad 9 from zope.deprecation import deprecated
3b7334 10 from zope.interface import implementer
6af3eb 11
0c29cf 12 from webob.cookies import JSONSerializer, SignedSerializer
8134a7 13
0c29cf 14 from pyramid.compat import pickle, PY2, text_, bytes_, native_
MM 15 from pyramid.csrf import check_csrf_origin, check_csrf_token
0c1c39 16
6af3eb 17 from pyramid.interfaces import ISession
a2c7c7 18 from pyramid.util import strings_differ
MW 19
6af3eb 20
968209 21 def manage_accessed(wrapped):
4fade6 22     """ Decorator which causes a cookie to be renewed when an accessor
MM 23     method is called."""
0c29cf 24
968209 25     def accessed(session, *arg, **kw):
4fade6 26         session.accessed = now = int(time.time())
b2dd47 27         if session._reissue_time is not None:
MM 28             if now - session.renewed > session._reissue_time:
29                 session.changed()
968209 30         return wrapped(session, *arg, **kw)
0c29cf 31
968209 32     accessed.__doc__ = wrapped.__doc__
CM 33     return accessed
0c29cf 34
4fade6 35
MM 36 def manage_changed(wrapped):
37     """ Decorator which causes a cookie to be set when a setter method
38     is called."""
0c29cf 39
4fade6 40     def changed(session, *arg, **kw):
MM 41         session.accessed = int(time.time())
42         session.changed()
43         return wrapped(session, *arg, **kw)
0c29cf 44
4fade6 45     changed.__doc__ = wrapped.__doc__
MM 46     return changed
0c29cf 47
968209 48
cf46a1 49 def signed_serialize(data, secret):
IW 50     """ Serialize any pickleable structure (``data``) and sign it
51     using the ``secret`` (must be a string).  Return the
52     serialization, which includes the signature as its first 40 bytes.
53     The ``signed_deserialize`` method will deserialize such a value.
54
55     This function is useful for creating signed cookies.  For example:
56
57     .. code-block:: python
58
59        cookieval = signed_serialize({'a':1}, 'secret')
60        response.set_cookie('signed_cookie', cookieval)
ba5ca6 61
MM 62     .. deprecated:: 1.10
63
64        This function will be removed in :app:`Pyramid` 2.0. It is using
65        pickle-based serialization, which is considered vulnerable to remote
66        code execution attacks and will no longer be used by the default
67        session factories at that time.
68
cf46a1 69     """
IW 70     pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
cf026e 71     try:
MM 72         # bw-compat with pyramid <= 1.5b1 where latin1 is the default
73         secret = bytes_(secret)
74     except UnicodeEncodeError:
75         secret = bytes_(secret, 'utf-8')
76     sig = hmac.new(secret, pickled, hashlib.sha1).hexdigest()
cf46a1 77     return sig + native_(base64.b64encode(pickled))
ba5ca6 78
0c29cf 79
ba5ca6 80 deprecated(
MM 81     'signed_serialize',
82     'This function will be removed in Pyramid 2.0. It is using pickle-based '
83     'serialization, which is considered vulnerable to remote code execution '
84     'attacks.',
85 )
0c29cf 86
cf46a1 87
IW 88 def signed_deserialize(serialized, secret, hmac=hmac):
89     """ Deserialize the value returned from ``signed_serialize``.  If
90     the value cannot be deserialized for any reason, a
91     :exc:`ValueError` exception will be raised.
92
93     This function is useful for deserializing a signed cookie value
94     created by ``signed_serialize``.  For example:
95
96     .. code-block:: python
97
98        cookieval = request.cookies['signed_cookie']
99        data = signed_deserialize(cookieval, 'secret')
ba5ca6 100
MM 101     .. deprecated:: 1.10
102
103        This function will be removed in :app:`Pyramid` 2.0. It is using
104        pickle-based serialization, which is considered vulnerable to remote
105        code execution attacks and will no longer be used by the default
106        session factories at that time.
cf46a1 107     """
IW 108     # hmac parameterized only for unit tests
109     try:
0c29cf 110         input_sig, pickled = (
MM 111             bytes_(serialized[:40]),
112             base64.b64decode(bytes_(serialized[40:])),
113         )
cf46a1 114     except (binascii.Error, TypeError) as e:
IW 115         # Badly formed data can make base64 die
116         raise ValueError('Badly formed base64 data: %s' % e)
117
cf026e 118     try:
MM 119         # bw-compat with pyramid <= 1.5b1 where latin1 is the default
120         secret = bytes_(secret)
121     except UnicodeEncodeError:
122         secret = bytes_(secret, 'utf-8')
123     sig = bytes_(hmac.new(secret, pickled, hashlib.sha1).hexdigest())
cf46a1 124
IW 125     # Avoid timing attacks (see
126     # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
127     if strings_differ(sig, input_sig):
128         raise ValueError('Invalid signature')
129
130     return pickle.loads(pickled)
0c29cf 131
cf46a1 132
ba5ca6 133 deprecated(
MM 134     'signed_deserialize',
135     'This function will be removed in Pyramid 2.0. It is using pickle-based '
136     'serialization, which is considered vulnerable to remote code execution '
137     'attacks.',
138 )
139
68c00d 140
8134a7 141 class PickleSerializer(object):
ee9c62 142     """ A serializer that uses the pickle protocol to dump Python
MM 143     data to bytes.
144
145     This is the default serializer used by Pyramid.
146
147     ``protocol`` may be specified to control the version of pickle used.
148     Defaults to :attr:`pickle.HIGHEST_PROTOCOL`.
149
150     """
0c29cf 151
ee9c62 152     def __init__(self, protocol=pickle.HIGHEST_PROTOCOL):
MM 153         self.protocol = protocol
154
8134a7 155     def loads(self, bstruct):
ee9c62 156         """Accept bytes and return a Python object."""
a9cd71 157         try:
MM 158             return pickle.loads(bstruct)
159         # at least ValueError, AttributeError, ImportError but more to be safe
160         except Exception:
161             raise ValueError
8134a7 162
CM 163     def dumps(self, appstruct):
ee9c62 164         """Accept a Python object and return bytes."""
MM 165         return pickle.dumps(appstruct, self.protocol)
8134a7 166
c31883 167
MM 168 JSONSerializer = JSONSerializer  # api
169
170
4fade6 171 def BaseCookieSessionFactory(
8134a7 172     serializer,
4fade6 173     cookie_name='session',
MM 174     max_age=None,
175     path='/',
176     domain=None,
177     secure=False,
178     httponly=False,
6c8ad4 179     samesite='Lax',
4fade6 180     timeout=1200,
MM 181     reissue_time=0,
182     set_on_exception=True,
0c29cf 183 ):
4fade6 184     """
MM 185     Configure a :term:`session factory` which will provide cookie-based
9536f9 186     sessions.  The return value of this function is a :term:`session factory`,
CM 187     which may be provided as the ``session_factory`` argument of a
188     :class:`pyramid.config.Configurator` constructor, or used as the
189     ``session_factory`` argument of the
4fade6 190     :meth:`pyramid.config.Configurator.set_session_factory` method.
MM 191
192     The session factory returned by this function will create sessions
193     which are limited to storing fewer than 4000 bytes of data (as the
194     payload must fit into a single cookie).
195
196     .. warning:
197
198        This class provides no protection from tampering and is only intended
199        to be used by framework authors to create their own cookie-based
200        session factories.
201
202     Parameters:
203
8134a7 204     ``serializer``
1098ac 205       An object with two methods: ``loads`` and ``dumps``.  The ``loads``
MM 206       method should accept bytes and return a Python object.  The ``dumps``
207       method should accept a Python object and return bytes.  A ``ValueError``
208       should be raised for malformed inputs.
4fade6 209
MM 210     ``cookie_name``
211       The name of the cookie used for sessioning. Default: ``'session'``.
212
213     ``max_age``
214       The maximum age of the cookie used for sessioning (in seconds).
215       Default: ``None`` (browser scope).
216
217     ``path``
218       The path used for the session cookie. Default: ``'/'``.
219
220     ``domain``
221       The domain used for the session cookie.  Default: ``None`` (no domain).
222
223     ``secure``
224       The 'secure' flag of the session cookie. Default: ``False``.
225
226     ``httponly``
227       Hide the cookie from Javascript by setting the 'HttpOnly' flag of the
228       session cookie. Default: ``False``.
229
2d9390 230     ``samesite``
beafcc 231       The 'samesite' option of the session cookie. Set the value to ``None``
MM 232       to turn off the samesite option.  Default: ``'Lax'``.
2d9390 233
4fade6 234     ``timeout``
MM 235       A number of seconds of inactivity before a session times out. If
89dc46 236       ``None`` then the cookie never expires. This lifetime only applies
MM 237       to the *value* within the cookie. Meaning that if the cookie expires
238       due to a lower ``max_age``, then this setting has no effect.
239       Default: ``1200``.
4fade6 240
MM 241     ``reissue_time``
242       The number of seconds that must pass before the cookie is automatically
243       reissued as the result of a request which accesses the session. The
244       duration is measured as the number of seconds since the last session
245       cookie was issued and 'now'.  If this value is ``0``, a new cookie
89dc46 246       will be reissued on every request accessing the session. If ``None``
4fade6 247       then the cookie's lifetime will never be extended.
MM 248
249       A good rule of thumb: if you want auto-expired cookies based on
250       inactivity: set the ``timeout`` value to 1200 (20 mins) and set the
251       ``reissue_time`` value to perhaps a tenth of the ``timeout`` value
252       (120 or 2 mins).  It's nonsensical to set the ``timeout`` value lower
253       than the ``reissue_time`` value, as the ticket will never be reissued.
254       However, such a configuration is not explicitly prevented.
255
256       Default: ``0``.
257
258     ``set_on_exception``
259       If ``True``, set a session cookie even if an exception occurs
260       while rendering a view. Default: ``True``.
261
262     .. versionadded: 1.5a3
beafcc 263
MM 264     .. versionchanged: 1.10
265
266        Added the ``samesite`` option and made the default ``'Lax'``.
4fade6 267     """
MM 268
269     @implementer(ISession)
270     class CookieSession(dict):
271         """ Dictionary-like session object """
272
273         # configuration parameters
274         _cookie_name = cookie_name
fa7886 275         _cookie_max_age = max_age if max_age is None else int(max_age)
4fade6 276         _cookie_path = path
MM 277         _cookie_domain = domain
278         _cookie_secure = secure
279         _cookie_httponly = httponly
2d9390 280         _cookie_samesite = samesite
4fade6 281         _cookie_on_exception = set_on_exception
67ff3b 282         _timeout = timeout if timeout is None else int(timeout)
0c29cf 283         _reissue_time = (
MM 284             reissue_time if reissue_time is None else int(reissue_time)
285         )
4fade6 286
MM 287         # dirty flag
288         _dirty = False
289
290         def __init__(self, request):
291             self.request = request
292             now = time.time()
293             created = renewed = now
294             new = True
295             value = None
296             state = {}
297             cookieval = request.cookies.get(self._cookie_name)
298             if cookieval is not None:
299                 try:
8134a7 300                     value = serializer.loads(bytes_(cookieval))
4fade6 301                 except ValueError:
b0b09c 302                     # the cookie failed to deserialize, dropped
4fade6 303                     value = None
MM 304
305             if value is not None:
306                 try:
8f4fbd 307                     # since the value is not necessarily signed, we have
MM 308                     # to unpack it a little carefully
309                     rval, cval, sval = value
310                     renewed = float(rval)
311                     created = float(cval)
312                     state = sval
4fade6 313                     new = False
8f4fbd 314                 except (TypeError, ValueError):
b0b09c 315                     # value failed to unpack properly or renewed was not
MM 316                     # a numeric type so we'll fail deserialization here
4fade6 317                     state = {}
MM 318
8f4fbd 319             if self._timeout is not None:
MM 320                 if now - renewed > self._timeout:
321                     # expire the session because it was not renewed
322                     # before the timeout threshold
4fade6 323                     state = {}
MM 324
325             self.created = created
326             self.accessed = renewed
327             self.renewed = renewed
328             self.new = new
329             dict.__init__(self, state)
330
331         # ISession methods
332         def changed(self):
333             if not self._dirty:
334                 self._dirty = True
0c29cf 335
4fade6 336                 def set_cookie_callback(request, response):
MM 337                     self._set_cookie(response)
0c29cf 338                     self.request = None  # explicitly break cycle for gc
MM 339
4fade6 340                 self.request.add_response_callback(set_cookie_callback)
MM 341
342         def invalidate(self):
0c29cf 343             self.clear()  # XXX probably needs to unset cookie
4fade6 344
MM 345         # non-modifying dictionary methods
346         get = manage_accessed(dict.get)
347         __getitem__ = manage_accessed(dict.__getitem__)
348         items = manage_accessed(dict.items)
349         values = manage_accessed(dict.values)
350         keys = manage_accessed(dict.keys)
351         __contains__ = manage_accessed(dict.__contains__)
352         __len__ = manage_accessed(dict.__len__)
353         __iter__ = manage_accessed(dict.__iter__)
354
bc37a5 355         if PY2:
4fade6 356             iteritems = manage_accessed(dict.iteritems)
MM 357             itervalues = manage_accessed(dict.itervalues)
358             iterkeys = manage_accessed(dict.iterkeys)
359             has_key = manage_accessed(dict.has_key)
360
361         # modifying dictionary methods
362         clear = manage_changed(dict.clear)
363         update = manage_changed(dict.update)
364         setdefault = manage_changed(dict.setdefault)
365         pop = manage_changed(dict.pop)
366         popitem = manage_changed(dict.popitem)
367         __setitem__ = manage_changed(dict.__setitem__)
368         __delitem__ = manage_changed(dict.__delitem__)
369
370         # flash API methods
371         @manage_changed
372         def flash(self, msg, queue='', allow_duplicate=True):
373             storage = self.setdefault('_f_' + queue, [])
374             if allow_duplicate or (msg not in storage):
375                 storage.append(msg)
376
377         @manage_changed
378         def pop_flash(self, queue=''):
379             storage = self.pop('_f_' + queue, [])
380             return storage
381
382         @manage_accessed
383         def peek_flash(self, queue=''):
384             storage = self.get('_f_' + queue, [])
385             return storage
386
387         # CSRF API methods
388         @manage_changed
389         def new_csrf_token(self):
390             token = text_(binascii.hexlify(os.urandom(20)))
391             self['_csrft_'] = token
392             return token
393
394         @manage_accessed
395         def get_csrf_token(self):
396             token = self.get('_csrft_', None)
397             if token is None:
398                 token = self.new_csrf_token()
399             return token
400
401         # non-API methods
402         def _set_cookie(self, response):
403             if not self._cookie_on_exception:
404                 exception = getattr(self.request, 'exception', None)
0c29cf 405                 if (
MM 406                     exception is not None
407                 ):  # dont set a cookie during exceptions
4fade6 408                     return False
0c29cf 409             cookieval = native_(
MM 410                 serializer.dumps((self.accessed, self.created, dict(self)))
411             )
4fade6 412             if len(cookieval) > 4064:
MM 413                 raise ValueError(
0c29cf 414                     'Cookie value is too long to store (%s bytes)'
MM 415                     % len(cookieval)
416                 )
4fade6 417             response.set_cookie(
MM 418                 self._cookie_name,
419                 value=cookieval,
420                 max_age=self._cookie_max_age,
421                 path=self._cookie_path,
422                 domain=self._cookie_domain,
423                 secure=self._cookie_secure,
424                 httponly=self._cookie_httponly,
2d9390 425                 samesite=self._cookie_samesite,
0c29cf 426             )
4fade6 427             return True
MM 428
429     return CookieSession
968209 430
c151ad 431
MM 432 def UnencryptedCookieSessionFactoryConfig(
433     secret,
434     timeout=1200,
435     cookie_name='session',
436     cookie_max_age=None,
437     cookie_path='/',
438     cookie_domain=None,
439     cookie_secure=False,
440     cookie_httponly=False,
6c8ad4 441     cookie_samesite='Lax',
c151ad 442     cookie_on_exception=True,
MM 443     signed_serialize=signed_serialize,
444     signed_deserialize=signed_deserialize,
0c29cf 445 ):
c151ad 446     """
MM 447     .. deprecated:: 1.5
448         Use :func:`pyramid.session.SignedCookieSessionFactory` instead.
449         Caveat: Cookies generated using ``SignedCookieSessionFactory`` are not
450         compatible with cookies generated using
451         ``UnencryptedCookieSessionFactory``, so existing user session data
452         will be destroyed if you switch to it.
13734a 453
c151ad 454     Configure a :term:`session factory` which will provide unencrypted
MM 455     (but signed) cookie-based sessions.  The return value of this
456     function is a :term:`session factory`, which may be provided as
457     the ``session_factory`` argument of a
458     :class:`pyramid.config.Configurator` constructor, or used
459     as the ``session_factory`` argument of the
460     :meth:`pyramid.config.Configurator.set_session_factory`
461     method.
462
463     The session factory returned by this function will create sessions
464     which are limited to storing fewer than 4000 bytes of data (as the
465     payload must fit into a single cookie).
466
467     Parameters:
468
469     ``secret``
470       A string which is used to sign the cookie.
471
472     ``timeout``
473       A number of seconds of inactivity before a session times out.
474
475     ``cookie_name``
476       The name of the cookie used for sessioning.
477
478     ``cookie_max_age``
479       The maximum age of the cookie used for sessioning (in seconds).
480       Default: ``None`` (browser scope).
481
482     ``cookie_path``
483       The path used for the session cookie.
484
485     ``cookie_domain``
486       The domain used for the session cookie.  Default: ``None`` (no domain).
487
488     ``cookie_secure``
489       The 'secure' flag of the session cookie.
490
491     ``cookie_httponly``
492       The 'httpOnly' flag of the session cookie.
493
2d9390 494     ``cookie_samesite``
beafcc 495       The 'samesite' option of the session cookie. Set the value to ``None``
MM 496       to turn off the samesite option.  Default: ``'Lax'``.
2d9390 497
c151ad 498     ``cookie_on_exception``
MM 499       If ``True``, set a session cookie even if an exception occurs
500       while rendering a view.
501
502     ``signed_serialize``
503       A callable which takes more or less arbitrary Python data structure and
504       a secret and returns a signed serialization in bytes.
505       Default: ``signed_serialize`` (using pickle).
506
507     ``signed_deserialize``
508       A callable which takes a signed and serialized data structure in bytes
509       and a secret and returns the original data structure if the signature
510       is valid. Default: ``signed_deserialize`` (using pickle).
beafcc 511
MM 512     .. versionchanged: 1.10
513
514        Added the ``samesite`` option and made the default ``'Lax'``.
c151ad 515     """
MM 516
517     class SerializerWrapper(object):
518         def __init__(self, secret):
519             self.secret = secret
13734a 520
c151ad 521         def loads(self, bstruct):
MM 522             return signed_deserialize(bstruct, secret)
523
524         def dumps(self, appstruct):
525             return signed_serialize(appstruct, secret)
526
527     serializer = SerializerWrapper(secret)
528
529     return BaseCookieSessionFactory(
530         serializer,
531         cookie_name=cookie_name,
532         max_age=cookie_max_age,
533         path=cookie_path,
534         domain=cookie_domain,
535         secure=cookie_secure,
536         httponly=cookie_httponly,
2d9390 537         samesite=cookie_samesite,
c151ad 538         timeout=timeout,
0c29cf 539         reissue_time=0,  # to keep session.accessed == session.renewed
c151ad 540         set_on_exception=cookie_on_exception,
MM 541     )
0c29cf 542
c151ad 543
MM 544 deprecated(
545     'UnencryptedCookieSessionFactoryConfig',
546     'The UnencryptedCookieSessionFactoryConfig callable is deprecated as of '
547     'Pyramid 1.5.  Use ``pyramid.session.SignedCookieSessionFactory`` instead.'
548     ' Caveat: Cookies generated using SignedCookieSessionFactory are not '
549     'compatible with cookies generated using UnencryptedCookieSessionFactory, '
0c29cf 550     'so existing user session data will be destroyed if you switch to it.',
MM 551 )
c151ad 552
c31883 553
3a6cbc 554 def SignedCookieSessionFactory(
MM 555     secret,
556     cookie_name='session',
4fade6 557     max_age=None,
MM 558     path='/',
559     domain=None,
560     secure=False,
561     httponly=False,
6c8ad4 562     samesite='Lax',
4fade6 563     set_on_exception=True,
MM 564     timeout=1200,
565     reissue_time=0,
566     hashalg='sha512',
b0b09c 567     salt='pyramid.session.',
8134a7 568     serializer=None,
0c29cf 569 ):
3a6cbc 570     """
9536f9 571     .. versionadded:: 1.5
13734a 572
3a6cbc 573     Configure a :term:`session factory` which will provide signed
MM 574     cookie-based sessions.  The return value of this
575     function is a :term:`session factory`, which may be provided as
576     the ``session_factory`` argument of a
577     :class:`pyramid.config.Configurator` constructor, or used
578     as the ``session_factory`` argument of the
579     :meth:`pyramid.config.Configurator.set_session_factory`
580     method.
968209 581
3a6cbc 582     The session factory returned by this function will create sessions
MM 583     which are limited to storing fewer than 4000 bytes of data (as the
584     payload must fit into a single cookie).
815955 585
3a6cbc 586     Parameters:
968209 587
3a6cbc 588     ``secret``
b0b09c 589       A string which is used to sign the cookie. The secret should be at
MM 590       least as long as the block size of the selected hash algorithm. For
13734a 591       ``sha512`` this would mean a 512 bit (64 character) secret.  It should
e521f1 592       be unique within the set of secret values provided to Pyramid for
CM 593       its various subsystems (see :ref:`admonishment_against_secret_sharing`).
968209 594
3a6cbc 595     ``hashalg``
MM 596       The HMAC digest algorithm to use for signing. The algorithm must be
597       supported by the :mod:`hashlib` library. Default: ``'sha512'``.
968209 598
b0b09c 599     ``salt``
MM 600       A namespace to avoid collisions between different uses of a shared
601       secret. Reusing a secret for different parts of an application is
2dea18 602       strongly discouraged (see :ref:`admonishment_against_secret_sharing`).
7c756b 603       Default: ``'pyramid.session.'``.
968209 604
3a6cbc 605     ``cookie_name``
MM 606       The name of the cookie used for sessioning. Default: ``'session'``.
968209 607
4fade6 608     ``max_age``
3a6cbc 609       The maximum age of the cookie used for sessioning (in seconds).
MM 610       Default: ``None`` (browser scope).
475532 611
4fade6 612     ``path``
3a6cbc 613       The path used for the session cookie. Default: ``'/'``.
968209 614
4fade6 615     ``domain``
3a6cbc 616       The domain used for the session cookie.  Default: ``None`` (no domain).
4df636 617
4fade6 618     ``secure``
3a6cbc 619       The 'secure' flag of the session cookie. Default: ``False``.
6f6d36 620
4fade6 621     ``httponly``
MM 622       Hide the cookie from Javascript by setting the 'HttpOnly' flag of the
623       session cookie. Default: ``False``.
4df636 624
2d9390 625     ``samesite``
beafcc 626       The 'samesite' option of the session cookie. Set the value to ``None``
MM 627       to turn off the samesite option.  Default: ``'Lax'``.
2d9390 628
4fade6 629     ``timeout``
MM 630       A number of seconds of inactivity before a session times out. If
89dc46 631       ``None`` then the cookie never expires. This lifetime only applies
MM 632       to the *value* within the cookie. Meaning that if the cookie expires
633       due to a lower ``max_age``, then this setting has no effect.
634       Default: ``1200``.
319793 635
4fade6 636     ``reissue_time``
MM 637       The number of seconds that must pass before the cookie is automatically
89dc46 638       reissued as the result of accessing the session. The
4fade6 639       duration is measured as the number of seconds since the last session
MM 640       cookie was issued and 'now'.  If this value is ``0``, a new cookie
89dc46 641       will be reissued on every request accessing the session. If ``None``
4fade6 642       then the cookie's lifetime will never be extended.
319793 643
4fade6 644       A good rule of thumb: if you want auto-expired cookies based on
MM 645       inactivity: set the ``timeout`` value to 1200 (20 mins) and set the
646       ``reissue_time`` value to perhaps a tenth of the ``timeout`` value
647       (120 or 2 mins).  It's nonsensical to set the ``timeout`` value lower
648       than the ``reissue_time`` value, as the ticket will never be reissued.
649       However, such a configuration is not explicitly prevented.
968209 650
4fade6 651       Default: ``0``.
MM 652
653     ``set_on_exception``
3a6cbc 654       If ``True``, set a session cookie even if an exception occurs
MM 655       while rendering a view. Default: ``True``.
656
8134a7 657     ``serializer``
1098ac 658       An object with two methods: ``loads`` and ``dumps``.  The ``loads``
MM 659       method should accept bytes and return a Python object.  The ``dumps``
660       method should accept a Python object and return bytes.  A ``ValueError``
661       should be raised for malformed inputs.  If a serializer is not passed,
662       the :class:`pyramid.session.PickleSerializer` serializer will be used.
4fade6 663
c31883 664     .. warning::
MM 665
666        In :app:`Pyramid` 2.0 the default ``serializer`` option will change to
667        use :class:`pyramid.session.JSONSerializer`. See
668        :ref:`pickle_session_deprecation` for more information about why this
669        change is being made.
670
4fade6 671     .. versionadded: 1.5a3
beafcc 672
MM 673     .. versionchanged: 1.10
674
675        Added the ``samesite`` option and made the default ``Lax``.
c31883 676
3a6cbc 677     """
8134a7 678     if serializer is None:
CM 679         serializer = PickleSerializer()
c31883 680         warnings.warn(
MM 681             'The default pickle serializer is deprecated as of Pyramid 1.9 '
682             'and it will be changed to use pyramid.session.JSONSerializer in '
683             'version 2.0. Explicitly set the serializer to avoid future '
684             'incompatibilities. See "Upcoming Changes to ISession in '
685             'Pyramid 2.0" for more information about this change.',
686             DeprecationWarning,
687             stacklevel=1,
688         )
3a6cbc 689
8134a7 690     signed_serializer = SignedSerializer(
0c29cf 691         secret, salt, hashalg, serializer=serializer
MM 692     )
3a6cbc 693
4fade6 694     return BaseCookieSessionFactory(
8134a7 695         signed_serializer,
3a6cbc 696         cookie_name=cookie_name,
4fade6 697         max_age=max_age,
MM 698         path=path,
699         domain=domain,
700         secure=secure,
701         httponly=httponly,
2d9390 702         samesite=samesite,
4fade6 703         timeout=timeout,
MM 704         reissue_time=reissue_time,
705         set_on_exception=set_on_exception,
3a6cbc 706     )
682a9b 707
0c29cf 708
682a9b 709 check_csrf_origin = check_csrf_origin  # api
0c29cf 710 deprecated(
MM 711     'check_csrf_origin',
712     'pyramid.session.check_csrf_origin is deprecated as of Pyramid '
713     '1.9. Use pyramid.csrf.check_csrf_origin instead.',
714 )
682a9b 715
MM 716 check_csrf_token = check_csrf_token  # api
0c29cf 717 deprecated(
MM 718     'check_csrf_token',
719     'pyramid.session.check_csrf_token is deprecated as of Pyramid '
720     '1.9. Use pyramid.csrf.check_csrf_token instead.',
721 )