Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #16 from diox/master

django 1.3 compatibility with dynamic hashers from 1.4
  • Loading branch information...
commit f4519bf0cc9b1dd7a7d78394fa4aec4504bc86e9 2 parents 3ba2b47 + 2645fb7
@fwenzel authored
View
16 django_sha2/__init__.py
@@ -1,17 +1,25 @@
VERSION = (0, 4)
__version__ = '.'.join(map(str, VERSION))
+def get_dynamic_hasher_names(HMAC_KEYS):
+ """
+ Return base dynamic hasher names for each entry in HMAC_KEYS (we need to
+ create one hasher class for each key). Names are sorted to make sure
+ the HMAC_KEYS are tested in the correct order and the first one is always
+ the first hasher name returned.
+ """
+ algo_name = lambda hmac_id: 'bcrypt{0}'.format(hmac_id.replace('-', '_'))
+ return [algo_name(key) for key in sorted(HMAC_KEYS.keys(), reverse=True)]
def get_password_hashers(BASE_PASSWORD_HASHERS, HMAC_KEYS):
"""
- Returns the names of the dynamic and regular hashers
- created in our hashers file
+ Return the names of the dynamic and regular hashers
+ created in our hashers file.
"""
# Where is the bcrypt hashers file located?
hashers_base = 'django_sha2.hashers.{0}'
- algo_name = lambda hmac_id: 'bcrypt{0}'.format(hmac_id.replace('-', '_'))
- dynamic_hasher_names = [algo_name(key) for key in HMAC_KEYS.keys()]
+ dynamic_hasher_names = get_dynamic_hasher_names(HMAC_KEYS)
dynamic_hashers = [hashers_base.format(k) for k in dynamic_hasher_names]
return dynamic_hashers + list(BASE_PASSWORD_HASHERS)
View
5 django_sha2/auth.py
@@ -13,6 +13,8 @@
from django.conf import settings
from django.contrib.auth import models as auth_models
+from django_sha2 import get_dynamic_hasher_names
+
ALGOS = (
'bcrypt',
@@ -63,7 +65,8 @@ def check_password(self, raw_password):
Supports automatic upgrading to stronger hashes.
"""
hashed_with = self.password.split('$', 1)[0]
- if hashed_with in ['bcrypt', 'hh']:
+ if hashed_with in ['bcrypt', 'hh'] or \
+ hashed_with in get_dynamic_hasher_names(settings.HMAC_KEYS):
matched = bcrypt_auth.check_password(self, raw_password)
else:
matched = check_password_old(self, raw_password)
View
2  django_sha2/bcrypt_auth.py
@@ -58,7 +58,7 @@ def check_password(user, raw_password):
except KeyError:
log.info('Invalid shared key version "{0}"'.format(key_ver))
return False
- bc_value = algo_and_hash[6:] # Yes, bcrypt <3s the leading $.
+ bc_value = algo_and_hash[algo_and_hash.find('$'):] # Yes, bcrypt <3s the leading $.
hmac_value = _hmac_create(raw_password, shared_key)
matched = _bcrypt_verify(hmac_value, bc_value)
View
3  test/django13/settings.py
@@ -26,7 +26,8 @@
## django-sha2 settings
PWD_ALGORITHM = 'bcrypt'
HMAC_KEYS = {
- '2011-01-01': 'ThisisASharedKey',
'2010-06-01': 'OldSharedKey',
+ '2011-01-01': 'ThisisASharedKey', # This is the most recent key
+ '2011-00-00': 'ThisKeyIsOldToo',
'2010-01-01': 'EvenOlderSharedKey'
}
View
40 test/django13/tests/test_bcrypt.py
@@ -21,9 +21,9 @@ def setUp(self):
def test_bcrypt_used(self):
"""Make sure bcrypt was used as the hash."""
- eq_(User.objects.get(username='john').password[:6], 'bcrypt')
- eq_(User.objects.get(username='jane').password[:6], 'bcrypt')
- eq_(User.objects.get(username='jude').password[:6], 'bcrypt')
+ eq_(User.objects.get(username='john').password[:7], 'bcrypt$')
+ eq_(User.objects.get(username='jane').password[:7], 'bcrypt$')
+ eq_(User.objects.get(username='jude').password[:7], 'bcrypt$')
def test_bcrypt_auth(self):
"""Try authenticating."""
@@ -42,6 +42,40 @@ def test_nokey(self):
assert not authenticate(username='jude', password=u'abcéäêëôøà')
assert not authenticate(username='jude', password=u'çççbbbààà')
+ def test_password_from_django14(self):
+ """Test that a password generated by django_sha2 with django 1.4 is
+ recognized and changed to a 1.3 version"""
+ # We can't easily call 1.4's hashers so we hardcode the passwords as
+ # returned with the specific salts and hmac_key in 1.4.
+ prefix = 'bcrypt2011_01_01$2a$12$'
+ suffix = '$2011-01-01'
+ raw_hashes = {
+ 'john': '02CfJWdVwLK80jlRe/Xx1u8sTHAR0JUmKV9YB4BS.Os4LK6nsoLie',
+ 'jane': '.ipDt6gRL3CPkVH7FEyR6.8YXeQFXAMyiX3mXpDh4YDBonrdofrcG',
+ 'jude': '6Ol.vgIFxMQw0LBhCLtv7OkV.oyJjen2GVMoiNcLnbsljSfYUkQqe',
+ }
+
+ u = User.objects.get(username="john")
+ django14_style_password = "%s%s%s" % (prefix, raw_hashes['john'],
+ suffix)
+ u.password = django14_style_password
+ assert u.check_password('123456')
+ eq_(u.password[:7], 'bcrypt$')
+
+ u = User.objects.get(username="jane")
+ django14_style_password = "%s%s%s" % (prefix, raw_hashes['jane'],
+ suffix)
+ u.password = django14_style_password
+ assert u.check_password('abc')
+ eq_(u.password[:7], 'bcrypt$')
+
+ u = User.objects.get(username="jude")
+ django14_style_password = "%s%s%s" % (prefix, raw_hashes['jude'],
+ suffix)
+ u.password = django14_style_password
+ assert u.check_password(u'abcéäêëôøà')
+ eq_(u.password[:7], 'bcrypt$')
+
def test_hmac_autoupdate(self):
"""Auto-update HMAC key if hash in DB is outdated."""
# Get HMAC key IDs to compare
View
7 test/django13/tests/test_sha2.py
@@ -1,6 +1,5 @@
# -*- coding:utf-8 -*-
from django import test
-from django.contrib.auth.models import get_hexdigest
from nose.tools import eq_
@@ -40,6 +39,12 @@ class Sha2Tests(test.TestCase):
def test_hexdigest(self):
"""Test various password hashes."""
+
+ # The following import need to stay inside the function to make sure
+ # monkeypatching has happened. If moved to the top the test would fail
+ # because the function has been imported too early before monkeypatch.
+ from django.contrib.auth.models import get_hexdigest
+
for algo, pws in self.HASHES.items():
for pw, hashed in pws.items():
eq_(get_hexdigest(algo, self.SALT, pw), hashed)
View
3  test/django14/settings.py
@@ -25,7 +25,8 @@
## django-sha2 settings
HMAC_KEYS = {
'2010-06-01': 'OldSharedKey',
- '2011-01-01': 'ThisisASharedKey',
+ '2011-01-01': 'ThisisASharedKey', # This is the most recent key
+ '2011-00-00': 'ThisKeyIsOldToo',
'2010-01-01': 'EvenOlderSharedKey'
}
Please sign in to comment.
Something went wrong with that request. Please try again.