summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2012-08-23 10:18:23 -0500
committerMichael Merickel <michael@merickel.org>2012-08-23 10:18:23 -0500
commitb8b896b748ba5d4fd9c6d46db0d6f192cab264f3 (patch)
tree58bd219db2e546ed1ed882cd5649eda77561412f
parenteb403ffe22501ae0546c02c7c24b54b5e3d1eb83 (diff)
parent2cc0710ecf9b47e5a6f41caf6b1c56b29bab2db0 (diff)
downloadpyramid-b8b896b748ba5d4fd9c6d46db0d6f192cab264f3.tar.gz
pyramid-b8b896b748ba5d4fd9c6d46db0d6f192cab264f3.tar.bz2
pyramid-b8b896b748ba5d4fd9c6d46db0d6f192cab264f3.zip
Merge branch 'master' of ianjosephwilson/pyramid into pull.617
Conflicts: CONTRIBUTORS.txt
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--pyramid/session.py106
-rw-r--r--pyramid/tests/test_session.py42
3 files changed, 102 insertions, 48 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index a2da7fbfd..e44117af3 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -177,4 +177,6 @@ Contributors
- Jeff Cook, 2012/06/16
+- Ian Wilson, 2012/06/17
+
- Roman Kozlovskyi, 2012/08/11
diff --git a/pyramid/session.py b/pyramid/session.py
index 76b2b30b1..40e21ddbc 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -33,6 +33,53 @@ def manage_accessed(wrapped):
accessed.__doc__ = wrapped.__doc__
return accessed
+def signed_serialize(data, secret):
+ """ Serialize any pickleable structure (``data``) and sign it
+ using the ``secret`` (must be a string). Return the
+ serialization, which includes the signature as its first 40 bytes.
+ The ``signed_deserialize`` method will deserialize such a value.
+
+ This function is useful for creating signed cookies. For example:
+
+ .. code-block:: python
+
+ cookieval = signed_serialize({'a':1}, 'secret')
+ response.set_cookie('signed_cookie', cookieval)
+ """
+ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+ return sig + native_(base64.b64encode(pickled))
+
+def signed_deserialize(serialized, secret, hmac=hmac):
+ """ Deserialize the value returned from ``signed_serialize``. If
+ the value cannot be deserialized for any reason, a
+ :exc:`ValueError` exception will be raised.
+
+ This function is useful for deserializing a signed cookie value
+ created by ``signed_serialize``. For example:
+
+ .. code-block:: python
+
+ cookieval = request.cookies['signed_cookie']
+ data = signed_deserialize(cookieval, 'secret')
+ """
+ # hmac parameterized only for unit tests
+ try:
+ input_sig, pickled = (serialized[:40],
+ base64.b64decode(bytes_(serialized[40:])))
+ except (binascii.Error, TypeError) as e:
+ # Badly formed data can make base64 die
+ raise ValueError('Badly formed base64 data: %s' % e)
+
+ sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
+
+ # Avoid timing attacks (see
+ # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
+ if strings_differ(sig, input_sig):
+ raise ValueError('Invalid signature')
+
+ return pickle.loads(pickled)
+
def UnencryptedCookieSessionFactoryConfig(
secret,
timeout=1200,
@@ -43,6 +90,8 @@ def UnencryptedCookieSessionFactoryConfig(
cookie_secure=False,
cookie_httponly=False,
cookie_on_exception=True,
+ signed_serialize=signed_serialize,
+ signed_deserialize=signed_deserialize,
):
"""
Configure a :term:`session factory` which will provide unencrypted
@@ -89,6 +138,15 @@ def UnencryptedCookieSessionFactoryConfig(
If ``True``, set a session cookie even if an exception occurs
while rendering a view. Default: ``True``.
+ ``signed_serialize``
+ A callable which takes more or less arbitrary python data structure and
+ a secret and returns a signed serialization in bytes.
+ Default: ``signed_serialize`` (using pickle).
+
+ ``signed_deserialize``
+ A callable which takes a signed and serialized data structure in bytes
+ and a secret and returns the original data structure if the signature
+ is valid. Default: ``signed_deserialize`` (using pickle).
"""
@implementer(ISession)
@@ -225,51 +283,3 @@ def UnencryptedCookieSessionFactoryConfig(
return True
return UnencryptedCookieSessionFactory
-
-def signed_serialize(data, secret):
- """ Serialize any pickleable structure (``data``) and sign it
- using the ``secret`` (must be a string). Return the
- serialization, which includes the signature as its first 40 bytes.
- The ``signed_deserialize`` method will deserialize such a value.
-
- This function is useful for creating signed cookies. For example:
-
- .. code-block:: python
-
- cookieval = signed_serialize({'a':1}, 'secret')
- response.set_cookie('signed_cookie', cookieval)
- """
- pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
- return sig + native_(base64.b64encode(pickled))
-
-def signed_deserialize(serialized, secret, hmac=hmac):
- """ Deserialize the value returned from ``signed_serialize``. If
- the value cannot be deserialized for any reason, a
- :exc:`ValueError` exception will be raised.
-
- This function is useful for deserializing a signed cookie value
- created by ``signed_serialize``. For example:
-
- .. code-block:: python
-
- cookieval = request.cookies['signed_cookie']
- data = signed_deserialize(cookieval, 'secret')
- """
- # hmac parameterized only for unit tests
- try:
- input_sig, pickled = (serialized[:40],
- base64.b64decode(bytes_(serialized[40:])))
- except (binascii.Error, TypeError) as e:
- # Badly formed data can make base64 die
- raise ValueError('Badly formed base64 data: %s' % e)
-
- sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest()
-
- # Avoid timing attacks (see
- # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf)
- if strings_differ(sig, input_sig):
- raise ValueError('Invalid signature')
-
- return pickle.loads(pickled)
-
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index 6d75c7950..5143b7a95 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -205,6 +205,48 @@ class TestUnencryptedCookieSession(unittest.TestCase):
self.assertTrue(token)
self.assertTrue('_csrft_' in session)
+ def test_serialize_option(self):
+ from pyramid.response import Response
+ secret = 'secret'
+ request = testing.DummyRequest()
+ session = self._makeOne(request,
+ signed_serialize=dummy_signed_serialize)
+ session['key'] = 'value'
+ response = Response()
+ self.assertEqual(session._set_cookie(response), True)
+ cookie = response.headerlist[-1][1]
+ expected_cookieval = dummy_signed_serialize(
+ (session.accessed, session.created, {'key': 'value'}), secret)
+ response = Response()
+ response.set_cookie('session', expected_cookieval)
+ expected_cookie = response.headerlist[-1][1]
+ self.assertEqual(cookie, expected_cookie)
+
+ def test_deserialize_option(self):
+ import time
+ secret = 'secret'
+ request = testing.DummyRequest()
+ accessed = time.time()
+ state = {'key': 'value'}
+ cookieval = dummy_signed_serialize((accessed, accessed, state), secret)
+ request.cookies['session'] = cookieval
+ session = self._makeOne(request,
+ signed_deserialize=dummy_signed_deserialize)
+ self.assertEqual(dict(session), state)
+
+def dummy_signed_serialize(data, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ pickled = pickle.dumps(data)
+ return base64.b64encode(bytes_(secret)) + base64.b64encode(pickled)
+
+def dummy_signed_deserialize(serialized, secret):
+ import base64
+ from pyramid.compat import pickle, bytes_
+ serialized_data = base64.b64decode(
+ serialized[len(base64.b64encode(bytes_(secret))):])
+ return pickle.loads(serialized_data)
+
class Test_manage_accessed(unittest.TestCase):
def _makeOne(self, wrapped):
from pyramid.session import manage_accessed