Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split pickle from encryption #4

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ Changelog
0.2 (unreleased)
----------------

- TBD

- Split the ``EncryptingPickleSerializer`` into ``EncryptedSerializer``
with a default dependency on ``pyramid.session.PickleSerializer`` allowing
alternative serializers to be used with the encryption interface.
See https://github.com/Pylons/pyramid_nacl_session/pull/4

0.1 (2015-11-23)
----------------
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ Contributors
------------

- Tres Seaver, 2015/11/16

- Michael Merickel, 2015/11/23
7 changes: 6 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
'sphinx.ext.intersphinx',
]

# Looks for objects in external projects
intersphinx_mapping = {
'pyramid': ('http://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
}

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

Expand Down Expand Up @@ -124,7 +129,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
#html_static_path = ['_static']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Does tox -e docs barf for you because of git's absurd ignore-empty-directories bug^Wdesign choice?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's why. FWIW I'm currently updating the tox.ini to support the coverage support in the other Pylons repos (100% on py2 + py3). That'll be a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather have an empty _static/placeholder.txt, to avoid future headscratching when adding (hypothetically) an image to illustrate something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
Expand Down
8 changes: 4 additions & 4 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ Use the generated secret to configure a session factory:
.. code-block:: python

from pyramid.session import BaseCookieSessionFactory
from pyramid_nacl_session import EncryptingPickleSerializer
from pyramid_nacl_session import EncryptedSerializer

def includeme(config):
serializer = EncryptingPickleSerializer(SECRET)
serializer = EncryptedSerializer(SECRET)
factory = BaseCookieSessionFactory(serializer) # other config ad lib.
config.set_session_factory(factory)

Expand Down Expand Up @@ -53,12 +53,12 @@ construct and register a session factory:

import binascii
from pyramid.session import BaseCookieSessionFactory
from pyramid_nacl_session import EncryptingPickleSerializer
from pyramid_nacl_session import EncryptedSerializer

def includeme(config):
hex_secret = config.settings['yourapp.session_secret'].strip()
secret = binascii.unhexlify(hex_secret)
serializer = EncryptingPickleSerializer(secret)
serializer = EncryptedSerializer(secret)
factory = BaseCookieSessionFactory(serializer) # other config ad lib.
config.set_session_factory(factory)

4 changes: 2 additions & 2 deletions pyramid_nacl_session/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def _export(thing):
Also, shuts pyflakes up about unused import.
"""

from .serializer import EncryptingPickleSerializer
_export(EncryptingPickleSerializer)
from .serializer import EncryptedSerializer
_export(EncryptedSerializer)
from .scripts import generate_secret
_export(generate_secret)
30 changes: 20 additions & 10 deletions pyramid_nacl_session/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,34 @@

from nacl.secret import SecretBox
from nacl.utils import random
from pyramid.compat import pickle
from pyramid.session import PickleSerializer


class EncryptingPickleSerializer(object):
"""Encrypt pickled session state using PyNaCl.
class EncryptedSerializer(object):
"""Encrypt session state using PyNaCl.

:type secret: bytes
:param secret: a 32-byte random secret for encrypting/decrypting the
pickled session state.
:param 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. Default: ``None``, which will
use :class:`pyramid.session.PickleSerializer`.
"""
def __init__(self, secret):
def __init__(self, secret, serializer=None):
if len(secret) != SecretBox.KEY_SIZE:
raise ValueError(
"Secret should be a random bytes string of length %d"
% SecretBox.KEY_SIZE)
"Secret should be a random bytes string of length %d" %
SecretBox.KEY_SIZE)
self.box = SecretBox(secret)

if serializer is None:
serializer = PickleSerializer()

self.serializer = serializer

def loads(self, encrypted_state):
"""Decrypt session state.

Expand All @@ -31,7 +42,7 @@ def loads(self, encrypted_state):
``session_state`` to :meth:`dumps`.
"""
payload = self.box.decrypt(urlsafe_b64decode(encrypted_state))
return pickle.loads(payload)
return self.serializer.loads(payload)

def dumps(self, session_state):
"""Encrypt session state.
Expand All @@ -42,7 +53,6 @@ def dumps(self, session_state):
:rtype: bytes
:returns: the encrypted session state
"""
pickled = pickle.dumps(session_state)
cstruct = self.serializer.dumps(session_state)
nonce = random(SecretBox.NONCE_SIZE)
return urlsafe_b64encode(self.box.encrypt(pickled, nonce))

return urlsafe_b64encode(self.box.encrypt(cstruct, nonce))
10 changes: 5 additions & 5 deletions pyramid_nacl_session/tests/test_serializer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import unittest


class EncryptingPickleSerializerTests(unittest.TestCase):
class EncryptedSerializerTests(unittest.TestCase):

def _getTargetClass(self):
from ..serializer import EncryptingPickleSerializer
return EncryptingPickleSerializer
from ..serializer import EncryptedSerializer
return EncryptedSerializer

def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
Expand All @@ -20,7 +20,7 @@ def test_dumps(self):
SECRET = 'SEEKRIT!' * 4 # 32 bytes
NONCE = b'\x01' * 24
APPSTRUCT = {'foo': 'bar'}
PICKLED = pickle.dumps(APPSTRUCT)
PICKLED = pickle.dumps(APPSTRUCT, pickle.HIGHEST_PROTOCOL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh! I forgot Python2 uses 0 as the default for BBB.

_base64_called = []
def _base64_encode(what):
_base64_called.append(what)
Expand All @@ -42,7 +42,7 @@ def test_loads(self):
SECRET = 'SEEKRIT!' * 4 # 32 bytes
NONCE = b'\x01' * 24
APPSTRUCT = {'foo': 'bar'}
PICKLED = pickle.dumps(APPSTRUCT)
PICKLED = pickle.dumps(APPSTRUCT, pickle.HIGHEST_PROTOCOL)
CIPHERTEXT = PICKLED + b':' + NONCE
_base64_called = []
def _base64_decode(what):
Expand Down