diff --git a/dav_auth/templates/dav_auth/forms/set_password.html b/dav_auth/templates/dav_auth/forms/set_password.html index 5abe91f..7fc15fc 100644 --- a/dav_auth/templates/dav_auth/forms/set_password.html +++ b/dav_auth/templates/dav_auth/forms/set_password.html @@ -32,4 +32,38 @@   +
+
+   +
+
+

{% trans 'Passwortrichtlinien' %}

+
+

+ 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. +

+

+ Je länger das Passwort ist und je mehr ungewöhnliche Zeichen darin enthalten sind, + umso unwahrscheinlicher ist es, dass das Passwort erraten werden kann. +

+

+ 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.
+ Ausserdem dürfen bestimmte Wörter nicht enthalten sein (z.B. Karlsruhe). +

+

+ Mehr Zeichen geben mehr Punkte. Und Großbuchstaben, Ziffern und Sonderzeichen geben Extrapunkte. +

+

+ Im Regelfall sollte euer Passwort aus mindestens 12 Zeichen bestehen, + darunter mindestens je zwei Großbuchstaben, Ziffern und Sonderzeichen.
+ Nehmt da ruhig alles, was eure Tastatur so hergibt. +

+
+
+
+   +
+
{% endblock page-container %} diff --git a/dav_auth/tests/test_emails.py b/dav_auth/tests/test_emails.py index b962ec1..5696276 100644 --- a/dav_auth/tests/test_emails.py +++ b/dav_auth/tests/test_emails.py @@ -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}, diff --git a/dav_auth/tests/test_forms.py b/dav_auth/tests/test_forms.py index 5388d1e..d02c9ba 100644 --- a/dav_auth/tests/test_forms.py +++ b/dav_auth/tests/test_forms.py @@ -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}) diff --git a/dav_auth/tests/test_screenshots.py b/dav_auth/tests/test_screenshots.py index 70e38fd..7ab5aee 100644 --- a/dav_auth/tests/test_screenshots.py +++ b/dav_auth/tests/test_screenshots.py @@ -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 diff --git a/dav_auth/tests/test_templates.py b/dav_auth/tests/test_templates.py index c140bfd..ebd1c66 100644 --- a/dav_auth/tests/test_templates.py +++ b/dav_auth/tests/test_templates.py @@ -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 diff --git a/dav_auth/tests/test_validators.py b/dav_auth/tests/test_validators.py index faff32e..62918fc 100644 --- a/dav_auth/tests/test_validators.py +++ b/dav_auth/tests/test_validators.py @@ -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.', ]), ] diff --git a/dav_auth/tests/test_views.py b/dav_auth/tests/test_views.py index 404650f..28a1ee0 100644 --- a/dav_auth/tests/test_views.py +++ b/dav_auth/tests/test_views.py @@ -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 diff --git a/dav_auth/validators.py b/dav_auth/validators.py index f1c4ba6..481eea4 100644 --- a/dav_auth/validators.py +++ b/dav_auth/validators.py @@ -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) diff --git a/dav_auth/views.py b/dav_auth/views.py index 995c8f5..fcf30ec 100644 --- a/dav_auth/views.py +++ b/dav_auth/views.py @@ -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'
\n

\n' + message += u'Dein Passwort entspricht nicht mehr den aktuellen Passwortrichtlinien.
\n' + message += u'Bitte hilf uns die Daten deiner Teilnehmer zu schützen und ändere dein Passwort.
\n' + message += u'

\n' + message += u'

\n' + message += u'Passwort ändern\n' % {'href': reverse('dav_auth:set_password')} + message += u'

\n
\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) diff --git a/dav_base/console_scripts/django_project_config/additional_settings.py b/dav_base/console_scripts/django_project_config/additional_settings.py index e85977f..470bb04 100644 --- a/dav_base/console_scripts/django_project_config/additional_settings.py +++ b/dav_base/console_scripts/django_project_config/additional_settings.py @@ -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' diff --git a/dav_events/tests/test_screenshots.py b/dav_events/tests/test_screenshots.py index f0246ab..a211883 100644 --- a/dav_events/tests/test_screenshots.py +++ b/dav_events/tests/test_screenshots.py @@ -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',