UPD: enable stronger password validation and eventually warning message on login
All checks were successful
buildbot/tox Build done.
All checks were successful
buildbot/tox Build done.
This commit is contained in:
@@ -32,4 +32,38 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<h3 class="top-most">{% trans 'Passwortrichtlinien' %}</h3>
|
||||
<div class="well">
|
||||
<p>
|
||||
Damit die persönlichen Daten unserer Teilnehmer jederzeit geschützt sind, ist es unabdingbar,
|
||||
dass eure Passwörter für das Touren- & Kurse-Portal nicht einfach zu erraten sind.
|
||||
</p>
|
||||
<p>
|
||||
Je länger das Passwort ist und je mehr <i>ungewöhnliche</i> Zeichen darin enthalten sind,
|
||||
umso unwahrscheinlicher ist es, dass das Passwort erraten werden kann.
|
||||
</p>
|
||||
<p>
|
||||
Um sicher zu stellen, dass eure Passwörter ausreichend lang sind und nicht nur
|
||||
aus einfachen Wörtern bestehen, wird die Güte eures Passwortes mit Punkten bewertet.<br />
|
||||
Ausserdem dürfen bestimmte Wörter nicht enthalten sein (z.B. Karlsruhe).
|
||||
</p>
|
||||
<p>
|
||||
Mehr Zeichen geben mehr Punkte. Und Großbuchstaben, Ziffern und Sonderzeichen geben Extrapunkte.
|
||||
</p>
|
||||
<p>
|
||||
Im Regelfall sollte euer Passwort aus mindestens 12 Zeichen bestehen,
|
||||
darunter mindestens je zwei Großbuchstaben, Ziffern und Sonderzeichen.<br />
|
||||
Nehmt da ruhig alles, was eure Tastatur so hergibt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock page-container %}
|
||||
|
||||
@@ -9,7 +9,7 @@ from ..emails import PasswordSetEmail
|
||||
|
||||
|
||||
TEST_USERNAME = 'user'
|
||||
TEST_PASSWORD = u'me||ön 2'
|
||||
TEST_PASSWORD = u'me||ön 21ABll'
|
||||
TEST_EMAIL = 'root@localhost'
|
||||
|
||||
PASSWORD_EMAIL_TEMPLATE = u"""Hallo {fullname},
|
||||
|
||||
@@ -9,7 +9,7 @@ from dav_base.tests.generic import FormDataSet, FormsTestCase
|
||||
from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm
|
||||
|
||||
TEST_USERNAME = 'root@localhost'
|
||||
TEST_PASSWORD = u'me||ön 2'
|
||||
TEST_PASSWORD = u'me||ön 21ABll'
|
||||
TEST_EMAIL = TEST_USERNAME
|
||||
USERNAME_MAX_LENGTH = 254
|
||||
|
||||
@@ -108,7 +108,7 @@ class SetPasswordFormTestCase(FormsTestCase):
|
||||
|
||||
def test_mismatch(self):
|
||||
data_sets = [
|
||||
FormDataSet({'new_password': 'mellon12', 'new_password_repeat': 'mellon13'},
|
||||
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB13+-'},
|
||||
[('new_password_repeat', 'password_mismatch')]),
|
||||
]
|
||||
super(SetPasswordFormTestCase, self).test_invalid_data(data_sets=data_sets, form_kwargs={'user': self.user})
|
||||
@@ -150,9 +150,10 @@ class SetPasswordFormTestCase(FormsTestCase):
|
||||
|
||||
def test_valid(self):
|
||||
data_sets = [
|
||||
FormDataSet({'new_password': 'mellon12', 'new_password_repeat': 'mellon12'}),
|
||||
FormDataSet({'new_password': 'mellon12', 'new_password_repeat': 'mellon12', 'send_password_mail': True}),
|
||||
FormDataSet({'new_password': u'"ä§ Mellon12', 'new_password_repeat': u'"ä§ Mellon12'}),
|
||||
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-'}),
|
||||
FormDataSet({'new_password': 'mellonAB12+-', 'new_password_repeat': 'mellonAB12+-',
|
||||
'send_password_mail': True}),
|
||||
FormDataSet({'new_password': u'"ä§ MellonAB12+-', 'new_password_repeat': u'"ä§ MellonAB12+-'}),
|
||||
FormDataSet({'new_password': 'mellon12' * 128, 'new_password_repeat': 'mellon12' * 128}),
|
||||
]
|
||||
super(SetPasswordFormTestCase, self).test_valid_data(data_sets=data_sets, form_kwargs={'user': self.user})
|
||||
|
||||
@@ -9,7 +9,7 @@ from selenium.webdriver.common.keys import Keys
|
||||
from dav_base.tests.generic import ScreenshotTestCase
|
||||
|
||||
TEST_USERNAME = 'root@localhost'
|
||||
TEST_PASSWORD = u'me||ön 2'
|
||||
TEST_PASSWORD = u'me||ön 21ABll'
|
||||
TEST_EMAIL = TEST_USERNAME
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from dav_base.tests.generic import SeleniumTestCase
|
||||
from .generic import SeleniumAuthMixin
|
||||
|
||||
TEST_USERNAME = 'root@localhost'
|
||||
TEST_PASSWORD = 'me||ön 2'
|
||||
TEST_PASSWORD = 'me||ön 21ABll'
|
||||
TEST_EMAIL = TEST_USERNAME
|
||||
|
||||
|
||||
|
||||
@@ -83,22 +83,22 @@ class CustomWordlistPasswordValidatorTestCase(SimpleTestCase):
|
||||
def test_invalid(self):
|
||||
invalid_passwords = [
|
||||
(u'passwort', [
|
||||
u'The password must not contain the word \'passwort\'',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'passwort\' enthalten.',
|
||||
]),
|
||||
(u'abcdDaVefgh', [
|
||||
u'The password must not contain the word \'dav\'',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'dav\' enthalten.',
|
||||
]),
|
||||
(u'abcdsektIonefgh', [
|
||||
u'The password must not contain the word \'sektion\'',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'sektion\' enthalten.',
|
||||
]),
|
||||
(u'alpen12verein34KArlsruhE berge', [
|
||||
u'The password must not contain the word \'karlsruhe\'',
|
||||
u'The password must not contain the word \'berge\'',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'berge\' enthalten.',
|
||||
]),
|
||||
(u'heinzel@alpenverein-karlsruhe.de', [
|
||||
u'The password must not contain the word \'heinzel\'',
|
||||
u'The password must not contain the word \'alpenverein\'',
|
||||
u'The password must not contain the word \'karlsruhe\'',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'heinzel\' enthalten.',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'alpenverein\' enthalten.',
|
||||
u'Das Passwort darf nicht die Zeichenfolge \'karlsruhe\' enthalten.',
|
||||
]),
|
||||
]
|
||||
|
||||
@@ -140,66 +140,66 @@ class CharacterClassPasswordValidatorTestCase(SimpleTestCase):
|
||||
def test_invalid(self):
|
||||
invalid_passwords = [
|
||||
(u'', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'A+-', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
]),
|
||||
(u'1234567890*', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'34*/()', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
]),
|
||||
(u'AA', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'CD0.,', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
]),
|
||||
(u'EF56', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'8GH?!8', [
|
||||
u'The password must contain at least 2 characters from a-z',
|
||||
u'Das Passwort muss mindestens 2 Kleinbuchstaben enthalten.',
|
||||
]),
|
||||
(u'bbX', [
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'$cd%', [
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
]),
|
||||
(u'ef90', [
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'1g=h3~', [
|
||||
u'The password must contain at least 2 characters from A-Z',
|
||||
u'Das Passwort muss mindestens 2 Großbuchstaben enthalten.',
|
||||
]),
|
||||
(u'Gi&jH', [
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
(u'IkK:i;', [
|
||||
u'The password must contain at least 2 digits from 0-9',
|
||||
u'Das Passwort muss mindestens 2 Ziffern enthalten.',
|
||||
]),
|
||||
(u'mKn4L8', [
|
||||
u'The password must contain at least 2 non alpha numeric characters',
|
||||
u'Das Passwort muss mindestens 2 Sonderzeichen enthalten.',
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.urls import reverse
|
||||
from ..forms import LoginForm, SetPasswordForm, CreateAndSendPasswordForm
|
||||
|
||||
TEST_USERNAME = 'root@localhost'
|
||||
TEST_PASSWORD = u'me||ön 2'
|
||||
TEST_PASSWORD = u'me||ön 21ABll'
|
||||
TEST_EMAIL = TEST_USERNAME
|
||||
|
||||
|
||||
|
||||
@@ -56,15 +56,15 @@ class PasswordScoreValidator(object):
|
||||
score, used_classes = self._get_score(password, user=user)
|
||||
|
||||
if used_classes < self.min_classes:
|
||||
raise ValidationError(_(u'The password must contain characters from at least %(min_classes)d'
|
||||
u' different character classes (i.e. lower, upper, digits, others).'),
|
||||
raise ValidationError(_(u'Das Passwort muss Zeichen aus mindestens %(min_classes)d'
|
||||
u' verschiedenen Arten von Zeichen bestehen'
|
||||
u' (d.h. Kleinbuchstaben, Großbuchstaben, Ziffern und Sonderzeichen).'),
|
||||
code='too_few_classes',
|
||||
params={'min_classes': self.min_classes})
|
||||
|
||||
if score < self.min_score:
|
||||
raise ValidationError(_(u'The password is too simple. Use more characters'
|
||||
u' and maybe use more different character classes'
|
||||
u' (i.e. lower, upper, digits, others).'),
|
||||
raise ValidationError(_(u'Dieses Passwort ist zu einfach. Benutze mehr Zeichen'
|
||||
u' und gegebenenfalls auch Großbuchstaben, Ziffern und Sonderzeichen.'),
|
||||
code='too_little_score')
|
||||
|
||||
return score
|
||||
@@ -116,7 +116,7 @@ class CustomWordlistPasswordValidator(object):
|
||||
lower_pw = password.lower()
|
||||
for word in self.words:
|
||||
if word in lower_pw:
|
||||
error = ValidationError(_(u'The password must not contain the word \'%(word)s\''),
|
||||
error = ValidationError(_(u'Das Passwort darf nicht die Zeichenfolge \'%(word)s\' enthalten.'),
|
||||
code='forbidden_word',
|
||||
params={'word': word})
|
||||
errors.append(error)
|
||||
@@ -166,23 +166,23 @@ class CharacterClassPasswordValidator(object):
|
||||
def validate(self, password, user=None):
|
||||
errors = []
|
||||
if not self._is_enough_lower(password):
|
||||
error = ValidationError(_(u'The password must contain at least %(min_lower)d characters from a-z'),
|
||||
error = ValidationError(_(u'Das Passwort muss mindestens %(min_lower)d Kleinbuchstaben enthalten.'),
|
||||
code='too_few_lower_characters',
|
||||
params={'min_lower': self.minimum_lower})
|
||||
errors.append(error)
|
||||
if not self._is_enough_upper(password):
|
||||
error = ValidationError(_(u'The password must contain at least %(min_upper)d characters from A-Z'),
|
||||
error = ValidationError(_(u'Das Passwort muss mindestens %(min_upper)d Großbuchstaben enthalten.'),
|
||||
code='too_few_upper_characters',
|
||||
params={'min_upper': self.minimum_upper})
|
||||
errors.append(error)
|
||||
if not self._is_enough_digits(password):
|
||||
error = ValidationError(_(u'The password must contain at least %(min_digits)d digits from 0-9'),
|
||||
error = ValidationError(_(u'Das Passwort muss mindestens %(min_digits)d Ziffern enthalten.'),
|
||||
code='too_few_digits',
|
||||
params={'min_digits': self.minimum_digits})
|
||||
errors.append(error)
|
||||
if not self._is_enough_others(password):
|
||||
error = ValidationError(_(u'The password must contain at least %(min_others)d'
|
||||
u' non alpha numeric characters'),
|
||||
error = ValidationError(_(u'Das Passwort muss mindestens %(min_others)d'
|
||||
u' Sonderzeichen enthalten.'),
|
||||
code='too_few_other_characters',
|
||||
params={'min_others': self.minimum_others})
|
||||
errors.append(error)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -7,6 +8,7 @@ from django.contrib.auth.password_validation import validate_password
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import resolve_url
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views import generic
|
||||
|
||||
@@ -29,11 +31,19 @@ class LoginView(auth_views.LoginView):
|
||||
|
||||
def form_valid(self, form):
|
||||
r = super(LoginView, self).form_valid(form)
|
||||
messages.success(self.request, _(u'Benutzer angemeldet: %(username)s') % {'username': form.get_user()})
|
||||
try:
|
||||
validate_password(form.cleaned_data['password'])
|
||||
except ValidationError as e:
|
||||
logger.warning(u'Weak password (%d): %s', self.request.user.pk, e)
|
||||
messages.success(self.request, _(u'Benutzer angemeldet: %(username)s') % {'username': form.get_user()})
|
||||
message = u'<br />\n<p>\n'
|
||||
message += u'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.<br />\n'
|
||||
message += u'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.<br />\n'
|
||||
message += u'</p>\n'
|
||||
message += u'<p>\n'
|
||||
message += u'<a href="%(href)s">Passwort ändern</a>\n' % {'href': reverse('dav_auth:set_password')}
|
||||
message += u'</p>\n<br />\n'
|
||||
messages.warning(self.request, mark_safe(message))
|
||||
return r
|
||||
|
||||
|
||||
@@ -76,7 +86,7 @@ class CreateAndSendPasswordView(generic.FormView):
|
||||
user_model = get_user_model()
|
||||
try:
|
||||
user = user_model.objects.get(username=username)
|
||||
random_password = user_model.objects.make_random_password(length=12)
|
||||
random_password = user_model.objects.make_random_password(length=32)
|
||||
user.set_password(random_password)
|
||||
user.save()
|
||||
email = emails.PasswordSetEmail(user, random_password)
|
||||
|
||||
@@ -52,6 +52,30 @@ DATABASES['default'] = {
|
||||
'NAME': os.path.join(BASE_VAR_DIR, 'db', 'devel.sqlite3'),
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
'OPTIONS': {
|
||||
'min_length': 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
'NAME': 'dav_auth.validators.PasswordScoreValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'dav_auth.validators.CustomWordlistPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
STATIC_ROOT = os.path.join(BASE_VAR_DIR, 'www', 'static')
|
||||
|
||||
LANGUAGE_CODE = 'de'
|
||||
|
||||
@@ -14,7 +14,7 @@ from dav_auth.tests.generic import SeleniumAuthMixin
|
||||
from .generic import RoleMixin
|
||||
|
||||
TEST_TRAINER_EMAIL = 'trainer@localhost'
|
||||
TEST_PASSWORD = u'me||ön 2'
|
||||
TEST_PASSWORD = u'me||ön 21ABll'
|
||||
TEST_EVENT_DATA_S = {
|
||||
'mode': 'training',
|
||||
'sport': 'S',
|
||||
|
||||
Reference in New Issue
Block a user