summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2020-10-06 10:48:17 -0400
committerEli Collins <elic@assurancetechnologies.com>2020-10-06 10:48:17 -0400
commitf0e6cded325b73fe7a848b005da5a32d501b09aa (patch)
tree81db633f40f15c4decfe763aaccdc815fa1fe329
parent6badf316805b188c92165e342ca88ce3be9a96db (diff)
downloadpasslib-f0e6cded325b73fe7a848b005da5a32d501b09aa.tar.gz
passlib.tests.test_ext_django: cleaned up detection of missing django hasher backends;
and some related helper methods.
-rw-r--r--passlib/tests/test_ext_django.py47
-rw-r--r--passlib/tests/test_handlers.py30
-rw-r--r--passlib/tests/test_handlers_django.py15
-rw-r--r--passlib/tests/utils.py12
4 files changed, 92 insertions, 12 deletions
diff --git a/passlib/tests/test_ext_django.py b/passlib/tests/test_ext_django.py
index f6ef4f1..f827a36 100644
--- a/passlib/tests/test_ext_django.py
+++ b/passlib/tests/test_ext_django.py
@@ -6,6 +6,7 @@
from __future__ import absolute_import, division, print_function
import logging; log = logging.getLogger(__name__)
import sys
+import re
# site
# pkg
from passlib import apps as _apps, exc, registry
@@ -18,7 +19,7 @@ from passlib.utils.compat import iteritems, get_method_function, u
from passlib.utils.decor import memoized_property
# tests
from passlib.tests.utils import TestCase, TEST_MODE, handler_derived_from
-from passlib.tests.test_handlers import get_handler_case, conditionally_available_hashes
+from passlib.tests.test_handlers import get_handler_case
# local
__all__ = [
"DjangoBehaviorTest",
@@ -112,6 +113,22 @@ def create_mock_setter():
setter.popstate = popstate
return setter
+
+def check_django_hasher_has_backend(name):
+ """
+ check whether django hasher is available;
+ or if it should be skipped because django lacks third-party library.
+ """
+ assert name
+ from django.contrib.auth.hashers import make_password
+ try:
+ make_password("", hasher=name)
+ return True
+ except ValueError as err:
+ if re.match("Couldn't load '.*?' algorithm .* No module named .*", str(err)):
+ return False
+ raise
+
#=============================================================================
# work up stock django config
#=============================================================================
@@ -329,6 +346,7 @@ class DjangoBehaviorTest(_ExtensionTest):
and run against the passlib extension, to verify it matches
those assumptions.
"""
+ log = self.getLogger()
patched, config = self.patched, self.config
# this tests the following methods:
# User.set_password()
@@ -441,6 +459,9 @@ class DjangoBehaviorTest(_ExtensionTest):
# User.set_password() - n/a
# User.check_password() - returns False
+ # FIXME: at some point past 1.8, some of these django started handler None differently;
+ # and/or throwing TypeError. need to investigate when that change occurred;
+ # update these tests, and maybe passlib.ext.django as well.
user = FakeUser()
user.password = None
self.assertFalse(user.check_password(PASS1))
@@ -490,26 +511,38 @@ class DjangoBehaviorTest(_ExtensionTest):
# testing various bits of per-scheme behavior.
#=======================================================
for scheme in ctx.schemes():
+
+ #
+ # TODO: break this loop up into separate parameterized tests.
+ #
+
#-------------------------------------------------------
# setup constants & imports, pick a sample secret/hash combo
#-------------------------------------------------------
+
handler = ctx.handler(scheme)
+ log.debug("testing scheme: %r => %r", scheme, handler)
deprecated = ctx.handler(scheme).deprecated
assert not deprecated or scheme != ctx.default_scheme()
try:
testcase = get_handler_case(scheme)
except exc.MissingBackendError:
- assert scheme in conditionally_available_hashes
continue
assert handler_derived_from(handler, testcase.handler)
if handler.is_disabled:
continue
- if not registry.has_backend(handler):
- # TODO: move this above get_handler_case(),
- # and omit MissingBackendError check.
+
+ # verify that django has a backend available
+ # (since our hasher may use different set of backends,
+ # get_handler_case() above may work, but django will have nothing)
+ if not patched and not check_django_hasher_has_backend(handler.django_name):
assert scheme in ["django_bcrypt", "django_bcrypt_sha256", "django_argon2"], \
"%r scheme should always have active backend" % scheme
+ # TODO: make this a SkipTest() once this loop has been parameterized.
+ log.warn("skipping scheme %r due to missing django dependancy", scheme)
continue
+
+ # find a sample (secret, hash) pair to test with
try:
secret, hash = sample_hashes[scheme]
except KeyError:
@@ -520,7 +553,9 @@ class DjangoBehaviorTest(_ExtensionTest):
break
other = 'dontletmein'
- # User.set_password() - n/a
+ #-------------------------------------------------------
+ # User.set_password() - not tested here
+ #-------------------------------------------------------
#-------------------------------------------------------
# User.check_password()+migration against known hash
diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py
index e7d2277..cad5ef9 100644
--- a/passlib/tests/test_handlers.py
+++ b/passlib/tests/test_handlers.py
@@ -10,7 +10,7 @@ import sys
import warnings
# site
# pkg
-from passlib import hash
+from passlib import exc, hash
from passlib.utils import repeat_string
from passlib.utils.compat import irange, PY3, u, get_method_function
from passlib.tests.utils import TestCase, HandlerCase, skipUnless, \
@@ -43,12 +43,30 @@ _handler_test_modules = [
]
def get_handler_case(scheme):
- """return HandlerCase instance for scheme, used by other tests"""
+ """
+ return HandlerCase instance for scheme, used by other tests.
+
+ :param scheme: name of hasher to locate test for (e.g. "bcrypt")
+
+ :raises KeyError:
+ if scheme isn't known hasher.
+
+ :raises MissingBackendError:
+ if hasher doesn't have any available backends.
+
+ :returns:
+ HandlerCase subclass (which derives from TestCase)
+ """
from passlib.registry import get_crypt_handler
handler = get_crypt_handler(scheme)
if hasattr(handler, "backends") and scheme not in _omitted_backend_tests:
- # NOTE: will throw MissingBackendError if none are installed.
- backend = handler.get_backend()
+ # XXX: if no backends available, could proceed to pick first backend for test lookup;
+ # should investigate if that would be useful to callers.
+ try:
+ backend = handler.get_backend()
+ except exc.MissingBackendError:
+ assert scheme in conditionally_available_hashes
+ raise
name = "%s_%s_test" % (scheme, backend)
else:
name = "%s_test" % scheme
@@ -60,7 +78,9 @@ def get_handler_case(scheme):
return getattr(mod, name)
except AttributeError:
pass
- raise KeyError("test case %r not found" % name)
+ # every hasher should have test suite, so if we get here, means test is either missing,
+ # misnamed, or _handler_test_modules list is out of date.
+ raise RuntimeError("can't find test case named %r for %r" % (name, scheme))
#: hashes which there may not be a backend available for,
#: and get_handler_case() may (correctly) throw a MissingBackendError
diff --git a/passlib/tests/test_handlers_django.py b/passlib/tests/test_handlers_django.py
index 50d2b33..f7c9a0d 100644
--- a/passlib/tests/test_handlers_django.py
+++ b/passlib/tests/test_handlers_django.py
@@ -5,6 +5,7 @@
from __future__ import with_statement
# core
import logging; log = logging.getLogger(__name__)
+import re
import warnings
# site
# pkg
@@ -13,7 +14,8 @@ from passlib.utils import repeat_string
from passlib.utils.compat import u
from passlib.tests.utils import TestCase, HandlerCase, skipUnless, SkipTest
from passlib.tests.test_handlers import UPASS_USD, UPASS_TABLE
-from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION
+from passlib.tests.test_ext_django import DJANGO_VERSION, MIN_DJANGO_VERSION, \
+ check_django_hasher_has_backend
# module
#=============================================================================
@@ -27,6 +29,10 @@ def vstr(version):
return ".".join(str(e) for e in version)
class _DjangoHelper(TestCase):
+ """
+ mixin for HandlerCase subclasses that are testing a hasher
+ which is also present in django.
+ """
__unittest_skip = True
#: minimum django version where hash alg is present / that we support testing against
@@ -40,10 +46,17 @@ class _DjangoHelper(TestCase):
max_django_version = None
def _require_django_support(self):
+ # make sure min django version
if DJANGO_VERSION < self.min_django_version:
raise self.skipTest("Django >= %s not installed" % vstr(self.min_django_version))
if self.max_django_version and DJANGO_VERSION > self.max_django_version:
raise self.skipTest("Django <= %s not installed" % vstr(self.max_django_version))
+
+ # make sure django has a backend for specified hasher
+ name = self.handler.django_name
+ if not check_django_hasher_has_backend(name):
+ raise self.skipTest('django hasher %r not available' % name)
+
return True
extra_fuzz_verifiers = HandlerCase.fuzz_verifiers + (
diff --git a/passlib/tests/utils.py b/passlib/tests/utils.py
index c8ab5a4..c9311b3 100644
--- a/passlib/tests/utils.py
+++ b/passlib/tests/utils.py
@@ -665,6 +665,18 @@ class TestCase(_TestCase):
wraps(orig)(value)
setattr(obj, attr, value)
+ def getLogger(self):
+ """
+ return logger named after current test.
+ """
+ cls = type(self)
+ # NOTE: conditional on qualname for PY2 compat
+ path = cls.__module__ + "." + getattr(cls, "__qualname__", cls.__name__)
+ name = self._testMethodName
+ if name:
+ path = path + "." + name
+ return logging.getLogger(path)
+
#===================================================================
# eoc
#===================================================================