diff --git a/dav_auth/tests/test_validators.py b/dav_auth/tests/test_validators.py index 31af7a2..faff32e 100644 --- a/dav_auth/tests/test_validators.py +++ b/dav_auth/tests/test_validators.py @@ -2,25 +2,25 @@ from django.core.exceptions import ValidationError from django.test import SimpleTestCase -from ..validators import PasswordScoreValidator, CharacterClassPasswordValidator +from ..validators import PasswordScoreValidator, CustomWordlistPasswordValidator, CharacterClassPasswordValidator class PasswordScoreValidatorTestCase(SimpleTestCase): def test_too_little_score(self): passwords = [ - '', # score = 0 - 'abcdefghijklmnopq', # score = 17 - 'Abcdefghijklmnop', # score = 16 + 1 - 'ABcdefghijklmno', # score = 15 + 2 - 'AB1defghijklmn', # score = 14 + 2 + 1 - 'AB12efghijklm', # score = 13 + 2 + 2 - 'AB12+fghijkl', # score = 12 + 2 + 2 + 1 - 'AB12+-ghijk', # score = 11 + 2 + 2 + 2 - 'AB12+-*hij', # score = 10 + 2 + 2 + 3 - 'AB12+-*hi/', # score = 10 + 2 + 2 + 3 - 'AB12+-*hi3', # score = 10 + 2 + 2 + 3 - 'AB12+-*hiC', # score = 10 + 2 + 2 + 3 - 'abcbbbgbbjklmnopqr', # score = 17 + u'', # score = 0 + u'abcdefghijklmnopq', # score = 17 + u'Abcdefghijklmnop', # score = 16 + 1 + u'ABcdefghijklmno', # score = 15 + 2 + u'AB1defghijklmn', # score = 14 + 2 + 1 + u'AB12efghijklm', # score = 13 + 2 + 2 + u'AB12+fghijkl', # score = 12 + 2 + 2 + 1 + u'AB12+-ghijk', # score = 11 + 2 + 2 + 2 + u'AB12+-*hij', # score = 10 + 2 + 2 + 3 + u'AB12+-*hi/', # score = 10 + 2 + 2 + 3 + u'AB12+-*hi3', # score = 10 + 2 + 2 + 3 + u'AB12+-*hiC', # score = 10 + 2 + 2 + 3 + u'abcbbbgbbjklmnopqr', # score = 17 ] validator = PasswordScoreValidator(min_classes=0) @@ -31,19 +31,19 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): except ValidationError as e: self.assertEqual('too_little_score', e.code) else: - self.fail('%s: no validation error was raised' % password) + self.fail(u'%s: no validation error was raised' % password) def test_too_few_classes(self): passwords = [ - '', # classes = 0 - 'abcdefgh', # classes = 1 - 'abcdef+-', # classes = 2 - 'abcd12gh', # classes = 2 - 'abcd12-+', # classes = 3 - 'abCDefgh', # classes = 2 - 'abCDef+-', # classes = 3 - 'abCD12gh', # classes = 3 - 'ABCD12-+', # classes = 3 + u'', # classes = 0 + u'abcdefgh', # classes = 1 + u'abcdef+-', # classes = 2 + u'abcd12gh', # classes = 2 + u'abcd12-+', # classes = 3 + u'abCDefgh', # classes = 2 + u'abCDef+-', # classes = 3 + u'abCD12gh', # classes = 3 + u'ABCD12-+', # classes = 3 ] validator = PasswordScoreValidator(min_score=0, min_classes=4) @@ -54,20 +54,20 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): except ValidationError as e: self.assertEqual('too_few_classes', e.code) else: - self.fail('%s: no validation error was raised' % password) + self.fail(u'%s: no validation error was raised' % password) def test_valid(self): passwords = [ - 'Abcdefghijklmnopq', - 'ABcdefghijklmnop', - 'AB1defghijklmno', - 'AB12efghijklmn', - 'AB12+fghijklm', - 'AB12+-ghijkl', - 'AB12+-*hijk', - 'ab1defghijklmnopq', - 'abcd+fghijklmnopq', - 'AB1CDEFGHIJKLMNOPQ', + u'Abcdefghijklmnopq', + u'ABcdefghijklmnop', + u'AB1defghijklmno', + u'AB12efghijklmn', + u'AB12+fghijklm', + u'AB12+-ghijkl', + u'AB12+-*hijk', + u'ab1defghijklmnopq', + u'abcd+fghijklmnopq', + u'AB1CDEFGHIJKLMNOPQ', ] validator = PasswordScoreValidator() @@ -79,6 +79,59 @@ class PasswordScoreValidatorTestCase(SimpleTestCase): self.fail(e) +class CustomWordlistPasswordValidatorTestCase(SimpleTestCase): + def test_invalid(self): + invalid_passwords = [ + (u'passwort', [ + u'The password must not contain the word \'passwort\'', + ]), + (u'abcdDaVefgh', [ + u'The password must not contain the word \'dav\'', + ]), + (u'abcdsektIonefgh', [ + u'The password must not contain the word \'sektion\'', + ]), + (u'alpen12verein34KArlsruhE berge', [ + u'The password must not contain the word \'karlsruhe\'', + u'The password must not contain the word \'berge\'', + ]), + (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\'', + ]), + ] + + validator = CustomWordlistPasswordValidator() + + for password, expected_errors in invalid_passwords: + try: + validator.validate(password) + except ValidationError as e: + errors = e.messages + for expected_error in expected_errors: + self.assertIn(expected_error, errors) + for error in errors: + self.assertIn(error, expected_errors) + else: + self.fail(u'%s: no validation error was raised' % password) + + def test_valid(self): + passwords = [ + u'', + u'password', + u'münchen', + ] + + validator = CustomWordlistPasswordValidator() + + for password in passwords: + try: + validator.validate(password) + except ValidationError as e: + self.fail(e) + + class CharacterClassPasswordValidatorTestCase(SimpleTestCase): def setUp(self): super(CharacterClassPasswordValidatorTestCase, self).setUp() @@ -86,72 +139,75 @@ 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', ]), - ('A+-', [ + (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', ]), - ('1234567890*', [ + (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', ]), - ('34*/()', [ + (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', ]), - ('AA', [ + (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', ]), - ('CD0.,', [ + (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', ]), - ('EF56', [ + (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', ]), - ('8GH?!8', [ + (u'8GH?!8', [ u'The password must contain at least 2 characters from a-z', ]), - ('bbX', [ + (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', ]), - ('$cd%', [ + (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', ]), - ('ef90', [ + (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', ]), - ('1g=h3~', [ + (u'1g=h3~', [ u'The password must contain at least 2 characters from A-Z', ]), - ('Gi&jH', [ + (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', ]), - ('IkK:i;', [ + (u'IkK:i;', [ u'The password must contain at least 2 digits from 0-9', ]), - ('mKn4L8', [ + (u'mKn4L8', [ u'The password must contain at least 2 non alpha numeric characters', ]), ] + + validator = self.validator + for password, expected_errors in invalid_passwords: try: - self.validator.validate(password) + validator.validate(password) except ValidationError as e: errors = e.messages for expected_error in expected_errors: @@ -159,12 +215,13 @@ class CharacterClassPasswordValidatorTestCase(SimpleTestCase): for error in errors: self.assertIn(error, expected_errors) else: - self.fail('%s: no validation error was raised' % password) + self.fail(u'%s: no validation error was raised' % password) def test_valid(self): - valid_passwords = ['abCD12+-'] + valid_passwords = [u'abCD12+-'] + validator = self.validator for password in valid_passwords: try: - self.validator.validate(password) + validator.validate(password) except ValidationError as e: self.fail(e) diff --git a/dav_auth/validators.py b/dav_auth/validators.py index ed796b9..f1c4ba6 100644 --- a/dav_auth/validators.py +++ b/dav_auth/validators.py @@ -4,69 +4,6 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ -class CharacterClassPasswordValidator(object): - def _is_enough_lower(self, password): - lower = re.sub(r'[^a-z]', '', password) - if len(lower) < self.minimum_lower: - return False - return True - - def _is_enough_upper(self, password): - upper = re.sub(r'[^A-Z]', '', password) - if len(upper) < self.minimum_upper: - return False - return True - - def _is_enough_digits(self, password): - digits = re.sub(r'[^0-9]', '', password) - if len(digits) < self.minimum_digits: - return False - return True - - def _is_enough_others(self, password): - others = re.sub(r'[a-zA-Z0-9]', '', password) - if len(others) < self.minimum_others: - return False - return True - - def __init__(self, minimum_lower=2, minimum_upper=2, minimum_digits=2, minimum_others=2): - self.minimum_lower = minimum_lower - self.minimum_upper = minimum_upper - self.minimum_digits = minimum_digits - self.minimum_others = minimum_others - - def validate(self, password, user=None): - errors = [] - if not self._is_enough_lower(password): - errors.append(ValidationError(_(u'The password must contain at least %(min_lower)d characters from a-z'), - code='too_few_lower_characters', - params={'min_lower': self.minimum_lower})) - if not self._is_enough_upper(password): - errors.append(ValidationError(_(u'The password must contain at least %(min_upper)d characters from A-Z'), - code='too_few_upper_characters', - params={'min_upper': self.minimum_upper})) - if not self._is_enough_digits(password): - errors.append(ValidationError(_(u'The password must contain at least %(min_digits)d digits from 0-9'), - code='too_few_digits', - params={'min_digits': self.minimum_digits})) - if not self._is_enough_others(password): - errors.append(ValidationError(_(u'The password must contain at least %(min_others)d' - u' non alpha numeric characters'), - code='too_few_other_characters', - params={'min_others': self.minimum_others})) - if errors: - raise ValidationError(errors) - - def get_help_text(self): - text = u'%s %s %s %s' % ( - _('The password must contain at least %d characters from a-z.') % self.minimum_lower, - _('The password must contain at least %d characters from A-Z.') % self.minimum_upper, - _('The password must contain at least %d digits from 0-9.') % self.minimum_digits, - _('The password must contain at least %d non alpha numeric characters.') % self.minimum_others, - ) - return text - - class PasswordScoreValidator(object): def _get_score(self, password, user=None): score = 0 @@ -159,3 +96,105 @@ class PasswordScoreValidator(object): text += _(u'Also the password must contain characters from %d different character classes' u' (i.e. lower, upper, digits, others).') % self.min_classes return text + + +class CustomWordlistPasswordValidator(object): + context = 'the Sektion Karlsruhe' + words = ( + u'dav', + u'berge', + u'sektion', + u'karlsruhe', + u'alpenverein', + u'heinzel', + u'passwort', + ) + + def validate(self, password, user=None): + errors = [] + + 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\''), + code='forbidden_word', + params={'word': word}) + errors.append(error) + + if errors: + raise ValidationError(errors) + + def get_help_text(self): + text = _(u'The password must not contain some specific words,' + u' that are common in context with %(context)s.') % {'context': self.context} + text += u'\n' + text += _(u'All words are matched case insensitive.') + return text + + +class CharacterClassPasswordValidator(object): + def _is_enough_lower(self, password): + lower = re.sub(r'[^a-z]', '', password) + if len(lower) < self.minimum_lower: + return False + return True + + def _is_enough_upper(self, password): + upper = re.sub(r'[^A-Z]', '', password) + if len(upper) < self.minimum_upper: + return False + return True + + def _is_enough_digits(self, password): + digits = re.sub(r'[^0-9]', '', password) + if len(digits) < self.minimum_digits: + return False + return True + + def _is_enough_others(self, password): + others = re.sub(r'[a-zA-Z0-9]', '', password) + if len(others) < self.minimum_others: + return False + return True + + def __init__(self, minimum_lower=2, minimum_upper=2, minimum_digits=2, minimum_others=2): + self.minimum_lower = minimum_lower + self.minimum_upper = minimum_upper + self.minimum_digits = minimum_digits + self.minimum_others = minimum_others + + 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'), + 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'), + 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'), + 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'), + code='too_few_other_characters', + params={'min_others': self.minimum_others}) + errors.append(error) + + if errors: + raise ValidationError(errors) + + def get_help_text(self): + text = u'%s %s %s %s' % ( + _('The password must contain at least %d characters from a-z.') % self.minimum_lower, + _('The password must contain at least %d characters from A-Z.') % self.minimum_upper, + _('The password must contain at least %d digits from 0-9.') % self.minimum_digits, + _('The password must contain at least %d non alpha numeric characters.') % self.minimum_others, + ) + return text