0003-misc-generate-hints-from-zxcvbn-report-63831.patch
src/authentic2/passwords.py | ||
---|---|---|
136 | 136 |
def get_password_strength(password): |
137 | 137 |
min_length = app_settings.A2_PASSWORD_POLICY_MIN_LENGTH |
138 | 138 | |
139 |
hint = _('add more words or characters') |
|
139 |
hint = _('add more words or characters.')
|
|
140 | 140 |
strength = 0 |
141 | 141 |
if min_length and len(password) < min_length: |
142 |
hint = _('use at least %s characters' % min_length) |
|
142 |
hint = _('use at least %s characters.' % min_length)
|
|
143 | 143 |
elif password: |
144 | 144 |
report = zxcvbn(password) |
145 | 145 |
strength = report['score'] |
146 |
suggestions = report['feedback']['suggestions'] |
|
147 |
if len(suggestions): |
|
148 |
hint = report['feedback']['suggestions'][0] |
|
146 |
hint = get_hint(report['sequence']) |
|
149 | 147 | |
150 | 148 |
return StrengthReport(strength, hint) |
149 | ||
150 | ||
151 |
def get_hint(matches): |
|
152 |
matches = sorted(matches, key=lambda m: len(m['token']), reverse=True) |
|
153 |
for match in matches: |
|
154 |
hint = get_hint_for_match(match) |
|
155 |
if hint: |
|
156 |
return hint |
|
157 |
return [_('use a longer password.')] |
|
158 | ||
159 | ||
160 |
def get_hint_for_match(match): |
|
161 |
pattern = match['pattern'] |
|
162 |
hint = None |
|
163 |
if pattern == 'spatial': |
|
164 |
if match['turns'] == 1: |
|
165 |
hint = _('avoid straight rows of keys like "{token}".') |
|
166 |
else: |
|
167 |
hint = _('avoid short keyboard patterns like "{token}".') |
|
168 | ||
169 |
if pattern == 'repeat': |
|
170 |
hint = _('avoid repeated words and characters like "{token}".') |
|
171 | ||
172 |
if pattern == 'sequence': |
|
173 |
hint = _('avoid sequences like "{token}".') |
|
174 | ||
175 |
if pattern == 'regex': |
|
176 |
if match['regex_name'] == 'recent_year': |
|
177 |
hint = _('avoid recent years.') |
|
178 | ||
179 |
if pattern == 'date': |
|
180 |
hint = _('avoid dates and years that are associated with you.') |
|
181 | ||
182 |
if pattern == 'dictionary': |
|
183 |
if match['l33t'] or match['reversed']: |
|
184 |
hint = _('avoid "{token}" : it\'s similar to a commonly used password') |
|
185 |
else: |
|
186 |
hint = _('avoid "{token}" : it\'s a commonly used password.') |
|
187 | ||
188 |
if hint is not None: |
|
189 |
return hint.format(token=match['token']) |
|
190 | ||
191 |
return None |
tests/api/test_all.py | ||
---|---|---|
1757 | 1757 | |
1758 | 1758 | |
1759 | 1759 |
@pytest.mark.parametrize( |
1760 |
'password,strength,label', |
|
1760 |
'min_length, password,strength,label',
|
|
1761 | 1761 |
[ |
1762 |
('?', 0, 'Very Weak'), |
|
1763 |
('?JR!', 1, 'Weak'), |
|
1764 |
('?JR!p4A', 2, 'Fair'), |
|
1765 |
('?JR!p4A2i', 3, 'Good'), |
|
1766 |
('?JR!p4A2i:#', 4, 'Strong'), |
|
1762 |
(0, '?', 0, 'Very Weak'), |
|
1763 |
(0, '?', 0, 'Very Weak'), |
|
1764 |
(0, '?JR!', 1, 'Weak'), |
|
1765 |
(0, '?JR!p4A', 2, 'Fair'), |
|
1766 |
(0, '?JR!p4A2i', 3, 'Good'), |
|
1767 |
(0, '?JR!p4A2i:#', 4, 'Strong'), |
|
1768 |
(12, '?JR!p4A2i:#', 0, 'Very Weak'), |
|
1767 | 1769 |
], |
1768 | 1770 |
) |
1769 |
def test_password_strength(app, settings, password, strength, label): |
|
1770 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 0
|
|
1771 |
def test_password_strength(app, settings, min_length, password, strength, label):
|
|
1772 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length
|
|
1771 | 1773 |
response = app.post_json('/api/password-strength/', params={'password': password}) |
1772 | 1774 |
assert response.json['result'] == 1 |
1773 | 1775 |
assert response.json['strength'] == strength |
1774 | 1776 |
assert response.json['strength_label'] == label |
1775 | 1777 | |
1776 | 1778 | |
1777 |
def test_password_strength_min_length(app, settings): |
|
1778 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 10 |
|
1779 | ||
1780 |
response = app.post_json('/api/password-strength/', params={'password': 'too_short'}) |
|
1781 |
assert response.json['result'] == 1 |
|
1782 |
assert response.json['strength'] == 0 |
|
1783 |
assert response.json['strength_label'] == 'Very Weak' |
|
1784 | ||
1785 |
response = app.post_json('/api/password-strength/', params={'password': 'long_enough'}) |
|
1779 |
@pytest.mark.parametrize( |
|
1780 |
'min_length, password, hint', |
|
1781 |
[ |
|
1782 |
(0, '', 'add more words or characters.'), |
|
1783 |
(0, 'sdfgh', 'avoid straight rows of keys like "sdfgh".'), |
|
1784 |
(0, 'ertgfd', 'avoid short keyboard patterns like "ertgfd".'), |
|
1785 |
(0, 'abab', 'avoid repeated words and characters like "abab".'), |
|
1786 |
(0, 'abcd', 'avoid sequences like "abcd".'), |
|
1787 |
(0, '2019', 'avoid recent years.'), |
|
1788 |
(0, '02/08/14', 'avoid dates and years that are associated with you.'), |
|
1789 |
(0, '02/08/14', 'avoid dates and years that are associated with you.'), |
|
1790 |
(0, 'p@ssword', 'avoid "p@ssword" : it\'s similar to a commonly used password'), |
|
1791 |
(0, 'password', 'avoid "password" : it\'s a commonly used password.'), |
|
1792 |
(42, 'password', 'use at least 42 characters.'), |
|
1793 |
], |
|
1794 |
) |
|
1795 |
def test_password_strength_hints(app, settings, min_length, password, hint): |
|
1796 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length |
|
1797 |
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 3 |
|
1798 |
response = app.post_json('/api/password-strength/', params={'password': password}) |
|
1786 | 1799 |
assert response.json['result'] == 1 |
1787 |
assert response.json['strength'] != 0 |
|
1788 |
assert response.json['strength_label'] != 'Very Weak' |
|
1800 |
assert response.json['hint'] == hint |
|
1789 | 1801 | |
1790 | 1802 | |
1791 | 1803 |
def test_api_users_get_or_create(settings, app, admin): |
1792 |
- |