misc: generate hints from zxcvbn report (#63831)
This commit is contained in:
parent
5de83a83d2
commit
4bb8877f3b
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue