diff --git a/frappe/tests/test_password_strength.py b/frappe/tests/test_password_strength.py new file mode 100644 index 0000000000..5dc87d185d --- /dev/null +++ b/frappe/tests/test_password_strength.py @@ -0,0 +1,18 @@ +import random +from string import printable +from time import time +from unittest import TestCase + +from frappe.utils.password_strength import test_password_strength + + +class TestPasswordStrength(TestCase): + def test_long_password(self): + password = "".join(random.choice(printable) for _ in range(600)) + + start_second = time() + result = test_password_strength(password) + end_second = time() + + self.assertLess(end_second - start_second, 10) + self.assertIn("feedback", result) diff --git a/frappe/utils/password_strength.py b/frappe/utils/password_strength.py index 59c784e5b4..eada58c638 100644 --- a/frappe/utils/password_strength.py +++ b/frappe/utils/password_strength.py @@ -1,10 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE -try: - from zxcvbn import zxcvbn -except Exception: - import zxcvbn +from zxcvbn import zxcvbn +from zxcvbn.scoring import ALL_UPPER, START_UPPER import frappe from frappe import _ @@ -12,8 +10,14 @@ from frappe import _ def test_password_strength(password, user_inputs=None): """Wrapper around zxcvbn.password_strength""" + if len(password) > 128: + # zxcvbn takes forever when checking long, random passwords. + # repetion patterns or user inputs in the first 128 characters + # will still be checked. + password = password[:128] + result = zxcvbn(password, user_inputs) - result.update({"feedback": get_feedback(result.get("score"), result.get("sequence"))}) + result["feedback"] = get_feedback(result.get("score"), result.get("sequence")) return result @@ -21,13 +25,7 @@ def test_password_strength(password, user_inputs=None): # ------------------------------------------- # feedback functionality code from https://github.com/sans-serif/python-zxcvbn/blob/master/zxcvbn/feedback.py # see license for feedback code at https://github.com/sans-serif/python-zxcvbn/blob/master/LICENSE.txt - -# Used for regex matching capitalization -import re - -# Used to get the regex patterns for capitalization -# (Used the same way in the original zxcvbn) -from zxcvbn import scoring +# ------------------------------------------- # Default feedback value default_feedback = { @@ -177,9 +175,9 @@ def get_dictionary_match_feedback(match, is_sole_match): word = match.get("token") # Variations of the match like UPPERCASES - if scoring.START_UPPER.match(word): + if START_UPPER.match(word): suggestions.append(_("Capitalization doesn't help very much.")) - elif scoring.ALL_UPPER.match(word): + elif ALL_UPPER.match(word): suggestions.append(_("All-uppercase is almost as easy to guess as all-lowercase.")) # Match contains l33t speak substitutions diff --git a/pyproject.toml b/pyproject.toml index 484fd13d1b..965990e028 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ dependencies = [ "terminaltables~=3.1.0", "traceback-with-variables~=2.0.4", "xlrd~=2.0.1", - "zxcvbn-python~=4.4.24", + "zxcvbn~=4.4.28", "markdownify~=0.11.2", # integration dependencies