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