Projet

Général

Profil

0003-misc-generate-hints-from-zxcvbn-report-63831.patch

Corentin Séchet, 02 septembre 2022 10:45

Télécharger (5,61 ko)

Voir les différences:

Subject: [PATCH 3/3] misc: generate hints from zxcvbn report (#63831)

 src/authentic2/passwords.py | 45 +++++++++++++++++++++++++++++++++-
 tests/api/test_all.py       | 49 ++++++++++++++++++++++++++++---------
 2 files changed, 82 insertions(+), 12 deletions(-)
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
-