diff options
| -rw-r--r-- | CHANGES.txt | 20 | ||||
| -rw-r--r-- | docs/api/authentication.rst | 2 | ||||
| -rw-r--r-- | docs/narr/security.rst | 4 | ||||
| -rw-r--r-- | docs/tutorials/wiki/authorization.rst | 18 | ||||
| -rw-r--r-- | docs/tutorials/wiki/src/authorization/tutorial/__init__.py | 6 | ||||
| -rw-r--r-- | docs/tutorials/wiki/src/tests/tutorial/__init__.py | 6 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/authorization.rst | 18 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/src/authorization/tutorial/__init__.py | 4 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/src/tests/tutorial/__init__.py | 4 | ||||
| -rw-r--r-- | pyramid/authentication.py | 40 | ||||
| -rw-r--r-- | pyramid/tests/test_authentication.py | 12 |
11 files changed, 86 insertions, 48 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 8d5a00e77..45fc19762 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -43,6 +43,24 @@ Bug Fixes attribute of the request. It no longer fails in this case. See https://github.com/Pylons/pyramid/issues/700 +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. + Internals --------- @@ -50,7 +68,7 @@ Internals move ``CyclicDependencyError`` from ``pyramid.config.util`` to ``pyramid.exceptions``, rename ``Singleton`` to ``Sentinel`` and move from ``pyramid.config.util`` to ``pyramid.config.util``; this is in an effort to - move that stuff that may be an API one day out of ``pyramid.config.util, + move that stuff that may be an API one day out of ``pyramid.config.util``, because that package should never be imported from non-Pyramid code. TopologicalSorter is still not an API, but may become one. diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index e6ee5658b..49ee405eb 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -9,6 +9,8 @@ Authentication Policies .. automodule:: pyramid.authentication .. autoclass:: SHA512AuthTktAuthenticationPolicy + :members: + :inherited-members: .. autoclass:: AuthTktAuthenticationPolicy :members: diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 07ec0f21e..3c9759e6c 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -90,9 +90,9 @@ For example: :linenos: from pyramid.config import Configurator - from pyramid.authentication import AuthTktAuthenticationPolicy + from pyramid.authentication import SHA512AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy - authentication_policy = AuthTktAuthenticationPolicy('seekrit') + authentication_policy = SHA512AuthTktAuthenticationPolicy('seekrit') authorization_policy = ACLAuthorizationPolicy() config = Configurator() config.set_authentication_policy(authentication_policy) diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 9e0bf0f09..e2c4637d7 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -134,15 +134,15 @@ Now add those policies to the configuration: (Only the highlighted lines need to be added.) -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.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 +We are enabling an ``SHA512AuthTktAuthenticationPolicy``, 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` +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 ``groupfinder()`` function that we created before. Add permission declarations diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 6989145d8..4c766fea2 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/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 AuthTktAuthenticationPolicy +from pyramid.authentication import SHA512AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from .models import appmaker @@ -14,8 +14,8 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a WSGI application. """ - authn_policy = AuthTktAuthenticationPolicy(secret='sosecret', - callback=groupfinder) + authn_policy = SHA512AuthTktAuthenticationPolicy(secret='sosecret', + callback=groupfinder) authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) config.set_authentication_policy(authn_policy) diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 6989145d8..4c766fea2 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/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 AuthTktAuthenticationPolicy +from pyramid.authentication import SHA512AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from .models import appmaker @@ -14,8 +14,8 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a WSGI application. """ - authn_policy = AuthTktAuthenticationPolicy(secret='sosecret', - callback=groupfinder) + authn_policy = SHA512AuthTktAuthenticationPolicy(secret='sosecret', + callback=groupfinder) authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) config.set_authentication_policy(authn_policy) diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 6b2d44410..e3087dea5 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -151,15 +151,15 @@ Now add those policies to the configuration: (Only the highlighted lines need to be added.) -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.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 +We are enabling an ``SHA512AuthTktAuthenticationPolicy``, 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` +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 ``groupfinder()`` function that we created before. Add permission declarations diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index 8922a3cc0..585cdf884 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -1,5 +1,5 @@ from pyramid.config import Configurator -from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authentication import SHA512AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config @@ -17,7 +17,7 @@ def main(global_config, **settings): engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine - authn_policy = AuthTktAuthenticationPolicy( + authn_policy = SHA512AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() config = Configurator(settings=settings, diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py index 8922a3cc0..585cdf884 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py @@ -1,5 +1,5 @@ from pyramid.config import Configurator -from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authentication import SHA512AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config @@ -17,7 +17,7 @@ def main(global_config, **settings): engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine - authn_policy = AuthTktAuthenticationPolicy( + authn_policy = SHA512AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() config = Configurator(settings=settings, diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 7e7ee4dfb..2e58c70e7 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -6,8 +6,8 @@ import base64 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 ( @@ -407,7 +407,7 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): return [] class BaseAuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): - """ A :app:`Pyramid` :term:`authentication policy` which + """A :app:`Pyramid` :term:`authentication policy` which obtains data from a Pyramid "auth ticket" cookie. Constructor Arguments @@ -562,8 +562,8 @@ class BaseAuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): @implementer(IAuthenticationPolicy) class SHA512AuthTktAuthenticationPolicy(BaseAuthTktAuthenticationPolicy): - __doc__ = """ - .. versionadded:: 1.4 + __doc__ = """.. versionadded:: 1.4 + """ + BaseAuthTktAuthenticationPolicy.__doc__ hashalg = 'sha512' @@ -578,13 +578,20 @@ class AuthTktAuthenticationPolicy(BaseAuthTktAuthenticationPolicy): """ + BaseAuthTktAuthenticationPolicy.__doc__ hashalg = 'md5' - def __init__(self, *a, **kw): - warnings.warn('Deprecated due to the usage of md5, ' - 'hash function known to have collisions. ' - 'Use SHA512AuthTktAuthenticationPolicy instead.', - DeprecationWarning, - stacklevel=2) - super(AuthTktAuthenticationPolicy, self).__init__(*a, **kw) +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'') @@ -654,7 +661,7 @@ class BadTicket(Exception): Exception.__init__(self, msg) # this function licensed under the MIT license (stolen from Paste) -def parse_ticket(secret, ticket, ip, hashalg): +def parse_ticket(secret, ticket, ip, hashalg='md5'): """ Parse the ticket, returning (timestamp, userid, tokens, user_data). @@ -694,7 +701,8 @@ def parse_ticket(secret, ticket, ip, hashalg): return (timestamp, userid, tokens, user_data) # this function licensed under the MIT license (stolen from Paste) -def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, hashalg): +def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, + hashalg='md5'): secret = bytes_(secret, 'utf-8') userid = bytes_(userid, 'utf-8') tokens = bytes_(tokens, 'utf-8') @@ -749,7 +757,8 @@ class AuthTktCookieHelper(object): def __init__(self, secret, cookie_name='auth_tkt', secure=False, include_ip=False, timeout=None, reissue_time=None, - max_age=None, http_only=False, path="/", wild_domain=True, hashalg='md5'): + max_age=None, http_only=False, path="/", wild_domain=True, + hashalg='md5'): self.secret = secret self.cookie_name = cookie_name self.include_ip = include_ip @@ -945,7 +954,8 @@ class AuthTktCookieHelper(object): user_data=user_data, cookie_name=self.cookie_name, secure=self.secure, - hashalg=self.hashalg) + hashalg=self.hashalg + ) cookie_value = ticket.cookie_value() return self._get_cookies(environ, cookie_value, max_age) diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index b1a88b915..8b640b860 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -430,7 +430,15 @@ class TestRemoteUserAuthenticationPolicy(unittest.TestCase): result = policy.forget(request) self.assertEqual(result, []) -class TestAutkTktAuthenticationPolicy(unittest.TestCase): +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 @@ -459,7 +467,7 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne(None, None)) -class TestSHA512AutkTktAuthenticationPolicy(unittest.TestCase): +class TestSHA512AuthTktAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import SHA512AuthTktAuthenticationPolicy return SHA512AuthTktAuthenticationPolicy |
