summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2013-12-22 15:25:02 -0500
committerEli Collins <elic@assurancetechnologies.com>2013-12-22 15:25:02 -0500
commitbffea42e623aa7229311f9b59144f600a8093815 (patch)
tree5d1f92c84cb84e5390f24282271255862f159f4d
parentd48644569397bfe529bc05a68ecd21d031550fa6 (diff)
downloadpasslib-bffea42e623aa7229311f9b59144f600a8093815.tar.gz
django compatibility part 2
* added implementation of django 1.6's bcrypt_sha256 hasher, and UTs * added django16 premade context to passlib.apps, made it default django_context * test_ext_django now makes use of django16_context * passlib.ext.django.utils.get_preset_config() now uses django16_context * tox 'django' and 'django-py3' now test bcrypt integration
-rw-r--r--docs/lib/passlib.apps.rst14
-rw-r--r--docs/lib/passlib.hash.django_std.rst2
-rw-r--r--passlib/apps.py14
-rw-r--r--passlib/ext/django/utils.py42
-rw-r--r--passlib/handlers/django.py72
-rw-r--r--passlib/registry.py1
-rw-r--r--passlib/tests/test_ext_django.py38
-rw-r--r--passlib/tests/test_handlers.py2
-rw-r--r--passlib/tests/test_handlers_django.py103
-rw-r--r--passlib/tests/utils.py3
-rw-r--r--tox.ini30
11 files changed, 253 insertions, 68 deletions
diff --git a/docs/lib/passlib.apps.rst b/docs/lib/passlib.apps.rst
index dee0125..c10496f 100644
--- a/docs/lib/passlib.apps.rst
+++ b/docs/lib/passlib.apps.rst
@@ -79,15 +79,23 @@ supported by the particular Django version.
.. versionadded:: 1.6
+.. data:: django16_context
+
+ The object replicates the stock password hashing policy for Django 1.6.
+ It supports all the Django 1.0-1.6 hashes, and defaults to
+ :class:`~passlib.hash.django_pbkdf2_sha256`. It treats all
+ Django 1.0 hashes as deprecated.
+
+ .. versionadded:: 1.6.2
+
.. data:: django_context
This alias will always point to the latest preconfigured Django
context supported by Passlib, and as such should support
all historical hashes built into Django.
- .. versionchanged:: 1.6
- This previously was an alias for :data:`django10_context`,
- and now points to :data:`django14_context`.
+ .. versionchanged:: 1.6.2
+ This now points to :data:`django16_context`.
.. _ldap-contexts:
diff --git a/docs/lib/passlib.hash.django_std.rst b/docs/lib/passlib.hash.django_std.rst
index c92e5d6..d8a8dee 100644
--- a/docs/lib/passlib.hash.django_std.rst
+++ b/docs/lib/passlib.hash.django_std.rst
@@ -73,6 +73,8 @@ Interface
.. versionadded:: 1.6
+.. autoclass:: django_bcrypt_sha256()
+
Format
------
An example :class:`!django_pbkdf2_sha256` hash (of ``password``) is:
diff --git a/passlib/apps.py b/passlib/apps.py
index 0afb73a..96308a4 100644
--- a/passlib/apps.py
+++ b/passlib/apps.py
@@ -99,14 +99,22 @@ django10_context = LazyCryptContext(
deprecated=["hex_md5"],
)
+_django14_schemes = ["django_pbkdf2_sha256", "django_pbkdf2_sha1",
+ "django_bcrypt"] + _django10_schemes
django14_context = LazyCryptContext(
- schemes=["django_pbkdf2_sha256", "django_pbkdf2_sha1", "django_bcrypt"] \
- + _django10_schemes,
+ schemes=_django14_schemes,
+ deprecated=_django10_schemes,
+)
+
+_django16_schemes = _django14_schemes[:]
+_django16_schemes.insert(1, "django_bcrypt_sha256")
+django16_context = LazyCryptContext(
+ schemes=_django16_schemes,
deprecated=_django10_schemes,
)
# this will always point to latest version
-django_context = django14_context
+django_context = django16_context
#=============================================================================
# ldap
diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py
index ab10b6f..161212b 100644
--- a/passlib/ext/django/utils.py
+++ b/passlib/ext/django/utils.py
@@ -28,6 +28,15 @@ __all__ = [
#=============================================================================
# default policies
#=============================================================================
+
+# map preset names -> passlib.app attrs
+_preset_map = {
+ "django-1.0": "django10_context",
+ "django-1.4": "django14_context",
+ "django-1.6": "django16_context",
+ "django-latest": "django_context",
+}
+
def get_preset_config(name):
"""Returns configuration string for one of the preset strings
supported by the ``PASSLIB_CONFIG`` setting.
@@ -35,36 +44,41 @@ def get_preset_config(name):
* ``"passlib-default"`` - default config used by this release of passlib.
* ``"django-default"`` - config matching currently installed django version.
- * ``"django-latest"`` - config matching newest django version (currently same as ``"django-1.4"``).
+ * ``"django-latest"`` - config matching newest django version (currently same as ``"django-1.6"``).
* ``"django-1.0"`` - config used by stock Django 1.0 - 1.3 installs
- * ``"django-1.4"`` -config used by stock Django 1.4 installs
+ * ``"django-1.4"`` - config used by stock Django 1.4 installs
+ * ``"django-1.6"`` - config used by stock Django 1.6 installs
"""
# TODO: add preset which includes HASHERS + PREFERRED_HASHERS,
- # after having imported any custom hashers. "django-current"
+ # after having imported any custom hashers. e.g. "django-current"
if name == "django-default":
- if (0,0) < DJANGO_VERSION < (1,4):
+ if not DJANGO_VERSION:
+ raise ValueError("can't resolve django-default preset, "
+ "django not installed")
+ if DJANGO_VERSION < (1,4):
name = "django-1.0"
- else:
+ elif DJANGO_VERSION < (1,6):
name = "django-1.4"
- if name == "django-1.0":
- from passlib.apps import django10_context
- return django10_context.to_string()
- if name == "django-1.4" or name == "django-latest":
- from passlib.apps import django14_context
- return django14_context.to_string()
+ else:
+ name = "django-1.6"
if name == "passlib-default":
return PASSLIB_DEFAULT
- raise ValueError("unknown preset config name: %r" % name)
+ try:
+ attr = _preset_map[name]
+ except KeyError:
+ raise ValueError("unknown preset config name: %r" % name)
+ import passlib.apps
+ return getattr(passlib.apps, attr).to_string()
# default context used by passlib 1.6
PASSLIB_DEFAULT = """
[passlib]
; list of schemes supported by configuration
-; currently all django 1.4 hashes, django 1.0 hashes,
+; currently all django 1.6, 1.4, and 1.0 hashes,
; and three common modular crypt format hashes.
schemes =
- django_pbkdf2_sha256, django_pbkdf2_sha1, django_bcrypt,
+ django_pbkdf2_sha256, django_pbkdf2_sha1, django_bcrypt, django_bcrypt_sha256,
django_salted_sha1, django_salted_md5, django_des_crypt, hex_md5,
sha512_crypt, bcrypt, phpass
diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py
index b643a66..83e8860 100644
--- a/passlib/handlers/django.py
+++ b/passlib/handlers/django.py
@@ -4,12 +4,14 @@
#=============================================================================
# core
from base64 import b64encode
-from hashlib import md5, sha1
+from binascii import hexlify
+from hashlib import md5, sha1, sha256
import re
import logging; log = logging.getLogger(__name__)
from warnings import warn
# site
# pkg
+from passlib.hash import bcrypt
from passlib.utils import to_unicode, classproperty
from passlib.utils.compat import b, bytes, str_to_uascii, uascii_to_str, unicode, u
from passlib.utils.pbkdf2 import pbkdf2
@@ -28,6 +30,8 @@ __all__ = [
#=============================================================================
# lazy imports & constants
#=============================================================================
+
+# imported by django_des_crypt._calc_checksum()
des_crypt = None
def _import_des_crypt():
@@ -162,7 +166,7 @@ class django_salted_md5(DjangoSaltedHash):
secret = secret.encode("utf-8")
return str_to_uascii(md5(self.salt.encode("ascii") + secret).hexdigest())
-django_bcrypt = uh.PrefixWrapper("django_bcrypt", "bcrypt",
+django_bcrypt = uh.PrefixWrapper("django_bcrypt", bcrypt,
prefix=u('bcrypt$'), ident=u("bcrypt$"),
# NOTE: this docstring is duplicated in the docs, since sphinx
# seems to be having trouble reading it via autodata::
@@ -181,6 +185,70 @@ django_bcrypt = uh.PrefixWrapper("django_bcrypt", "bcrypt",
""")
django_bcrypt.django_name = "bcrypt"
+class django_bcrypt_sha256(bcrypt):
+ """This class implements Django 1.6's Bcrypt+SHA256 hash, and follows the :ref:`password-hash-api`.
+
+ It supports a variable-length salt, and a variable number of rounds.
+
+ While the algorithm and format is somewhat different,
+ the api and options for this hash are identical to :class:`!bcrypt` itself,
+ see :doc:`/lib/passlib.hash.bcrypt` for more details.
+
+ .. versionadded:: 1.6.2
+ """
+ name = "django_bcrypt_sha256"
+ django_name = "bcrypt_sha256"
+ _digest = sha256
+
+ # NOTE: django bcrypt ident locked at "$2a$", so omitting 'ident' support.
+ setting_kwds = ("salt", "rounds")
+
+ # sample hash:
+ # bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu
+
+ # XXX: we can't use .ident attr due to bcrypt code using it.
+ # working around that via django_prefix
+ django_prefix = u('bcrypt_sha256$')
+
+ @classmethod
+ def identify(cls, hash):
+ hash = uh.to_unicode_for_identify(hash)
+ if not hash:
+ return False
+ return hash.startswith(cls.django_prefix)
+
+ @classmethod
+ def from_string(cls, hash):
+ hash = to_unicode(hash, "ascii", "hash")
+ if not hash.startswith(cls.django_prefix):
+ raise uh.exc.InvalidHashError(cls)
+ bhash = hash[len(cls.django_prefix):]
+ if not bhash.startswith("$2"):
+ raise uh.exc.MalformedHashError(cls)
+ return super(django_bcrypt_sha256, cls).from_string(bhash)
+
+ def __init__(self, **kwds):
+ if 'ident' in kwds and kwds.get("use_defaults"):
+ raise TypeError("%s does not support the ident keyword" %
+ self.__class__.__name__)
+ return super(django_bcrypt_sha256, self).__init__(**kwds)
+
+ def to_string(self):
+ bhash = super(django_bcrypt_sha256, self).to_string()
+ return uascii_to_str(self.django_prefix) + bhash
+
+ def _calc_checksum(self, secret):
+ if isinstance(secret, unicode):
+ secret = secret.encode("utf-8")
+ secret = hexlify(self._digest(secret).digest())
+ return super(django_bcrypt_sha256, self)._calc_checksum(secret)
+
+ # patch set_backend so it modifies bcrypt class, not this one...
+ # else it would clobber our _calc_checksum() wrapper above.
+ @classmethod
+ def set_backend(cls, *args, **kwds):
+ return bcrypt.set_backend(*args, **kwds)
+
class django_pbkdf2_sha256(DjangoVariableHash):
"""This class implements Django's PBKDF2-HMAC-SHA256 hash, and follows the :ref:`password-hash-api`.
diff --git a/passlib/registry.py b/passlib/registry.py
index 2616b8c..5a8055c 100644
--- a/passlib/registry.py
+++ b/passlib/registry.py
@@ -91,6 +91,7 @@ _locations = dict(
crypt16 = "passlib.handlers.des_crypt",
des_crypt = "passlib.handlers.des_crypt",
django_bcrypt = "passlib.handlers.django",
+ django_bcrypt_sha256 = "passlib.handlers.django",
django_pbkdf2_sha256 = "passlib.handlers.django",
django_pbkdf2_sha1 = "passlib.handlers.django",
django_salted_sha1 = "passlib.handlers.django",
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index d522386..54a6a78 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -9,7 +9,7 @@ import sys
import warnings
# site
# pkg
-from passlib.apps import django10_context, django14_context
+from passlib.apps import django10_context, django14_context, django16_context
from passlib.context import CryptContext
import passlib.exc as exc
from passlib.utils.compat import iteritems, unicode, get_method_function, u, PY3
@@ -109,22 +109,28 @@ def create_mock_setter():
# work up stock django config
#=============================================================================
sample_hashes = {} # override sample hashes used in test cases
-if has_django14:
- # have to modify this a little -
- # all but pbkdf2_sha256 will be deprecated here,
- # whereas preconfigured passlib policy is more permissive
+if DJANGO_VERSION >= (1,6):
+ stock_config = django16_context.to_dict()
+ stock_config.update(
+ deprecated="auto"
+ )
+ sample_hashes.update(
+ django_pbkdf2_sha256=("not a password", "pbkdf2_sha256$12000$rpUPFQOVetrY$cEcWG4DjjDpLrDyXnduM+XJUz25U63RcM3//xaFnBnw="),
+ )
+elif DJANGO_VERSION >= (1,4):
stock_config = django14_context.to_dict()
- stock_config['deprecated'] = ["django_pbkdf2_sha1", "django_bcrypt"] + stock_config['deprecated']
- if DJANGO_VERSION >= (1,6):
- sample_hashes.update(
- django_pbkdf2_sha256=("not a password", "pbkdf2_sha256$12000$rpUPFQOVetrY$cEcWG4DjjDpLrDyXnduM+XJUz25U63RcM3//xaFnBnw="),
- )
-elif has_django1:
+ stock_config.update(
+ deprecated="auto",
+ django_pbkdf2_sha256__default_rounds=10000,
+ )
+elif DJANGO_VERSION >= (1,0):
stock_config = django10_context.to_dict()
else:
# 0.9.6 config
- stock_config = dict(schemes=["django_salted_sha1", "django_salted_md5", "hex_md5"],
- deprecated=["hex_md5"])
+ stock_config = dict(
+ schemes=["django_salted_sha1", "django_salted_md5", "hex_md5"],
+ deprecated=["hex_md5"]
+ )
#=============================================================================
# test utils
@@ -618,8 +624,10 @@ class DjangoBehaviorTest(_ExtensionTest):
self.assertTrue(user.check_password(secret))
# check if it upgraded the hash
+ # NOTE: needs_update kept separate in case we need to test rounds.
needs_update = deprecated
if needs_update:
+ self.assertNotEqual(user.password, hash)
self.assertFalse(handler.identify(user.password))
self.assertTrue(ctx.handler().verify(secret, user.password))
self.assert_valid_password(user, saved=user.password)
@@ -798,7 +806,9 @@ class DjangoExtensionTest(_ExtensionTest):
"test PASSLIB_CONFIG='<preset>'"
# test django presets
self.load_extension(PASSLIB_CONTEXT="django-default", check=False)
- if has_django14:
+ if DJANGO_VERSION >= (1,6):
+ ctx = django16_context
+ elif DJANGO_VERSION >= (1,4):
ctx = django14_context
else:
ctx = django10_context
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index 1a772ca..d300a84 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -33,7 +33,7 @@ def get_handler_case(scheme):
"return HandlerCase instance for scheme, used by other tests"
from passlib.registry import get_crypt_handler
handler = get_crypt_handler(scheme)
- if hasattr(handler, "backends") and not hasattr(handler, "wrapped"):
+ if hasattr(handler, "backends") and not hasattr(handler, "wrapped") and handler.name != "django_bcrypt_sha256":
backend = handler.get_backend()
name = "%s_%s_test" % (scheme, backend)
else:
diff --git a/passlib/tests/test_handlers_django.py b/passlib/tests/test_handlers_django.py
index ac5d1f9..00a2f9b 100644
--- a/passlib/tests/test_handlers_django.py
+++ b/passlib/tests/test_handlers_django.py
@@ -21,23 +21,30 @@ from passlib.tests.test_handlers import UPASS_WAV, UPASS_USD, UPASS_TABLE
#=============================================================================
# django
#=============================================================================
+
+# standard string django uses
+UPASS_LETMEIN = u('l\xe8tmein')
+
+def vstr(version):
+ return ".".join(str(e) for e in version)
+
class _DjangoHelper(object):
# NOTE: not testing against Django < 1.0 since it doesn't support
# most of these hash formats.
- # flag if hash wasn't added until Django 1.4
- requires14 = False
+ # flag that hash wasn't added until specified version
+ min_django_version = ()
def fuzz_verifier_django(self):
from passlib.tests.test_ext_django import DJANGO_VERSION
- if DJANGO_VERSION < (1,0):
- return None
- if self.requires14 and DJANGO_VERSION < (1,4):
+ # check_password() not added until 1.0
+ min_django_version = max(self.min_django_version, (1,0))
+ if DJANGO_VERSION < min_django_version:
return None
from django.contrib.auth.models import check_password
def verify_django(secret, hash):
"django/check_password"
- if DJANGO_VERSION >= (1,4) and not secret:
+ if (1,4) <= DJANGO_VERSION < (1,6) and not secret:
return "skip"
if self.handler.name == "django_bcrypt" and hash.startswith("bcrypt$$2y$"):
hash = hash.replace("$$2y$", "$$2a$")
@@ -51,16 +58,16 @@ class _DjangoHelper(object):
def test_90_django_reference(self):
"run known correct hashes through Django's check_password()"
- from passlib.tests.test_ext_django import has_django1, has_django14
- if self.requires14 and not has_django14:
- raise self.skipTest("Django >= 1.4 not installed")
- if not has_django1:
- raise self.skipTest("Django >= 1.0 not installed")
+ from passlib.tests.test_ext_django import DJANGO_VERSION
+ # check_password() not added until 1.0
+ min_django_version = max(self.min_django_version, (1,0))
+ if DJANGO_VERSION < min_django_version:
+ raise self.skipTest("Django >= %s not installed" % vstr(min_django_version))
from django.contrib.auth.models import check_password
assert self.known_correct_hashes
for secret, hash in self.iter_known_hashes():
- if has_django14 and not secret:
- # django 1.4 rejects empty passwords
+ if (1,4) <= DJANGO_VERSION < (1,6) and not secret:
+ # django 1.4-1.5 rejects empty passwords
self.assertFalse(check_password(secret, hash),
"empty string should not have verified")
continue
@@ -76,8 +83,10 @@ class _DjangoHelper(object):
def test_91_django_generation(self):
"test against output of Django's make_password()"
from passlib.tests.test_ext_django import DJANGO_VERSION
- if DJANGO_VERSION < (1,4):
- raise self.skipTest("Django >= 1.4 not installed")
+ # make_password() not added until 1.4
+ min_django_version = max(self.min_django_version, (1,4))
+ if DJANGO_VERSION < min_django_version:
+ raise self.skipTest("Django >= %s not installed" % vstr(min_django_version))
from passlib.utils import tick
from django.contrib.auth.hashers import make_password
name = self.handler.django_name # set for all the django_* handlers
@@ -229,7 +238,7 @@ class django_salted_sha1_test(HandlerCase, _DjangoHelper):
class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
"test django_pbkdf2_sha256"
handler = hash.django_pbkdf2_sha256
- requires14 = True
+ min_django_version = (1,4)
known_correct_hashes = [
#
@@ -244,7 +253,7 @@ class django_pbkdf2_sha256_test(HandlerCase, _DjangoHelper):
class django_pbkdf2_sha1_test(HandlerCase, _DjangoHelper):
"test django_pbkdf2_sha1"
handler = hash.django_pbkdf2_sha1
- requires14 = True
+ min_django_version = (1,4)
known_correct_hashes = [
#
@@ -260,7 +269,7 @@ class django_bcrypt_test(HandlerCase, _DjangoHelper):
"test django_bcrypt"
handler = hash.django_bcrypt
secret_size = 72
- requires14 = True
+ min_django_version = (1,4)
known_correct_hashes = [
#
@@ -292,6 +301,64 @@ class django_bcrypt_test(HandlerCase, _DjangoHelper):
django_bcrypt_test = skipUnless(hash.bcrypt.has_backend(),
"no bcrypt backends available")(django_bcrypt_test)
+class django_bcrypt_sha256_test(HandlerCase, _DjangoHelper):
+ "test django_bcrypt_sha256"
+ handler = hash.django_bcrypt_sha256
+ min_django_version = (1,6)
+ forbidden_characters = None
+
+ known_correct_hashes = [
+ #
+ # custom - generated via django 1.6 hasher
+ #
+ ('',
+ 'bcrypt_sha256$$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu'),
+ (UPASS_LETMEIN,
+ 'bcrypt_sha256$$2a$08$NDjSAIcas.EcoxCRiArvT.MkNiPYVhrsrnJsRkLueZOoV1bsQqlmC'),
+ (UPASS_TABLE,
+ 'bcrypt_sha256$$2a$06$kCXUnRFQptGg491siDKNTu8RxjBGSjALHRuvhPYNFsa4Ea5d9M48u'),
+
+ # test >72 chars is hashed correctly -- under bcrypt these hash the same.
+ (repeat_string("abc123",72),
+ 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OySmyXA8FoY4PjGizjE1QSDfuL5MXNni'),
+ (repeat_string("abc123",72)+"qwr",
+ 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61Ocy0BEz1RK6xslSNi8PlaLX2pe7x/KQG'),
+ (repeat_string("abc123",72)+"xyz",
+ 'bcrypt_sha256$$2a$06$Tg/oYyZTyAf.Nb3qSgN61OvY2zoRVUa2Pugv2ExVOUT2YmhvxUFUa'),
+ ]
+
+ known_malformed_hashers = [
+ # data in django salt field
+ 'bcrypt_sha256$xyz$2a$06$/3OeRpbOf8/l6nPPRdZPp.nRiyYqPobEZGdNRBWihQhiFDh1ws1tu',
+ ]
+
+ def test_30_HasManyIdents(self):
+ raise self.skipTest("multiple idents not supported")
+
+ def test_30_HasOneIdent(self):
+ # forbidding ident keyword, django doesn't support configuring this
+ handler = self.handler
+ handler(use_defaults=True)
+ self.assertRaises(TypeError, handler, ident="$2a$", use_defaults=True)
+
+ # NOTE: the following have been cloned from _bcrypt_test()
+
+ def populate_settings(self, kwds):
+ # speed up test w/ lower rounds
+ kwds.setdefault("rounds", 4)
+ super(django_bcrypt_sha256_test, self).populate_settings(kwds)
+
+ def fuzz_setting_rounds(self):
+ # decrease default rounds for fuzz testing to speed up volume.
+ return randintgauss(5, 8, 6, 1)
+
+ def fuzz_setting_ident(self):
+ # omit multi-ident tests, only $2a$ counts for this class
+ return None
+
+django_bcrypt_sha256_test = skipUnless(hash.bcrypt.has_backend(),
+ "no bcrypt backends available")(django_bcrypt_sha256_test)
+
#=============================================================================
# eof
#=============================================================================
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index 2965689..f4dc811 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -1152,9 +1152,10 @@ class HandlerCase(TestCase):
c3 = self.do_genconfig(salt=s1[:-1])
self.assertNotEqual(c3, c1)
+ # XXX: make this a class-level flag
def prepare_salt(self, salt):
"prepare generated salt"
- if self.handler.name in ["bcrypt", "django_bcrypt"]:
+ if self.handler.name in ["bcrypt", "django_bcrypt", "django_bcrypt_sha256"]:
from passlib.utils import bcrypt64
salt = bcrypt64.repair_unused(salt)
return salt
diff --git a/tox.ini b/tox.ini
index 10e254e..e9b8ea1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -115,50 +115,50 @@ commands =
#===========================================================================
[testenv:django12]
deps =
- nose
- unittest2
django<1.3
+ {[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
[testenv:django13]
deps =
- nose
- unittest2
django<1.4
+ {[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
[testenv:django14]
deps =
- nose
- unittest2
django<1.5
+ bcrypt
+ {[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
[testenv:django15]
deps =
- nose
- unittest2
django<1.6
+ bcrypt
+ {[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
[testenv:django]
+# NOTE: including bcrypt so django bcrypt hasher is included
deps =
- nose
- unittest2
django
+ bcrypt
+ {[testenv]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
[testenv:django-py3]
+# NOTE: including bcrypt so django bcrypt hasher is included
basepython = python3
deps =
- nose
- unittest2py3k
django
+ bcrypt
+ {[testenv:py32]deps}
commands =
nosetests {posargs:passlib.tests.test_ext_django passlib.tests.test_handlers_django}
@@ -168,10 +168,16 @@ commands =
[testenv:pypy]
# pypy (as of v1.6 - v2.2) targets Python 2.7
basepython = pypy
+deps =
+ bcrypt
+ {[testenv]deps}
[testenv:pypy3]
# pypy3 (as of v2.1b1) targets Python 3.2
basepython = pypy3
+deps =
+ bcrypt
+ {[testenv:py32]deps}
#===========================================================================
# Jython - no special directives, currently same as py25