0003-misc-generate-hints-from-zxcvbn-report-63831.patch
src/authentic2/passwords.py | ||
---|---|---|
122 | 122 |
elif password: |
123 | 123 |
report = zxcvbn(password) |
124 | 124 |
strength = report['score'] |
125 |
hints = report['feedback']['suggestions']
|
|
125 |
hints = get_hints(report['sequence'])
|
|
126 | 126 | |
127 | 127 |
return self.StrengthReport(strength >= self.min_strength, strength, hints) |
128 | 128 | |
... | ... | |
145 | 145 |
yield self.Check(result=bool(re.match(self.regexp, password)), label=self.regexp_label) |
146 | 146 | |
147 | 147 | |
148 |
def get_hints(matches): |
|
149 |
matches = sorted(matches, key=lambda m: len(m['token']), reverse=True) |
|
150 |
hints = [get_hint_for_match(m) for m in matches] |
|
151 |
hints = list(h for h in hints if h) |
|
152 |
if hints: |
|
153 |
return hints |
|
154 |
return [_('Use a longer password.')] |
|
155 | ||
156 | ||
157 |
def get_hint_for_match(match): |
|
158 |
pattern = match['pattern'] |
|
159 |
hint = None |
|
160 |
if pattern == 'spatial': |
|
161 |
if match['turns'] == 1: |
|
162 |
hint = _('Avoid straight rows of keys like "{token}".') |
|
163 |
else: |
|
164 |
hint = _('Avoid short keyboard patterns like "{token}".') |
|
165 | ||
166 |
if pattern == 'repeat': |
|
167 |
hint = _('Avoid repeated words and characters like "{token}".') |
|
168 | ||
169 |
if pattern == 'sequence': |
|
170 |
hint = _('Avoid sequences like "{token}".') |
|
171 | ||
172 |
if pattern == 'regex': |
|
173 |
if match['regex_name'] == 'recent_year': |
|
174 |
hint = _('Avoid recent years.') |
|
175 | ||
176 |
if pattern == 'date': |
|
177 |
hint = _('Avoid dates and years that are associated with you.') |
|
178 | ||
179 |
if pattern == 'dictionary': |
|
180 |
if match['l33t'] or match['reversed']: |
|
181 |
hint = _('Avoid "{token}" : it\'s similar to a commonly used password') |
|
182 |
else: |
|
183 |
hint = _('Avoid "{token}" : it\'s a commonly used password.') |
|
184 | ||
185 |
if hint is not None: |
|
186 |
return hint.format(token=match['token']) |
|
187 | ||
188 |
return None |
|
189 | ||
190 | ||
148 | 191 |
def get_password_checker(*args, **kwargs): |
149 | 192 |
return import_string(app_settings.A2_PASSWORD_POLICY_CLASS)(*args, **kwargs) |
150 | 193 |
tests/api/test_all.py | ||
---|---|---|
1756 | 1756 |
assert response.json['checks'][4]['result'] is True |
1757 | 1757 | |
1758 | 1758 | |
1759 |
def test_validate_password_strength(app, settings): |
|
1759 |
@pytest.mark.parametrize( |
|
1760 |
'password, strength', |
|
1761 |
[ |
|
1762 |
('?', 0), |
|
1763 |
('?JR!', 1), |
|
1764 |
('?JR!p4"', 2), |
|
1765 |
('?JR!p4A2"', 3), |
|
1766 |
('?JR!p4A2i:"', 4), |
|
1767 |
], |
|
1768 |
) |
|
1769 |
def test_validate_password_strength(app, settings, password, strength): |
|
1760 | 1770 |
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 3 |
1761 | 1771 |
response = app.post_json('/api/validate-password/', params={'password': 'short'}) |
1762 | 1772 |
assert response.json['result'] == 1 |
... | ... | |
1764 | 1774 |
assert response.json['ok'] is False |
1765 | 1775 |
assert response.json['hints'] == ['Use at least 8 characters'] |
1766 | 1776 | |
1767 |
response = app.post_json('/api/validate-password/', params={'password': 'w34k P455w0rd'}) |
|
1777 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 0 |
|
1778 |
settings.A2_PASSWORD_POLICY_MIN_CLASSES = 0 |
|
1779 |
response = app.post_json('/api/validate-password/', params={'password': password}) |
|
1768 | 1780 |
assert response.json['result'] == 1 |
1769 |
assert response.json['ok'] is False |
|
1770 |
assert response.json['hints'] == [ |
|
1771 |
'Add another word or two. Uncommon words are better.', |
|
1772 |
'Capitalization doesn\'t help very much.', |
|
1773 |
'Predictable substitutions like \'@\' instead of \'a\' don\'t help very much.', |
|
1774 |
] |
|
1781 |
assert response.json['ok'] == (strength >= 3) |
|
1782 |
assert response.json['strength_label'] == ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'][strength] |
|
1783 |
assert response.json['strength'] == strength |
|
1784 | ||
1775 | 1785 | |
1776 |
response = app.post_json('/api/validate-password/', params={'password': 'xbA2E4]#o'}) |
|
1786 |
@pytest.mark.parametrize( |
|
1787 |
'password, hint', |
|
1788 |
[ |
|
1789 |
('sdfgh', 'Avoid straight rows of keys like "sdfgh".'), |
|
1790 |
('ertgfd', 'Avoid short keyboard patterns like "ertgfd".'), |
|
1791 |
('abab', 'Avoid repeated words and characters like "abab".'), |
|
1792 |
('abcd', 'Avoid sequences like "abcd".'), |
|
1793 |
('2019', 'Avoid recent years.'), |
|
1794 |
('02/08/14', 'Avoid dates and years that are associated with you.'), |
|
1795 |
('02/08/14', 'Avoid dates and years that are associated with you.'), |
|
1796 |
('p@ssword', 'Avoid "p@ssword" : it\'s similar to a commonly used password'), |
|
1797 |
('password', 'Avoid "password" : it\'s a commonly used password.'), |
|
1798 |
], |
|
1799 |
) |
|
1800 |
def test_validate_password_hints(app, settings, password, hint): |
|
1801 |
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 3 |
|
1802 |
settings.A2_PASSWORD_POLICY_MIN_LENGTH = 0 |
|
1803 |
settings.A2_PASSWORD_POLICY_MIN_CLASSES = 0 |
|
1804 |
response = app.post_json('/api/validate-password/', params={'password': password}) |
|
1777 | 1805 |
assert response.json['result'] == 1 |
1778 |
assert response.json['ok'] is True |
|
1779 |
assert response.json['hints'] == [] |
|
1806 |
assert response.json['hints'] == [hint] |
|
1780 | 1807 | |
1781 | 1808 | |
1782 | 1809 |
def test_api_users_get_or_create(settings, app, admin): |
1783 |
- |