summaryrefslogtreecommitdiff
path: root/passlib/tests
diff options
context:
space:
mode:
authorEli Collins <elic@assurancetechnologies.com>2019-11-11 18:21:19 -0500
committerEli Collins <elic@assurancetechnologies.com>2019-11-11 18:21:19 -0500
commit33fe23e32994474b92e6132a35bc77e9dcfd214a (patch)
tree501e1cc8486c59610250bb3244c657b11eeffe0c /passlib/tests
parent8c3470170628cbf6b18b48d95a69800b79b327ec (diff)
parent5cadf38764107e5b7c436421288ad770354626b4 (diff)
downloadpasslib-33fe23e32994474b92e6132a35bc77e9dcfd214a.tar.gz
Merge from stable
Diffstat (limited to 'passlib/tests')
-rw-r--r--passlib/tests/backports.py4
-rw-r--r--passlib/tests/test_apache.py120
-rw-r--r--passlib/tests/test_crypto_scrypt.py57
-rw-r--r--passlib/tests/test_handlers_django.py4
-rw-r--r--passlib/tests/test_handlers_scrypt.py1
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")