summaryrefslogtreecommitdiff
path: root/passlib/ext/django/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'passlib/ext/django/utils.py')
-rw-r--r--passlib/ext/django/utils.py70
1 files changed, 55 insertions, 15 deletions
diff --git a/passlib/ext/django/utils.py b/passlib/ext/django/utils.py
index 1d1bd67..2f8a2ef 100644
--- a/passlib/ext/django/utils.py
+++ b/passlib/ext/django/utils.py
@@ -26,13 +26,29 @@ __all__ = [
"DJANGO_VERSION",
"MIN_DJANGO_VERSION",
"get_preset_config",
- "get_django_hasher",
+ "quirks",
]
#: minimum version supported by passlib.ext.django
MIN_DJANGO_VERSION = (1, 8)
#=============================================================================
+# quirk detection
+#=============================================================================
+
+class quirks:
+
+ #: django check_password() started throwing error on encoded=None
+ #: (really identify_hasher did)
+ none_causes_check_password_error = DJANGO_VERSION >= (2, 1)
+
+ #: django is_usable_password() started returning True for password = {None, ""} values.
+ empty_is_usable_password = DJANGO_VERSION >= (2, 1)
+
+ #: django is_usable_password() started returning True for non-hash strings in 2.1
+ invalid_is_usable_password = DJANGO_VERSION >= (2, 1)
+
+#=============================================================================
# default policies
#=============================================================================
@@ -235,6 +251,13 @@ class DjangoTranslator(object):
md5="MD5PasswordHasher",
)
+ if DJANGO_VERSION > (2, 1):
+ # present but disabled by default as of django 2.1; not sure when added,
+ # so not listing it by default.
+ _builtin_django_hashers.update(
+ bcrypt="BCryptPasswordHasher",
+ )
+
def _create_django_hasher(self, django_name):
"""
helper to create new django hasher by name.
@@ -244,17 +267,22 @@ class DjangoTranslator(object):
module = sys.modules.get("passlib.ext.django.models")
if module is None or not module.adapter.patched:
from django.contrib.auth.hashers import get_hasher
- return get_hasher(django_name)
-
- # We've patched django's get_hashers(), so calling django's get_hasher()
- # or get_hashers_by_algorithm() would only land us back here.
- # As non-ideal workaround, have to use original get_hashers(),
- get_hashers = module.adapter._manager.getorig("django.contrib.auth.hashers:get_hashers").__wrapped__
- for hasher in get_hashers():
- if hasher.algorithm == django_name:
- return hasher
-
- # hardcode a few for cases where get_hashers() look won't work.
+ try:
+ return get_hasher(django_name)
+ except ValueError as err:
+ if not str(err).startswith("Unknown password hashing algorithm"):
+ raise
+ else:
+ # We've patched django's get_hashers(), so calling django's get_hasher()
+ # or get_hashers_by_algorithm() would only land us back here.
+ # As non-ideal workaround, have to use original get_hashers(),
+ get_hashers = module.adapter._manager.getorig("django.contrib.auth.hashers:get_hashers").__wrapped__
+ for hasher in get_hashers():
+ if hasher.algorithm == django_name:
+ return hasher
+
+ # hardcode a few for cases where get_hashers() lookup won't work
+ # (mainly, hashers that are present in django, but disabled by their default config)
path = self._builtin_django_hashers.get(django_name)
if path:
if "." not in path:
@@ -545,12 +573,20 @@ class DjangoContextAdapter(DjangoTranslator):
"""
# XXX: this currently ignores "preferred" keyword, since its purpose
# was for hash migration, and that's handled by the context.
+ # XXX: honor "none_causes_check_password_error" quirk for django 2.2+?
+ # seems safer to return False.
if password is None or not self.is_password_usable(encoded):
return False
# verify password
context = self.context
- correct = context.verify(password, encoded)
+ try:
+ correct = context.verify(password, encoded)
+ except exc.UnknownHashError:
+ # As of django 1.5, unidentifiable hashes returns False
+ # (side-effect of django issue 18453)
+ return False
+
if not (correct and setter):
return correct
@@ -591,8 +627,12 @@ class DjangoContextAdapter(DjangoTranslator):
if not self.is_password_usable(hash):
return False
cat = self.get_user_category(user)
- ok, new_hash = self.context.verify_and_update(password, hash,
- category=cat)
+ try:
+ ok, new_hash = self.context.verify_and_update(password, hash, category=cat)
+ except exc.UnknownHashError:
+ # As of django 1.5, unidentifiable hashes returns False
+ # (side-effect of django issue 18453)
+ return False
if ok and new_hash is not None:
# migrate to new hash if needed.
user.password = new_hash