summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt20
-rw-r--r--docs/api/authentication.rst4
-rw-r--r--docs/narr/security.rst4
-rw-r--r--docs/tutorials/wiki/authorization.rst18
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py6
-rw-r--r--docs/tutorials/wiki2/authorization.rst18
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py4
-rw-r--r--pyramid/authentication.py85
-rw-r--r--pyramid/tests/test_authentication.py100
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