misc: validate password strength (#63830)

This commit is contained in:
Corentin Sechet 2022-05-18 11:29:27 +02:00
parent 1a9537f2e3
commit 8e15762cd5
4 changed files with 34 additions and 0 deletions

View File

@ -182,6 +182,7 @@ setup(
'chardet',
'attrs>17',
'atomicwrites',
'zxcvbn',
],
zip_safe=False,
classifiers=[

View File

@ -202,6 +202,9 @@ default_settings = dict(
A2_PASSWORD_POLICY_SHOW_LAST_CHAR=Setting(
default=False, definition='Show last character in password fields'
),
A2_PASSWORD_POLICY_MIN_STRENGTH=Setting(
default=None, definition='Minimun password strength on a 0-4 scale'
),
A2_SUGGESTED_EMAIL_DOMAINS=Setting(
default=[
'gmail.com',

View File

@ -23,6 +23,7 @@ from django.core.exceptions import ValidationError
from django.utils.functional import lazy
from django.utils.module_loading import import_string
from django.utils.translation import ugettext as _
from zxcvbn import zxcvbn
from . import app_settings
@ -90,7 +91,12 @@ class DefaultPasswordChecker(PasswordChecker):
def regexp_label(self):
return app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG
@property
def min_strength(self):
return app_settings.A2_PASSWORD_POLICY_MIN_STRENGTH
def __call__(self, password, **kwargs):
if self.min_length:
yield self.Check(
result=len(password) >= self.min_length, label=_('%s characters') % self.min_length
@ -108,6 +114,13 @@ class DefaultPasswordChecker(PasswordChecker):
if self.regexp and self.regexp_label:
yield self.Check(result=bool(re.match(self.regexp, password)), label=self.regexp_label)
if self.min_strength:
score = 0
if password:
score = zxcvbn(password)['score']
yield self.Check(result=score > self.min_strength, label=_('Secure password'))
def get_password_checker(*args, **kwargs):
return import_string(app_settings.A2_PASSWORD_POLICY_CLASS)(*args, **kwargs)

View File

@ -1756,6 +1756,23 @@ def test_validate_password_regex(app, settings):
assert response.json['checks'][4]['result'] is True
def test_validate_password_strength(app, settings):
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 2
response = app.post_json('/api/validate-password/', params={'password': 'w34k P455w0rd'})
assert response.json['result'] == 1
assert response.json['ok'] is False
assert len(response.json['checks']) == 5
assert response.json['checks'][4]['label'] == 'Secure password'
assert response.json['checks'][4]['result'] is False
response = app.post_json('/api/validate-password/', params={'password': 'xbA2E4]#o'})
assert response.json['result'] == 1
assert response.json['ok'] is True
assert len(response.json['checks']) == 5
assert response.json['checks'][4]['label'] == 'Secure password'
assert response.json['checks'][4]['result'] is True
def test_api_users_get_or_create(settings, app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
# test missing first_name