Chris McDonough
2012-11-04 fee21a3199d2d6e24e34c1e4bc71dfcfa0e31e64
Merge branch 'fix.695'
17 files modified
272 ■■■■ changed files
CHANGES.txt 23 ●●●●● patch | view | raw | blame | history
TODO.txt 3 ●●●●● patch | view | raw | blame | history
docs/api/authentication.rst 4 ●●●● patch | view | raw | blame | history
docs/narr/security.rst 10 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/authorization.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/authorization/tutorial/__init__.py 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki/src/tests/tutorial/__init__.py 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/authorization.rst 4 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/authorization/tutorial/__init__.py 6 ●●●● patch | view | raw | blame | history
docs/tutorials/wiki2/src/tests/tutorial/__init__.py 6 ●●●● patch | view | raw | blame | history
pyramid/authentication.py 102 ●●●●● patch | view | raw | blame | history
pyramid/tests/pkgs/conflictapp/__init__.py 3 ●●●● patch | view | raw | blame | history
pyramid/tests/pkgs/defpermbugapp/__init__.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/pkgs/forbiddenapp/__init__.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/pkgs/forbiddenview/__init__.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/pkgs/permbugapp/__init__.py 2 ●●● patch | view | raw | blame | history
pyramid/tests/test_authentication.py 87 ●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -4,6 +4,10 @@
Features
--------
- ``pyramid.authentication.AuthTktAuthenticationPolicy`` has been updated to
  support newer hashing algorithms such as ``sha512``. Existing applications
  should consider updating if possible.
- Added an ``effective_principals`` route and view predicate.
- Do not allow the userid returned from the ``authenticated_userid`` or the
@@ -47,20 +51,11 @@
Deprecations
------------
- The ``pyramid.authentication.AuthTktAuthenticationPolicy`` authentication
  policy is deprecated in Pyramid 1.4 due to its use of the MD5 hashing
  algorithm, which has known hash collision vulnerabilities.  The risk of an
  exploit is low.  However, for improved authentication security, use the
  ``pyramid.authentication.SHA512AuthTktAuthenticationPolicy`` instead.
  Cookies generated by the AuthTktAuthenticationPolicy are not compatible with
  cookies generated by the SHA512AuthTktAuthenticationPolicy, however, so
  switching to the latter will imply that all existing users with a valid
  cookie will be required to re-login.  The SHA-512 version is not compatible
  with Apache's mod_auth_tkt either, so if you are relying on that
  compatibility, you'll want to stick with the MD5 version.
  A deprecation warning is now emitted when the AuthTktAuthenticationPolicy is
  imported.
- ``pyramid.authentication.AuthTktAuthenticationPolicy`` will emit a warning
  if an application is using the policy without explicitly setting the
  ``hashalg``. This is because the default is "md5" which is considered
  insecure. If you really want "md5" then you must specify it explicitly to
  get rid of the warning.
Internals
---------
TODO.txt
@@ -141,6 +141,9 @@
- 1.6: Remove IContextURL and TraversalContextURL.
- 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default
  ``hashalg`` to ``sha512``.
Probably Bad Ideas
------------------
docs/api/authentication.rst
@@ -8,10 +8,6 @@
.. automodule:: pyramid.authentication
  .. autoclass:: SHA512AuthTktAuthenticationPolicy
     :members:
     :inherited-members:
  .. autoclass:: AuthTktAuthenticationPolicy
     :members:
     :inherited-members:
docs/narr/security.rst
@@ -90,13 +90,13 @@
   :linenos:
   from pyramid.config import Configurator
   from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
   from pyramid.authentication import AuthTktAuthenticationPolicy
   from pyramid.authorization import ACLAuthorizationPolicy
   authentication_policy = SHA512AuthTktAuthenticationPolicy('seekrit')
   authorization_policy = ACLAuthorizationPolicy()
   authn_policy = AuthTktAuthenticationPolicy('seekrit', hashalg='sha512')
   authz_policy = ACLAuthorizationPolicy()
   config = Configurator()
   config.set_authentication_policy(authentication_policy)
   config.set_authorization_policy(authorization_policy)
   config.set_authentication_policy(authn_policy)
   config.set_authorization_policy(authz_policy)
.. note:: the ``authentication_policy`` and ``authorization_policy``
   arguments may also be passed to their respective methods mentioned above
docs/tutorials/wiki/authorization.rst
@@ -134,12 +134,12 @@
(Only the highlighted lines need to be added.)
We are enabling an ``SHA512AuthTktAuthenticationPolicy``, it is based in an
We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an
auth ticket that may be included in the request, and an
``ACLAuthorizationPolicy`` that uses an ACL to determine the allow or deny
outcome for a view.
Note that the :class:`pyramid.authentication.SHA512AuthTktAuthenticationPolicy`
Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
constructor accepts two arguments: ``secret`` and ``callback``.  ``secret`` is
a string representing an encryption key used by the "authentication ticket"
machinery represented by this policy: it is required.  The ``callback`` is the
docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -1,7 +1,7 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from .models import appmaker
@@ -14,8 +14,8 @@
def main(global_config, **settings):
    """ This function returns a WSGI application.
    """
    authn_policy = SHA512AuthTktAuthenticationPolicy(secret='sosecret',
                                                     callback=groupfinder)
    authn_policy = AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder, hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config = Configurator(root_factory=root_factory, settings=settings)
    config.set_authentication_policy(authn_policy)
docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -1,7 +1,7 @@
from pyramid.config import Configurator
from pyramid_zodbconn import get_connection
from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from .models import appmaker
@@ -14,8 +14,8 @@
def main(global_config, **settings):
    """ This function returns a WSGI application.
    """
    authn_policy = SHA512AuthTktAuthenticationPolicy(secret='sosecret',
                                                     callback=groupfinder)
    authn_policy = AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder, hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config = Configurator(root_factory=root_factory, settings=settings)
    config.set_authentication_policy(authn_policy)
docs/tutorials/wiki2/authorization.rst
@@ -151,12 +151,12 @@
(Only the highlighted lines need to be added.)
We are enabling an ``SHA512AuthTktAuthenticationPolicy``, it is based in an
We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an
auth ticket that may be included in the request, and an
``ACLAuthorizationPolicy`` that uses an ACL to determine the allow or deny
outcome for a view.
Note that the :class:`pyramid.authentication.SHA512AuthTktAuthenticationPolicy`
Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
constructor accepts two arguments: ``secret`` and ``callback``.  ``secret`` is
a string representing an encryption key used by the "authentication ticket"
machinery represented by this policy: it is required.  The ``callback`` is the
docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -1,5 +1,5 @@
from pyramid.config import Configurator
from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from sqlalchemy import engine_from_config
@@ -17,8 +17,8 @@
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    authn_policy = SHA512AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder)
    authn_policy = AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder, hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config = Configurator(settings=settings,
                          root_factory='tutorial.models.RootFactory')
docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -1,5 +1,5 @@
from pyramid.config import Configurator
from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from sqlalchemy import engine_from_config
@@ -17,8 +17,8 @@
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    authn_policy = SHA512AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder)
    authn_policy = AuthTktAuthenticationPolicy(
        'sosecret', callback=groupfinder, hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config = Configurator(settings=settings,
                          root_factory='tutorial.models.RootFactory')
pyramid/authentication.py
@@ -6,8 +6,8 @@
import datetime
import re
import time as time_mod
import warnings
from zope.deprecation import deprecated
from zope.interface import implementer
from pyramid.compat import (
@@ -406,9 +406,19 @@
        be done somewhere else or in a subclass."""
        return []
class BaseAuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
_marker = object()
@implementer(IAuthenticationPolicy)
class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy):
    """A :app:`Pyramid` :term:`authentication policy` which
    obtains data from a Pyramid "auth ticket" cookie.
    .. warning::
       The default hash algorithm used in this policy is MD5 and has known
       hash collision vulnerabilities. The risk of an exploit is low.
       However, for improved authentication security, use
       ``hashalg='sha512'``.
    Constructor Arguments
@@ -498,6 +508,33 @@
       wildcard domain.
       Optional.
    ``hashalg``
       Default: ``md5`` (the literal string).
       Any hash algorithm supported by Python's ``hashlib.new()`` function
       can be used as the ``hashalg``.
       Cookies generated by different instances of AuthTktAuthenticationPolicy
       using different ``hashalg`` options are not compatible. Switching the
       ``hashalg`` will imply that all existing users with a valid cookie will
       be required to re-login.
       A warning is emitted at startup if an explicit ``hashalg`` is not
       passed.  This is for backwards compatibility reasons.
       This option is available as of :app:`Pyramid` 1.4.
       Optional.
       .. note::
          ``md5`` is the default for backwards compatibility reasons. However,
          if you don't specify ``md5`` as the hashalg explicitly, a warning is
          issued at application startup time.  An explicit value of ``sha512``
          is recommended for improved security, and ``sha512`` will become the
          default in a future Pyramid version.
    ``debug``
        Default: ``False``.  If ``debug`` is ``True``, log messages to the
@@ -508,7 +545,6 @@
    Objects of this class implement the interface described by
    :class:`pyramid.interfaces.IAuthenticationPolicy`.
    """
    hashalg = ''
    def __init__(self,
                 secret,
@@ -523,7 +559,32 @@
                 http_only=False,
                 wild_domain=True,
                 debug=False,
                 hashalg=_marker
                 ):
        if hashalg is _marker:
            hashalg = 'md5'
            warnings.warn(
                'The MD5 hash function used by default by the '
                'AuthTktAuthenticationPolicy is known to be '
                'susceptible to collision attacks.  It is the current default '
                'for backwards compatibility reasons, but we recommend that '
                'you use the SHA512 algorithm instead for improved security.  '
                'Pass ``hashalg=\'sha512\'`` to the '
                'AuthTktAuthenticationPolicy constructor to do so.\n\nNote '
                'that a change to the hash algorithms will invalidate existing '
                'auth tkt cookies set by your application.  If backwards '
                'compatibility of existing auth tkt cookies is of greater '
                'concern than the risk posed by the potential for a hash '
                'collision, you\'ll want to continue using MD5 explicitly.  '
                'To do so, pass ``hashalg=\'md5\'`` in your application to '
                'the AuthTktAuthenticationPolicy constructor.   When you do so '
                'this warning will not be emitted again.  The default '
                'algorithm used in this policy will change in the future, so '
                'setting an explicit hashalg will futureproof your '
                'application.',
                DeprecationWarning,
                stacklevel=2
                )
        self.cookie = AuthTktCookieHelper(
            secret,
            cookie_name=cookie_name,
@@ -535,7 +596,7 @@
            http_only=http_only,
            path=path,
            wild_domain=wild_domain,
            hashalg=self.hashalg,
            hashalg=hashalg,
            )
        self.callback = callback
        self.debug = debug
@@ -559,39 +620,6 @@
    def forget(self, request):
        """ A list of headers which will delete appropriate cookies."""
        return self.cookie.forget(request)
@implementer(IAuthenticationPolicy)
class SHA512AuthTktAuthenticationPolicy(BaseAuthTktAuthenticationPolicy):
    __doc__ = """.. versionadded:: 1.4
    """ + BaseAuthTktAuthenticationPolicy.__doc__
    hashalg = 'sha512'
@implementer(IAuthenticationPolicy)
class AuthTktAuthenticationPolicy(BaseAuthTktAuthenticationPolicy):
    __doc__ = """
    .. warning::
        Deprecated in 1.4 due to security concerns,
        use :class:`SHA512AuthTktAuthenticationPolicy` instead.
    """ + BaseAuthTktAuthenticationPolicy.__doc__
    hashalg = 'md5'
deprecated(
    'AuthTktAuthenticationPolicy',
    'The AuthTktAuthenticationPolicy is deprecated in Pyramid 1.4 '
    'due to its use of the MD5 hashing algorithm, which has known '
    'hash collision vulnerabilities.  The risk of an exploit is low.  '
    'However, for improved authentication security, use the '
    'pyramid.authentication.SHA512AuthTktAuthenticationPolicy instead.  '
    'Cookies generated by the AuthTktAuthenticationPolicy are *not* '
    'compatible with cookies generated by the '
    'SHA512AuthTktAuthenticationPolicy, however, so switching to the '
    'latter will imply that all existing users with a valid cookie '
    'will be required to re-login.  The SHA-512 version is not compatible '
    'with Apache\'s mod_auth_tkt either.'
    )
def b64encode(v):
    return base64.b64encode(bytes_(v)).strip().replace(b'\n', b'')
pyramid/tests/pkgs/conflictapp/__init__.py
@@ -18,6 +18,7 @@
    config.add_view(protectedview, name='protected', permission='view')
    config.add_view(routeview, route_name='aroute')
    config.add_route('aroute', '/route')
    config.set_authentication_policy(AuthTktAuthenticationPolicy('seekri1t'))
    config.set_authentication_policy(AuthTktAuthenticationPolicy(
        'seekri1t', hashalg='sha512'))
    config.set_authorization_policy(ACLAuthorizationPolicy())
    config.include('pyramid.tests.pkgs.conflictapp.included')
pyramid/tests/pkgs/defpermbugapp/__init__.py
@@ -17,7 +17,7 @@
def includeme(config):
     from pyramid.authorization import ACLAuthorizationPolicy
     from pyramid.authentication import AuthTktAuthenticationPolicy
     authn_policy = AuthTktAuthenticationPolicy('seekt1t')
     authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512')
     authz_policy = ACLAuthorizationPolicy()
     config.scan('pyramid.tests.pkgs.defpermbugapp')
     config._set_authentication_policy(authn_policy)
pyramid/tests/pkgs/forbiddenapp/__init__.py
@@ -16,7 +16,7 @@
def includeme(config):
     from pyramid.authentication import AuthTktAuthenticationPolicy
     from pyramid.authorization import ACLAuthorizationPolicy
     authn_policy = AuthTktAuthenticationPolicy('seekr1t')
     authn_policy = AuthTktAuthenticationPolicy('seekr1t', hashalg='sha512')
     authz_policy = ACLAuthorizationPolicy()
     config._set_authentication_policy(authn_policy)
     config._set_authorization_policy(authz_policy)
pyramid/tests/pkgs/forbiddenview/__init__.py
@@ -20,7 +20,7 @@
    return Response('OK bar')
def includeme(config):
    authn_policy = AuthTktAuthenticationPolicy('seekri1')
    authn_policy = AuthTktAuthenticationPolicy('seekri1', hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config.set_authentication_policy(authn_policy)
    config.set_authorization_policy(authz_policy)
pyramid/tests/pkgs/permbugapp/__init__.py
@@ -14,7 +14,7 @@
def includeme(config):
     from pyramid.authentication import AuthTktAuthenticationPolicy
     from pyramid.authorization import ACLAuthorizationPolicy
     authn_policy = AuthTktAuthenticationPolicy('seekt1t')
     authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512')
     authz_policy = ACLAuthorizationPolicy()
     config.set_authentication_policy(authn_policy)
     config.set_authorization_policy(authz_policy)
pyramid/tests/test_authentication.py
@@ -1,4 +1,5 @@
import unittest
import warnings
from pyramid import testing
from pyramid.compat import (
    text_,
@@ -431,14 +432,6 @@
        self.assertEqual(result, [])
class TestAuthTktAuthenticationPolicy(unittest.TestCase):
    def setUp(self):
        from zope.deprecation import __show__
        __show__.off()
    def tearDown(self):
        from zope.deprecation import __show__
        __show__.on()
    def _getTargetClass(self):
        from pyramid.authentication import AuthTktAuthenticationPolicy
        return AuthTktAuthenticationPolicy
@@ -448,71 +441,27 @@
        inst.cookie = DummyCookieHelper(cookieidentity)
        return inst
    def test_is_subclass(self):
        from pyramid.authentication import BaseAuthTktAuthenticationPolicy
        inst = self._makeOne(None, None)
        self.assertTrue(isinstance(inst, BaseAuthTktAuthenticationPolicy))
    def setUp(self):
        self.warnings = warnings.catch_warnings()
        self.warnings.__enter__()
        warnings.simplefilter('ignore', DeprecationWarning)
    def test_md5(self):
        inst = self._makeOne(None, None)
        self.assertEqual(inst.hashalg, 'md5')
    def test_class_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyClass
        from pyramid.interfaces import IAuthenticationPolicy
        verifyClass(IAuthenticationPolicy, self._getTargetClass())
    def test_instance_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyObject
        from pyramid.interfaces import IAuthenticationPolicy
        verifyObject(IAuthenticationPolicy, self._makeOne(None, None))
class TestSHA512AuthTktAuthenticationPolicy(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.authentication import SHA512AuthTktAuthenticationPolicy
        return SHA512AuthTktAuthenticationPolicy
    def _makeOne(self, callback, cookieidentity, **kw):
        inst = self._getTargetClass()('secret', callback, **kw)
        inst.cookie = DummyCookieHelper(cookieidentity)
        return inst
    def test_is_subclass(self):
        from pyramid.authentication import BaseAuthTktAuthenticationPolicy
        inst = self._makeOne(None, None)
        self.assertTrue(isinstance(inst, BaseAuthTktAuthenticationPolicy))
    def test_sha512(self):
        inst = self._makeOne(None, None)
        self.assertEqual(inst.hashalg, 'sha512')
    def test_class_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyClass
        from pyramid.interfaces import IAuthenticationPolicy
        verifyClass(IAuthenticationPolicy, self._getTargetClass())
    def test_instance_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyObject
        from pyramid.interfaces import IAuthenticationPolicy
        verifyObject(IAuthenticationPolicy, self._makeOne(None, None))
class TestBaseAutkTktAuthenticationPolicy(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.authentication import BaseAuthTktAuthenticationPolicy
        return BaseAuthTktAuthenticationPolicy
    def _makeOne(self, callback, cookieidentity, **kw):
        inst = self._getTargetClass()('secret', callback, **kw)
        inst.cookie = DummyCookieHelper(cookieidentity)
        return inst
    def tearDown(self):
        self.warnings.__exit__(None, None, None)
    def test_allargs(self):
        # pass all known args
        inst = self._getTargetClass()(
            'secret', callback=None, cookie_name=None, secure=False,
            include_ip=False, timeout=None, reissue_time=None,
            hashalg='sha512',
            )
        self.assertEqual(inst.callback, None)
    def test_hashalg_override(self):
        # important to ensure hashalg is passed to cookie helper
        inst = self._getTargetClass()('secret', hashalg='sha512')
        self.assertEqual(inst.cookie.hashalg, 'sha512')
    def test_unauthenticated_userid_returns_None(self):
        request = DummyRequest({})
@@ -586,6 +535,16 @@
        result = policy.forget(request)
        self.assertEqual(result, [])
    def test_class_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyClass
        from pyramid.interfaces import IAuthenticationPolicy
        verifyClass(IAuthenticationPolicy, self._getTargetClass())
    def test_instance_implements_IAuthenticationPolicy(self):
        from zope.interface.verify import verifyObject
        from pyramid.interfaces import IAuthenticationPolicy
        verifyObject(IAuthenticationPolicy, self._makeOne(None, None))
class TestAuthTktCookieHelper(unittest.TestCase):
    def _getTargetClass(self):
        from pyramid.authentication import AuthTktCookieHelper