diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2020-10-06 10:48:17 -0400 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2020-10-06 10:48:17 -0400 |
commit | f0e6cded325b73fe7a848b005da5a32d501b09aa (patch) | |
tree | 81db633f40f15c4decfe763aaccdc815fa1fe329 | |
parent | 6badf316805b188c92165e342ca88ce3be9a96db (diff) | |
download | passlib-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.py | 47 | ||||
-rw-r--r-- | passlib/tests/test_handlers.py | 30 | ||||
-rw-r--r-- | passlib/tests/test_handlers_django.py | 15 | ||||
-rw-r--r-- | passlib/tests/utils.py | 12 |
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 #=================================================================== |