Projet

Général

Profil

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

Corentin Séchet, 05 septembre 2022 16:45

Télécharger (5,62 ko)

Voir les différences:

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

 src/authentic2/passwords.py | 49 +++++++++++++++++++++++++++++++++----
 tests/api/test_all.py       | 46 ++++++++++++++++++++++++++++------
 2 files changed, 83 insertions(+), 12 deletions(-)
src/authentic2/passwords.py
122 122
        elif password:
123 123
            report = zxcvbn(password)
124 124
            strength = report['score']
125
            suggestions = report['feedback']['suggestions']
126
            if len(suggestions):
127
                hint = report['feedback']['suggestions'][0]
128
            else:
129
                hint = _('add more words or characters')
125
            hint = get_hint(report['sequence'])
130 126

  
131 127
        return self.StrengthReport(strength >= self.min_strength, strength, hint)
132 128

  
......
149 145
            yield self.Check(result=bool(re.match(self.regexp, password)), label=self.regexp_label)
150 146

  
151 147

  
148
def get_hint(matches):
149
    matches = sorted(matches, key=lambda m: len(m['token']), reverse=True)
150
    for match in matches:
151
        hint = get_hint_for_match(match)
152
        if hint:
153
            return hint
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

  
152 191
def get_password_checker(*args, **kwargs):
153 192
    return import_string(app_settings.A2_PASSWORD_POLICY_CLASS)(*args, **kwargs)
154 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['hint'] == '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['hint'] == 'Add another word or two. Uncommon words are better.'
1771
    response = app.post_json('/api/validate-password/', params={'password': 'xbA2E4]#o'})
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

  
1785

  
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})
1772 1805
    assert response.json['result'] == 1
1773
    assert response.json['ok'] is True
1774
    assert response.json['hint'] == 'add more words or characters'
1806
    assert response.json['hint'] == hint
1775 1807

  
1776 1808

  
1777 1809
def test_api_users_get_or_create(settings, app, admin):
1778
-