diff options
author | Eli Collins <elic@assurancetechnologies.com> | 2019-11-11 18:21:19 -0500 |
---|---|---|
committer | Eli Collins <elic@assurancetechnologies.com> | 2019-11-11 18:21:19 -0500 |
commit | 33fe23e32994474b92e6132a35bc77e9dcfd214a (patch) | |
tree | 501e1cc8486c59610250bb3244c657b11eeffe0c /passlib/tests | |
parent | 8c3470170628cbf6b18b48d95a69800b79b327ec (diff) | |
parent | 5cadf38764107e5b7c436421288ad770354626b4 (diff) | |
download | passlib-33fe23e32994474b92e6132a35bc77e9dcfd214a.tar.gz |
Merge from stable
Diffstat (limited to 'passlib/tests')
-rw-r--r-- | passlib/tests/backports.py | 4 | ||||
-rw-r--r-- | passlib/tests/test_apache.py | 120 | ||||
-rw-r--r-- | passlib/tests/test_crypto_scrypt.py | 57 | ||||
-rw-r--r-- | passlib/tests/test_handlers_django.py | 4 | ||||
-rw-r--r-- | passlib/tests/test_handlers_scrypt.py | 1 |
5 files changed, 172 insertions, 14 deletions
diff --git a/passlib/tests/backports.py b/passlib/tests/backports.py index c93b599..5058cec 100644 --- a/passlib/tests/backports.py +++ b/passlib/tests/backports.py @@ -14,7 +14,9 @@ from passlib.utils.compat import PY26 # local __all__ = [ "TestCase", - "skip", "skipIf", "skipUnless" + "unittest", + # TODO: deprecate these exports in favor of "unittest.XXX" + "skip", "skipIf", "skipUnless", ] #============================================================================= diff --git a/passlib/tests/test_apache.py b/passlib/tests/test_apache.py index 0649c8c..7f8afa5 100644 --- a/passlib/tests/test_apache.py +++ b/passlib/tests/test_apache.py @@ -6,16 +6,23 @@ from __future__ import with_statement # core from logging import getLogger import os +import subprocess # site # pkg -from passlib import apache +from passlib import apache, registry from passlib.exc import MissingBackendError from passlib.utils.compat import irange +from passlib.tests.backports import unittest from passlib.tests.utils import TestCase, get_file, set_file, ensure_mtime_changed from passlib.utils import to_bytes +from passlib.utils.handlers import to_unicode_for_identify # module log = getLogger(__name__) +#============================================================================= +# helpers +#============================================================================= + def backdate_file_mtime(path, offset=10): """backdate file's mtime by specified amount""" # NOTE: this is used so we can test code which detects mtime changes, @@ -25,6 +32,58 @@ def backdate_file_mtime(path, offset=10): os.utime(path, (atime, mtime)) #============================================================================= +# detect external HTPASSWD tool +#============================================================================= + + +htpasswd_path = os.environ.get("PASSLIB_TEST_HTPASSWD_PATH") or "htpasswd" + + +def _call_htpasswd(args, stdin=None): + """ + helper to run htpasswd cmd + """ + if stdin is not None: + stdin = stdin.encode("utf-8") + proc = subprocess.Popen([htpasswd_path] + args, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE if stdin else None) + out, err = proc.communicate(stdin) + rc = proc.wait() + out = to_unicode_for_identify(out or "") + return out, rc + + +def _call_htpasswd_verify(path, user, password): + """ + wrapper for htpasswd verify + """ + out, rc = _call_htpasswd(["-vi", path, user], password) + return not rc + + +def _detect_htpasswd(): + """ + helper to check if htpasswd is present + """ + try: + out, rc = _call_htpasswd([]) + except OSError: + # TODO: under py3, could trap the more specific FileNotFoundError + # cmd not found + return False, False + # when called w/o args, it should print usage to stderr & return rc=2 + if not rc: + log.warning("htpasswd test returned with rc=0") + have_bcrypt = " -B " in out + return True, have_bcrypt + + +HAVE_HTPASSWD, HAVE_HTPASSWD_BCRYPT = _detect_htpasswd() + +requires_htpasswd_cmd = unittest.skipUnless(HAVE_HTPASSWD, "requires `htpasswd` cmdline tool") + + +#============================================================================= # htpasswd #============================================================================= class HtpasswdFileTest(TestCase): @@ -355,6 +414,65 @@ class HtpasswdFileTest(TestCase): ) self.assertEqual(ht.to_string(), target) + @requires_htpasswd_cmd + def test_htpasswd_cmd_verify(self): + """ + verify "htpasswd" command can read output + """ + path = self.mktemp() + ht = apache.HtpasswdFile(path=path, new=True) + + def hash_scheme(pwd, scheme): + return ht.context.handler(scheme).hash(pwd) + + # base scheme + ht.set_hash("user1", hash_scheme("password","apr_md5_crypt")) + + # 2.2-compat scheme + host_no_bcrypt = apache.htpasswd_defaults["host_apache_22"] + ht.set_hash("user2", hash_scheme("password", host_no_bcrypt)) + + # 2.4-compat scheme + host_best = apache.htpasswd_defaults["host"] + ht.set_hash("user3", hash_scheme("password", host_best)) + + # unsupported scheme -- should always fail to verify + ht.set_hash("user4", "$xxx$foo$bar$baz") + + # make sure htpasswd properly recognizes hashes + ht.save() + + self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong")) + self.assertFalse(_call_htpasswd_verify(path, "user2", "wrong")) + self.assertFalse(_call_htpasswd_verify(path, "user3", "wrong")) + self.assertFalse(_call_htpasswd_verify(path, "user4", "wrong")) + + self.assertTrue(_call_htpasswd_verify(path, "user1", "password")) + self.assertTrue(_call_htpasswd_verify(path, "user2", "password")) + self.assertTrue(_call_htpasswd_verify(path, "user3", "password")) + + @requires_htpasswd_cmd + @unittest.skipUnless(registry.has_backend("bcrypt"), "bcrypt support required") + def test_htpasswd_cmd_verify_bcrypt(self): + """ + verify "htpasswd" command can read bcrypt format + + this tests for regression of issue 95, where we output "$2b$" instead of "$2y$"; + fixed in v1.7.2. + """ + path = self.mktemp() + ht = apache.HtpasswdFile(path=path, new=True) + def hash_scheme(pwd, scheme): + return ht.context.handler(scheme).hash(pwd) + ht.set_hash("user1", hash_scheme("password", "bcrypt")) + ht.save() + self.assertFalse(_call_htpasswd_verify(path, "user1", "wrong")) + if HAVE_HTPASSWD_BCRYPT: + self.assertTrue(_call_htpasswd_verify(path, "user1", "password")) + else: + # apache2.2 should fail, acting like it's an unknown hash format + self.assertFalse(_call_htpasswd_verify(path, "user1", "password")) + #=================================================================== # eoc #=================================================================== diff --git a/passlib/tests/test_crypto_scrypt.py b/passlib/tests/test_crypto_scrypt.py index 6266ab1..a3c8eef 100644 --- a/passlib/tests/test_crypto_scrypt.py +++ b/passlib/tests/test_crypto_scrypt.py @@ -560,6 +560,36 @@ class _CommonScryptTest(TestCase): # eoc #============================================================================= + +#----------------------------------------------------------------------- +# check what backends 'should' be available +#----------------------------------------------------------------------- + +def _can_import_cffi_scrypt(): + try: + import scrypt + except ImportError as err: + if "scrypt" in str(err): + return False + raise + return True + +has_cffi_scrypt = _can_import_cffi_scrypt() + + +def _can_import_stdlib_scrypt(): + try: + from hashlib import scrypt + return True + except ImportError: + return False + +has_stdlib_scrypt = _can_import_stdlib_scrypt() + +#----------------------------------------------------------------------- +# test individual backends +#----------------------------------------------------------------------- + # NOTE: builtin version runs VERY slow (except under PyPy, where it's only 11x slower), # so skipping under quick test mode. @skipUnless(PYPY or TEST_MODE(min="default"), "skipped under current test mode") @@ -573,29 +603,32 @@ class BuiltinScryptTest(_CommonScryptTest): def test_missing_backend(self): """backend management -- missing backend""" - if _can_import_scrypt(): - raise self.skipTest("'scrypt' backend is present") + if has_stdlib_scrypt or has_cffi_scrypt: + raise self.skipTest("non-builtin backend is present") self.assertRaises(exc.MissingBackendError, scrypt_mod._set_backend, 'scrypt') -def _can_import_scrypt(): - """check if scrypt package is importable""" - try: - import scrypt - except ImportError as err: - if "scrypt" in str(err): - return False - raise - return True -@skipUnless(_can_import_scrypt(), "'scrypt' package not found") +@skipUnless(has_cffi_scrypt, "'scrypt' package not found") class ScryptPackageTest(_CommonScryptTest): backend = "scrypt" def test_default_backend(self): """backend management -- default backend""" + if has_stdlib_scrypt: + raise self.skipTest("higher priority backend present") scrypt_mod._set_backend("default") self.assertEqual(scrypt_mod.backend, "scrypt") + +@skipUnless(has_stdlib_scrypt, "'hashlib.scrypt()' not found") +class StdlibScryptTest(_CommonScryptTest): + backend = "stdlib" + + def test_default_backend(self): + """backend management -- default backend""" + scrypt_mod._set_backend("default") + self.assertEqual(scrypt_mod.backend, "stdlib") + #============================================================================= # eof #============================================================================= diff --git a/passlib/tests/test_handlers_django.py b/passlib/tests/test_handlers_django.py index 5333b7c..bbb4496 100644 --- a/passlib/tests/test_handlers_django.py +++ b/passlib/tests/test_handlers_django.py @@ -391,6 +391,10 @@ class django_argon2_test(HandlerCase, _DjangoHelper): class FuzzHashGenerator(_base_argon2_test.FuzzHashGenerator): + def random_type(self): + # override default since django only uses type I (see note in class) + return "I" + def random_rounds(self): # decrease default rounds for fuzz testing to speed up volume. return self.randintgauss(1, 3, 2, 1) diff --git a/passlib/tests/test_handlers_scrypt.py b/passlib/tests/test_handlers_scrypt.py index bbd3cd7..5ab6d9f 100644 --- a/passlib/tests/test_handlers_scrypt.py +++ b/passlib/tests/test_handlers_scrypt.py @@ -102,6 +102,7 @@ class _scrypt_test(HandlerCase): return self.randintgauss(4, 10, 6, 1) # create test cases for specific backends +scrypt_stdlib_test = _scrypt_test.create_backend_case("stdlib") scrypt_scrypt_test = _scrypt_test.create_backend_case("scrypt") scrypt_builtin_test = _scrypt_test.create_backend_case("builtin") |