misc: generate hints from zxcvbn report (#63831)

This commit is contained in:
Corentin Sechet 2022-09-01 23:17:48 +02:00
parent 5de83a83d2
commit 4bb8877f3b
2 changed files with 77 additions and 24 deletions

View File

@ -136,15 +136,56 @@ class StrengthReport:
def get_password_strength(password):
min_length = app_settings.A2_PASSWORD_POLICY_MIN_LENGTH
hint = _('add more words or characters')
hint = _('add more words or characters.')
strength = 0
if min_length and len(password) < min_length:
hint = _('use at least %s characters' % min_length)
hint = _('use at least %s characters.' % min_length)
elif password:
report = zxcvbn(password)
strength = report['score']
suggestions = report['feedback']['suggestions']
if len(suggestions):
hint = report['feedback']['suggestions'][0]
hint = get_hint(report['sequence'])
return StrengthReport(strength, hint)
def get_hint(matches):
matches = sorted(matches, key=lambda m: len(m['token']), reverse=True)
for match in matches:
hint = get_hint_for_match(match)
if hint:
return hint
return [_('use a longer password.')]
def get_hint_for_match(match):
pattern = match['pattern']
hint = None
if pattern == 'spatial':
if match['turns'] == 1:
hint = _('avoid straight rows of keys like "{token}".')
else:
hint = _('avoid short keyboard patterns like "{token}".')
if pattern == 'repeat':
hint = _('avoid repeated words and characters like "{token}".')
if pattern == 'sequence':
hint = _('avoid sequences like "{token}".')
if pattern == 'regex':
if match['regex_name'] == 'recent_year':
hint = _('avoid recent years.')
if pattern == 'date':
hint = _('avoid dates and years that are associated with you.')
if pattern == 'dictionary':
if match['l33t'] or match['reversed']:
hint = _('avoid "{token}" : it\'s similar to a commonly used password')
else:
hint = _('avoid "{token}" : it\'s a commonly used password.')
if hint is not None:
return hint.format(token=match['token'])
return None

View File

@ -1757,35 +1757,47 @@ def test_validate_password_regex(app, settings):
@pytest.mark.parametrize(
'password,strength,label',
'min_length, password,strength,label',
[
('?', 0, 'Very Weak'),
('?JR!', 1, 'Weak'),
('?JR!p4A', 2, 'Fair'),
('?JR!p4A2i', 3, 'Good'),
('?JR!p4A2i:#', 4, 'Strong'),
(0, '?', 0, 'Very Weak'),
(0, '?', 0, 'Very Weak'),
(0, '?JR!', 1, 'Weak'),
(0, '?JR!p4A', 2, 'Fair'),
(0, '?JR!p4A2i', 3, 'Good'),
(0, '?JR!p4A2i:#', 4, 'Strong'),
(12, '?JR!p4A2i:#', 0, 'Very Weak'),
],
)
def test_password_strength(app, settings, password, strength, label):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 0
def test_password_strength(app, settings, min_length, password, strength, label):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length
response = app.post_json('/api/password-strength/', params={'password': password})
assert response.json['result'] == 1
assert response.json['strength'] == strength
assert response.json['strength_label'] == label
def test_password_strength_min_length(app, settings):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 10
response = app.post_json('/api/password-strength/', params={'password': 'too_short'})
@pytest.mark.parametrize(
'min_length, password, hint',
[
(0, '', 'add more words or characters.'),
(0, 'sdfgh', 'avoid straight rows of keys like "sdfgh".'),
(0, 'ertgfd', 'avoid short keyboard patterns like "ertgfd".'),
(0, 'abab', 'avoid repeated words and characters like "abab".'),
(0, 'abcd', 'avoid sequences like "abcd".'),
(0, '2019', 'avoid recent years.'),
(0, '02/08/14', 'avoid dates and years that are associated with you.'),
(0, '02/08/14', 'avoid dates and years that are associated with you.'),
(0, 'p@ssword', 'avoid "p@ssword" : it\'s similar to a commonly used password'),
(0, 'password', 'avoid "password" : it\'s a commonly used password.'),
(42, 'password', 'use at least 42 characters.'),
],
)
def test_password_strength_hints(app, settings, min_length, password, hint):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 3
response = app.post_json('/api/password-strength/', params={'password': password})
assert response.json['result'] == 1
assert response.json['strength'] == 0
assert response.json['strength_label'] == 'Very Weak'
response = app.post_json('/api/password-strength/', params={'password': 'long_enough'})
assert response.json['result'] == 1
assert response.json['strength'] != 0
assert response.json['strength_label'] != 'Very Weak'
assert response.json['hint'] == hint
def test_api_users_get_or_create(settings, app, admin):