summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2013-12-07 01:07:24 -0500
committerChris McDonough <chrism@plope.com>2013-12-07 01:07:24 -0500
commit8134a723b12221663babc855ccc941daf88ba5c3 (patch)
treecb199a3a00e066964f4a5488f0ba5d43e53c0541
parent3a950cb42ee450a02d567b25bcb2847f586eabfa (diff)
downloadpyramid-8134a723b12221663babc855ccc941daf88ba5c3.tar.gz
pyramid-8134a723b12221663babc855ccc941daf88ba5c3.tar.bz2
pyramid-8134a723b12221663babc855ccc941daf88ba5c3.zip
use a single serializer instead of serialize/deserialize in session.py, use SignedSerializer from (yet to be released) webob 1.3 instead of local logic in session.py
-rw-r--r--pyramid/session.py104
-rw-r--r--pyramid/tests/test_session.py27
2 files changed, 63 insertions, 68 deletions
diff --git a/pyramid/session.py b/pyramid/session.py
index d3a4113b9..c9d738b9c 100644
--- a/pyramid/session.py
+++ b/pyramid/session.py
@@ -8,6 +8,8 @@ import time
from zope.deprecation import deprecated
from zope.interface import implementer
+from webob.cookies import SignedSerializer
+
from pyramid.compat import (
pickle,
PY3,
@@ -119,9 +121,17 @@ def check_csrf_token(request,
return False
return True
+class PickleSerializer(object):
+ """ A Webob cookie serializer that uses the pickle protocol to dump Python
+ data to bytes."""
+ def loads(self, bstruct):
+ return pickle.loads(bstruct)
+
+ def dumps(self, appstruct):
+ return pickle.dumps(appstruct, pickle.HIGHEST_PROTOCOL)
+
def BaseCookieSessionFactory(
- serialize,
- deserialize,
+ serializer,
cookie_name='session',
max_age=None,
path='/',
@@ -154,13 +164,11 @@ def BaseCookieSessionFactory(
Parameters:
- ``serialize``
- A callable accepting a Python object and returning a bytestring. A
- ``ValueError`` should be raised for malformed inputs.
-
- ``deserialize``
- A callable accepting a bytestring and returning a Python object. A
- ``ValueError`` should be raised for malformed inputs.
+ ``serializer``
+ An object with two methods: `loads`` and ``dumps``. The ``loads`` method
+ should accept bytes and return a Python object. The ``dumps`` method
+ should accept a Python object and return bytes. A ``ValueError`` should
+ be raised for malformed inputs.
``cookie_name``
The name of the cookie used for sessioning. Default: ``'session'``.
@@ -238,7 +246,7 @@ def BaseCookieSessionFactory(
cookieval = request.cookies.get(self._cookie_name)
if cookieval is not None:
try:
- value = deserialize(bytes_(cookieval))
+ value = serializer.loads(bytes_(cookieval))
except ValueError:
# the cookie failed to deserialize, dropped
value = None
@@ -336,7 +344,7 @@ def BaseCookieSessionFactory(
exception = getattr(self.request, 'exception', None)
if exception is not None: # dont set a cookie during exceptions
return False
- cookieval = native_(serialize(
+ cookieval = native_(serializer.dumps(
(self.accessed, self.created, dict(self))
))
if len(cookieval) > 4064:
@@ -430,9 +438,20 @@ def UnencryptedCookieSessionFactoryConfig(
is valid. Default: ``signed_deserialize`` (using pickle).
"""
+ class SerializerWrapper(object):
+ def __init__(self, secret):
+ self.secret = secret
+
+ def loads(self, bstruct):
+ return signed_deserialize(bstruct, secret)
+
+ def dumps(self, appstruct):
+ return signed_serialize(appstruct, secret)
+
+ serializer = SerializerWrapper(secret)
+
return BaseCookieSessionFactory(
- lambda v: signed_serialize(v, secret),
- lambda v: signed_deserialize(v, secret),
+ serializer,
cookie_name=cookie_name,
max_age=cookie_max_age,
path=cookie_path,
@@ -463,8 +482,7 @@ def SignedCookieSessionFactory(
reissue_time=0,
hashalg='sha512',
salt='pyramid.session.',
- serialize=None,
- deserialize=None,
+ serializer=None,
):
"""
.. versionadded:: 1.5
@@ -546,53 +564,27 @@ def SignedCookieSessionFactory(
If ``True``, set a session cookie even if an exception occurs
while rendering a view. Default: ``True``.
- ``serialize``
- A callable accepting a Python object and returning a bytestring. A
- ``ValueError`` should be raised for malformed inputs.
- Default: :func:`pickle.dumps`.
-
- ``deserialize``
- A callable accepting a bytestring and returning a Python object. A
- ``ValueError`` should be raised for malformed inputs.
- Default: :func:`pickle.loads`.
+ ``serializer``
+ An object with two methods: `loads`` and ``dumps``. The ``loads`` method
+ should accept bytes and return a Python object. The ``dumps`` method
+ should accept a Python object and return bytes. A ``ValueError`` should
+ be raised for malformed inputs. If a serializer is not passed, the
+ :class:`pyramid.session.PickleSerializer` serializer will be used.
.. versionadded: 1.5a3
"""
+ if serializer is None:
+ serializer = PickleSerializer()
- if serialize is None:
- serialize = lambda v: pickle.dumps(v, pickle.HIGHEST_PROTOCOL)
-
- if deserialize is None:
- deserialize = pickle.loads
-
- digestmod = lambda string=b'': hashlib.new(hashalg, string)
- digest_size = digestmod().digest_size
-
- salted_secret = bytes_(salt or '') + bytes_(secret)
-
- def signed_serialize(appstruct):
- cstruct = serialize(appstruct)
- sig = hmac.new(salted_secret, cstruct, digestmod).digest()
- return base64.b64encode(cstruct + sig)
-
- def signed_deserialize(bstruct):
- try:
- fstruct = base64.b64decode(bstruct)
- except (binascii.Error, TypeError) as e:
- raise ValueError('Badly formed base64 data: %s' % e)
-
- cstruct = fstruct[:-digest_size]
- expected_sig = fstruct[-digest_size:]
-
- sig = hmac.new(salted_secret, cstruct, digestmod).digest()
- if strings_differ(sig, expected_sig):
- raise ValueError('Invalid signature')
-
- return deserialize(cstruct)
+ signed_serializer = SignedSerializer(
+ secret,
+ salt,
+ hashalg,
+ serializer=serializer,
+ )
return BaseCookieSessionFactory(
- signed_serialize,
- signed_deserialize,
+ signed_serializer,
cookie_name=cookie_name,
max_age=max_age,
path=path,
diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py
index a9f70d6a0..1ad0729b3 100644
--- a/pyramid/tests/test_session.py
+++ b/pyramid/tests/test_session.py
@@ -264,8 +264,8 @@ class SharedCookieSessionTests(object):
class TestBaseCookieSession(SharedCookieSessionTests, unittest.TestCase):
def _makeOne(self, request, **kw):
from pyramid.session import BaseCookieSessionFactory
- return BaseCookieSessionFactory(
- dummy_serialize, dummy_deserialize, **kw)(request)
+ serializer = DummySerializer()
+ return BaseCookieSessionFactory(serializer, **kw)(request)
def _serialize(self, value):
return json.dumps(value)
@@ -294,7 +294,7 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase):
digestmod = lambda: hashlib.new(hashalg)
cstruct = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
sig = hmac.new(salt + b'secret', cstruct, digestmod).digest()
- return base64.b64encode(cstruct + sig)
+ return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=')
def test_reissue_not_triggered(self):
import time
@@ -353,11 +353,12 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase):
import hmac
import time
request = testing.DummyRequest()
- cstruct = dummy_serialize((time.time(), 0, {'state': 1}))
+ serializer = DummySerializer()
+ cstruct = serializer.dumps((time.time(), 0, {'state': 1}))
sig = hmac.new(b'pyramid.session.secret', cstruct, sha512).digest()
- cookieval = base64.b64encode(cstruct + sig)
+ cookieval = base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=')
request.cookies['session'] = cookieval
- session = self._makeOne(request, deserialize=dummy_deserialize)
+ session = self._makeOne(request, serializer=serializer)
self.assertEqual(session['state'], 1)
def test_invalid_data_size(self):
@@ -382,7 +383,7 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase):
try:
result = callbacks[0](request, response)
- except TypeError as e: # pragma: no cover
+ except TypeError: # pragma: no cover
self.fail('HMAC failed to initialize due to key length.')
self.assertEqual(result, None)
@@ -413,8 +414,9 @@ class TestUnencryptedCookieSession(SharedCookieSessionTests, unittest.TestCase):
kw.setdefault(dest, kw.pop(src))
def _serialize(self, value):
+ from pyramid.compat import bytes_
from pyramid.session import signed_serialize
- return signed_serialize(value, 'secret')
+ return bytes_(signed_serialize(value, 'secret'))
def test_serialize_option(self):
from pyramid.response import Response
@@ -596,11 +598,12 @@ class Test_check_csrf_token(unittest.TestCase):
result = self._callFUT(request, 'csrf_token', raises=False)
self.assertEqual(result, False)
-def dummy_serialize(value):
- return json.dumps(value).encode('utf-8')
+class DummySerializer(object):
+ def dumps(self, value):
+ return json.dumps(value).encode('utf-8')
-def dummy_deserialize(value):
- return json.loads(value.decode('utf-8'))
+ def loads(self, value):
+ return json.loads(value.decode('utf-8'))
class DummySessionFactory(dict):
_dirty = False