diff options
| author | Chris McDonough <chrism@plope.com> | 2012-11-04 01:51:57 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-11-04 01:51:57 -0500 |
| commit | 9149461a45399a9f7f23322daa7c02e1397f9a91 (patch) | |
| tree | b419ceb66f4298b63d4c0f591f1f5ef0ff7a58a8 | |
| parent | 34d4cd0ea38fdbe0ab0e0832fc2114953ef4e94a (diff) | |
| parent | 04875452db1da40bd8ed0841869d511b8d86527d (diff) | |
| download | pyramid-9149461a45399a9f7f23322daa7c02e1397f9a91.tar.gz pyramid-9149461a45399a9f7f23322daa7c02e1397f9a91.tar.bz2 pyramid-9149461a45399a9f7f23322daa7c02e1397f9a91.zip | |
Merge branch 'iElectric-feature.sha512_auth'
| -rw-r--r-- | CHANGES.txt | 20 | ||||
| -rw-r--r-- | docs/api/authentication.rst | 4 | ||||
| -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 | 85 | ||||
| -rw-r--r-- | pyramid/tests/test_authentication.py | 100 |
11 files changed, 205 insertions, 64 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 19d08618b..49ee405eb 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -8,6 +8,10 @@ Authentication Policies .. automodule:: pyramid.authentication + .. autoclass:: SHA512AuthTktAuthenticationPolicy + :members: + :inherited-members: + .. autoclass:: AuthTktAuthenticationPolicy :members: :inherited-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 8be34cc0a..2e58c70e7 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -1,12 +1,13 @@ import binascii from codecs import utf_8_decode from codecs import utf_8_encode -from hashlib import md5 +import hashlib import base64 import datetime import re import time as time_mod +from zope.deprecation import deprecated from zope.interface import implementer from pyramid.compat import ( @@ -405,9 +406,8 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): be done somewhere else or in a subclass.""" return [] -@implementer(IAuthenticationPolicy) -class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): - """ A :app:`Pyramid` :term:`authentication policy` which +class BaseAuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): + """A :app:`Pyramid` :term:`authentication policy` which obtains data from a Pyramid "auth ticket" cookie. Constructor Arguments @@ -508,6 +508,8 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ + hashalg = '' + def __init__(self, secret, callback=None, @@ -533,6 +535,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): http_only=http_only, path=path, wild_domain=wild_domain, + hashalg=self.hashalg, ) self.callback = callback self.debug = debug @@ -557,6 +560,39 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """ 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'') @@ -585,7 +621,8 @@ class AuthTicket(object): """ def __init__(self, secret, userid, ip, tokens=(), user_data='', - time=None, cookie_name='auth_tkt', secure=False): + time=None, cookie_name='auth_tkt', secure=False, + hashalg='md5'): self.secret = secret self.userid = userid self.ip = ip @@ -597,11 +634,12 @@ class AuthTicket(object): self.time = time self.cookie_name = cookie_name self.secure = secure + self.hashalg = hashalg def digest(self): return calculate_digest( self.ip, self.time, self.secret, self.userid, self.tokens, - self.user_data) + self.user_data, self.hashalg) def cookie_value(self): v = '%s%08x%s!' % (self.digest(), int(self.time), @@ -623,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): +def parse_ticket(secret, ticket, ip, hashalg='md5'): """ Parse the ticket, returning (timestamp, userid, tokens, user_data). @@ -631,13 +669,14 @@ def parse_ticket(secret, ticket, ip): with an explanation. """ ticket = ticket.strip('"') - digest = ticket[:32] + digest_size = hashlib.new(hashalg).digest_size * 2 + digest = ticket[:digest_size] try: - timestamp = int(ticket[32:40], 16) + timestamp = int(ticket[digest_size:digest_size + 8], 16) except ValueError as e: raise BadTicket('Timestamp is not a hex integer: %s' % e) try: - userid, data = ticket[40:].split('!', 1) + userid, data = ticket[digest_size + 8:].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') userid = url_unquote(userid) @@ -649,7 +688,7 @@ def parse_ticket(secret, ticket, ip): user_data = data expected = calculate_digest(ip, timestamp, secret, - userid, tokens, user_data) + userid, tokens, user_data, hashalg) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) @@ -662,16 +701,20 @@ def parse_ticket(secret, ticket, ip): 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): +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') user_data = bytes_(user_data, 'utf-8') - digest0 = md5( + hash_obj = hashlib.new(hashalg) + hash_obj.update( encode_ip_timestamp(ip, timestamp) + secret + userid + b'\0' - + tokens + b'\0' + user_data).hexdigest() - digest = md5(bytes_(digest0) + secret).hexdigest() - return digest + + tokens + b'\0' + user_data) + digest = hash_obj.hexdigest() + hash_obj2 = hashlib.new(hashalg) + hash_obj2.update(bytes_(digest) + secret) + return hash_obj2.hexdigest() # this function licensed under the MIT license (stolen from Paste) def encode_ip_timestamp(ip, timestamp): @@ -714,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): + 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 @@ -725,6 +769,7 @@ class AuthTktCookieHelper(object): self.http_only = http_only self.path = path self.wild_domain = wild_domain + self.hashalg = hashalg static_flags = [] if self.secure: @@ -793,7 +838,7 @@ class AuthTktCookieHelper(object): try: timestamp, userid, tokens, user_data = self.parse_ticket( - self.secret, cookie, remote_addr) + self.secret, cookie, remote_addr, self.hashalg) except self.BadTicket: return None @@ -908,7 +953,9 @@ class AuthTktCookieHelper(object): tokens=tokens, user_data=user_data, cookie_name=self.cookie_name, - secure=self.secure) + secure=self.secure, + 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 2b7a770c1..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 @@ -440,13 +448,14 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase): inst.cookie = DummyCookieHelper(cookieidentity) return inst - 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, - ) - self.assertEqual(inst.callback, None) + def test_is_subclass(self): + from pyramid.authentication import BaseAuthTktAuthenticationPolicy + inst = self._makeOne(None, None) + self.assertTrue(isinstance(inst, BaseAuthTktAuthenticationPolicy)) + + 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 @@ -458,6 +467,53 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase): 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 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, + ) + self.assertEqual(inst.callback, None) + def test_unauthenticated_userid_returns_None(self): request = DummyRequest({}) policy = self._makeOne(None, None) @@ -1068,6 +1124,14 @@ class TestAuthTicket(unittest.TestCase): result = ticket.digest() self.assertEqual(result, '126fd6224912187ee9ffa80e0b81420c') + def test_digest_sha512(self): + ticket = self._makeOne('secret', 'userid', '0.0.0.0', + time=10, hashalg='sha512') + result = ticket.digest() + self.assertEqual(result, '74770b2e0d5b1a54c2a466ec567a40f7d7823576aa49'\ + '3c65fc3445e9b44097f4a80410319ef8cb256a2e60b9'\ + 'c2002e48a9e33a3e8ee4379352c04ef96d2cb278') + def test_cookie_value(self): ticket = self._makeOne('secret', 'userid', '0.0.0.0', time=10, tokens=('a', 'b')) @@ -1086,13 +1150,13 @@ class TestBadTicket(unittest.TestCase): self.assertTrue(isinstance(exc, Exception)) class Test_parse_ticket(unittest.TestCase): - def _callFUT(self, secret, ticket, ip): + def _callFUT(self, secret, ticket, ip, hashalg='md5'): from pyramid.authentication import parse_ticket - return parse_ticket(secret, ticket, ip) + return parse_ticket(secret, ticket, ip, hashalg) - def _assertRaisesBadTicket(self, secret, ticket, ip): + def _assertRaisesBadTicket(self, secret, ticket, ip, hashalg='md5'): from pyramid.authentication import BadTicket - self.assertRaises(BadTicket,self._callFUT, secret, ticket, ip) + self.assertRaises(BadTicket,self._callFUT, secret, ticket, ip, hashalg) def test_bad_timestamp(self): ticket = 'x' * 64 @@ -1111,6 +1175,13 @@ class Test_parse_ticket(unittest.TestCase): result = self._callFUT('secret', ticket, '0.0.0.0') self.assertEqual(result, (10, 'userid', ['a', 'b'], '')) + def test_correct_with_user_data_sha512(self): + ticket = '7d947cdef99bad55f8e3382a8bd089bb9dd0547f7925b7d189adc1160cab'\ + '0ec0e6888faa41eba641a18522b26f19109f3ffafb769767ba8a26d02aae'\ + 'ae56599a0000000auserid!a,b!' + result = self._callFUT('secret', ticket, '0.0.0.0', 'sha512') + self.assertEqual(result, (10, 'userid', ['a', 'b'], '')) + class TestSessionAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import SessionAuthenticationPolicy @@ -1319,13 +1390,14 @@ class DummyCookieHelper: class DummyAuthTktModule(object): def __init__(self, timestamp=0, userid='userid', tokens=(), user_data='', - parse_raise=False): + parse_raise=False, hashalg="md5"): self.timestamp = timestamp self.userid = userid self.tokens = tokens self.user_data = user_data self.parse_raise = parse_raise - def parse_ticket(secret, value, remote_addr): + self.hashalg = hashalg + def parse_ticket(secret, value, remote_addr, hashalg): self.secret = secret self.value = value self.remote_addr = remote_addr |
