From 98f2b77a2dbc9aa2bb1104eb8b80b16643ca2977 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 24 Mar 2015 16:47:05 +0100 Subject: [PATCH 1/2] report all password requirements at once on password input (fixes #6805) --- src/authentic2/registration_backend/forms.py | 9 ++++--- src/authentic2/validators.py | 36 +++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/authentic2/registration_backend/forms.py b/src/authentic2/registration_backend/forms.py index f2e5789..0ccca1a 100644 --- a/src/authentic2/registration_backend/forms.py +++ b/src/authentic2/registration_backend/forms.py @@ -67,17 +67,18 @@ class RegistrationForm(Form): send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [data['email']], fail_silently=True) class RegistrationCompletionForm(forms.UserAttributeFormMixin, Form): error_css_class = 'form-field-error' required_css_class = 'form-field-required' password1 = CharField(widget=PasswordInput, label=_("Password"), - validators=[validators.validate_password]) + validators=[validators.validate_password], + help_text=validators.password_help_text()) password2 = CharField(widget=PasswordInput, label=_("Password (again)")) def __init__(self, *args, **kwargs): """ Inject required fields in registration form """ super(RegistrationCompletionForm, self).__init__(*args, **kwargs) User = compat.get_user_model() @@ -192,18 +193,20 @@ class PasswordResetMixin(Form): return ret self.user.save = save return ret class SetPasswordForm(PasswordResetMixin, auth_forms.SetPasswordForm): new_password1 = CharField(label=_("New password"), widget=PasswordInput, - validators=[validators.validate_password]) + validators=[validators.validate_password], + help_text=validators.password_help_text()) class PasswordChangeForm(forms.NextUrlFormMixin, PasswordResetMixin, auth_forms.PasswordChangeForm): new_password1 = CharField(label=_("New password"), widget=PasswordInput, - validators=[validators.validate_password]) + validators=[validators.validate_password], + help_text=validators.password_help_text()) diff --git a/src/authentic2/validators.py b/src/authentic2/validators.py index 63257fa..3323da8 100644 --- a/src/authentic2/validators.py +++ b/src/authentic2/validators.py @@ -1,17 +1,19 @@ from __future__ import unicode_literals import string import re +import six import smtplib -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.encoding import force_text from django.core.exceptions import ValidationError +from django.utils.functional import lazy import socket import dns.resolver import dns.exception from . import app_settings # copied from http://www.djangotips.com/real-email-validation @@ -78,30 +80,52 @@ class EmailValidator(object): email_validator = EmailValidator() def validate_password(password): password_set = set(password) digits = set(string.digits) lower = set(string.lowercase) upper = set(string.uppercase) punc = set(string.punctuation) + errors = [] if not password: return min_len = app_settings.A2_PASSWORD_POLICY_MIN_LENGTH if len(password) < min_len: - raise ValidationError(_('password must contain at least %d ' - 'characters') % min_len) + errors.append(ValidationError(_('password must contain at least %d ' + 'characters') % min_len)) class_count = 0 for cls in (digits, lower, upper, punc): if not password_set.isdisjoint(cls): class_count += 1 min_class_count = app_settings.A2_PASSWORD_POLICY_MIN_CLASSES if class_count < min_class_count: - raise ValidationError(_('password must contain characters ' + errors.append(ValidationError(_('password must contain characters ' 'from at least %d classes among: lowercase letters, ' - 'uppercase letters, digits, and punctuations') % min_class_count) + 'uppercase letters, digits, and punctuations') % min_class_count)) if app_settings.A2_PASSWORD_POLICY_REGEX: if not re.match(app_settings.A2_PASSWORD_POLICY_REGEX, password): msg = app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG msg = msg or _('your password dit not match the regular expession %s') % app_settings.A2_PASSWORD_POLICY_REGEX - raise ValidationError(msg) + errors.append(ValidationError(msg)) + if errors: + raise ValidationError(errors) + +def __password_help_text_helper(): + if app_settings.A2_PASSWORD_POLICY_MIN_LENGTH: + yield ugettext('Your password must contain at least %(min_length)d characters.') % {'min_length': app_settings.A2_PASSWORD_POLICY_MIN_LENGTH} + if app_settings.A2_PASSWORD_POLICY_MIN_CLASSES: + yield ugettext('Your password must contain characters from at least %(min_classes)d ' + 'classes among: lowercase letters, uppercase letters, digits, ' + 'and punctuations') % {'min_classes': app_settings.A2_PASSWORD_POLICY_MIN_CLASSES} + if app_settings.A2_PASSWORD_POLICY_REGEX: + yield ugettext(app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG) or \ + ugettext('Your password must match the regular expression: ' + '%(regexp)s, please change this message using the ' + 'A2_PASSWORD_POLICY_REGEX_ERROR_MSG setting.') % \ + {'regexp': app_settings.A2_PASSWORD_POLICY_REGEX} + +def password_help_text(): + return ' '.join(__password_help_text_helper()) + +password_help_text = lazy(password_help_text, six.text_type) -- 1.9.1