Projet

Général

Profil

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

Corentin Séchet, 07 septembre 2022 13:34

Télécharger (5,64 ko)

Voir les différences:

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

 src/authentic2/passwords.py | 51 +++++++++++++++++++++++++++++++++----
 tests/api/test_all.py       | 50 ++++++++++++++++++++++--------------
 2 files changed, 77 insertions(+), 24 deletions(-)
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
-