summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-08-23 12:33:54 -0400
committerChris McDonough <chrism@plope.com>2012-08-23 12:33:54 -0400
commit0d9dccd259d52be9be2400cfe6ee5b709b812bfc (patch)
treeddb6d0174bab3a49c19e9f678c1c7ce4a43c5a8f
parentb9b4657e79e7e1ed1f0c79bf82c3fe19a0ab5687 (diff)
parent75a8ff4ad0ba8eed13592eb5e7a211da3035e209 (diff)
downloadpyramid-0d9dccd259d52be9be2400cfe6ee5b709b812bfc.tar.gz
pyramid-0d9dccd259d52be9be2400cfe6ee5b709b812bfc.tar.bz2
pyramid-0d9dccd259d52be9be2400cfe6ee5b709b812bfc.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt16
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--docs/narr/firstapp.rst10
-rw-r--r--docs/narr/helloworld.py15
-rw-r--r--docs/narr/views.rst2
-rw-r--r--pyramid/config/views.py3
-rw-r--r--pyramid/mako_templating.py2
-rw-r--r--pyramid/session.py106
-rw-r--r--pyramid/tests/test_config/test_views.py18
-rw-r--r--pyramid/tests/test_mako_templating.py10
-rw-r--r--pyramid/tests/test_session.py42
11 files changed, 166 insertions, 62 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 0291d924a..e799c8f00 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -17,12 +17,21 @@ Bug Fixes
during iteration`` exception. It no longer does. See
https://github.com/Pylons/pyramid/issues/635 for more information.
-- In Mako Templates lookup, cyheck if the uri is already adjusted and bring
+- In Mako Templates lookup, check if the uri is already adjusted and bring
it back to an asset spec. Normally occurs with inherited templates or
included components.
https://github.com/Pylons/pyramid/issues/606
https://github.com/Pylons/pyramid/issues/607
+- In Mako Templates lookup, check for absolute uri (using mako directories)
+ when mixing up inheritance with asset specs.
+ https://github.com/Pylons/pyramid/issues/662
+
+- HTTP Accept headers were not being normalized causing potentially
+ conflicting view registrations to go unnoticed. Two views that only
+ differ in the case ('text/html' vs. 'text/HTML') will now raise an error.
+ https://github.com/Pylons/pyramid/pull/620
+
Features
--------
@@ -105,6 +114,11 @@ Features
config = Configurator()
config.add_permission('view')
+- The ``UnencryptedCookieSessionFactoryConfig`` now accepts
+ ``signed_serialize`` and ``signed_deserialize`` hooks which may be used
+ to influence how the sessions are marshalled (by default this is done
+ with HMAC+pickle).
+
Deprecations
------------
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index a2da7fbfd..264acf048 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -173,8 +173,12 @@ Contributors
- Marin Rukavina, 2012/05/03
+- Lorenzo M. Catucci, 2012/06/08
+
- Marc Abramowitz, 2012/06/13
- Jeff Cook, 2012/06/16
+- Ian Wilson, 2012/06/17
+
- Roman Kozlovskyi, 2012/08/11
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index a86826d86..ccaa6e9e2 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -127,7 +127,7 @@ defined imports and function definitions, placed within the confines of an
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-13
+ :lines: 9-15
Let's break this down piece-by-piece.
@@ -136,7 +136,7 @@ Configurator Construction
.. literalinclude:: helloworld.py
:linenos:
- :lines: 8-9
+ :lines: 9-10
The ``if __name__ == '__main__':`` line in the code sample above represents a
Python idiom: the code inside this if clause is not invoked unless the script
@@ -169,7 +169,7 @@ Adding Configuration
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 10-11
+ :lines: 11-12
First line above calls the :meth:`pyramid.config.Configurator.add_route`
method, which registers a :term:`route` to match any URL path that begins
@@ -189,7 +189,7 @@ WSGI Application Creation
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 12
+ :lines: 13
After configuring views and ending configuration, the script creates a WSGI
*application* via the :meth:`pyramid.config.Configurator.make_wsgi_app`
@@ -218,7 +218,7 @@ WSGI Application Serving
.. ignore-next-block
.. literalinclude:: helloworld.py
:linenos:
- :lines: 13
+ :lines: 14-15
Finally, we actually serve the application to requestors by starting up a
WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server
diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py
index 7c26c8cdc..c01329af9 100644
--- a/docs/narr/helloworld.py
+++ b/docs/narr/helloworld.py
@@ -2,14 +2,15 @@ from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
+
def hello_world(request):
- return Response('Hello %(name)s!' % request.matchdict)
+ return Response('Hello %(name)s!' % request.matchdict)
if __name__ == '__main__':
- config = Configurator()
- config.add_route('hello', '/hello/{name}')
- config.add_view(hello_world, route_name='hello')
- app = config.make_wsgi_app()
- server = make_server('0.0.0.0', 8080, app)
- server.serve_forever()
+ config = Configurator()
+ config.add_route('hello', '/hello/{name}')
+ config.add_view(hello_world, route_name='hello')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index f6ee9a8d5..9e41464a6 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -177,7 +177,7 @@ HTTP Exceptions
~~~~~~~~~~~~~~~
All classes documented in the :mod:`pyramid.httpexceptions` module documented
-as inheriting from the :class:`pryamid.httpexceptions.HTTPException` are
+as inheriting from the :class:`pyramid.httpexceptions.HTTPException` are
:term:`http exception` objects. Instances of an HTTP exception object may
either be *returned* or *raised* from within view code. In either case
(return or raise) the instance will be used as as the view's response.
diff --git a/pyramid/config/views.py b/pyramid/config/views.py
index 1c4e20dd6..b61a71914 100644
--- a/pyramid/config/views.py
+++ b/pyramid/config/views.py
@@ -1047,6 +1047,9 @@ class ViewsConfiguratorMixin(object):
name=renderer, package=self.package,
registry = self.registry)
+ if accept is not None:
+ accept = accept.lower()
+
introspectables = []
pvals = predicates.copy()
pvals.update(
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 489c1f11a..2b09e8d45 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -43,6 +43,8 @@ class PkgResourceTemplateLookup(TemplateLookup):
if relativeto is not None:
relativeto = relativeto.replace('$', ':')
if not(':' in uri) and (':' in relativeto):
+ if uri.startswith('/'):
+ return uri
pkg, relto = relativeto.split(':')
_uri = posixpath.join(posixpath.dirname(relto), uri)
return '{0}:{1}'.format(pkg, _uri)
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_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 38f60d79b..72a0d8ebd 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -646,6 +646,24 @@ class TestViewsConfigurationMixin(unittest.TestCase):
request.accept = DummyAccept('text/html', 'text/html')
self.assertEqual(wrapper(None, request), 'OK2')
+ def test_add_view_mixed_case_replaces_existing_view(self):
+ from pyramid.renderers import null_renderer
+ def view(context, request): return 'OK'
+ def view2(context, request): return 'OK2'
+ def view3(context, request): return 'OK3'
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, renderer=null_renderer)
+ config.add_view(view=view2, accept='text/html', renderer=null_renderer)
+ config.add_view(view=view3, accept='text/HTML', renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertTrue(IMultiView.providedBy(wrapper))
+ self.assertEqual(len(wrapper.media_views.items()),1)
+ self.assertFalse('text/HTML' in wrapper.media_views)
+ self.assertEqual(wrapper(None, None), 'OK')
+ request = DummyRequest()
+ request.accept = DummyAccept('text/html', 'text/html')
+ self.assertEqual(wrapper(None, request), 'OK3')
+
def test_add_views_with_accept_multiview_replaces_existing(self):
from pyramid.renderers import null_renderer
def view(context, request): return 'OK'
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index aced6c586..97b2c679b 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -499,6 +499,16 @@ class TestPkgResourceTemplateLookup(unittest.TestCase):
result = inst.adjust_uri('b', '../a')
self.assertEqual(result, '../b')
+ def test_adjust_uri_not_asset_spec_abs_with_relativeto_asset_spec(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('/c', 'a:b')
+ self.assertEqual(result, '/c')
+
+ def test_adjust_uri_asset_spec_with_relativeto_not_asset_spec_abs(self):
+ inst = self._makeOne()
+ result = inst.adjust_uri('a:b', '/c')
+ self.assertEqual(result, 'a:b')
+
def test_get_template_not_asset_spec(self):
fixturedir = self.get_fixturedir()
inst = self._makeOne(directories=[fixturedir])
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