162 lines
6.7 KiB
Python
162 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
import re
|
|
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
|
|
char_counters = {}
|
|
used_classes = []
|
|
credits = {
|
|
'lower': self.lcredit,
|
|
'upper': self.ucredit,
|
|
'digit': self.dcredit,
|
|
'other': self.ocredit,
|
|
}
|
|
|
|
for c in password:
|
|
if c not in char_counters:
|
|
char_counters[c] = 1
|
|
else:
|
|
char_counters[c] += 1
|
|
if (self.max_repeat > 0) and (char_counters[c] > self.max_repeat):
|
|
continue
|
|
|
|
score += 1
|
|
|
|
if c.isalpha() and c.islower():
|
|
char_class = 'lower'
|
|
elif c.isupper():
|
|
char_class = 'upper'
|
|
elif c.isdigit():
|
|
char_class = 'digit'
|
|
else:
|
|
char_class = 'other'
|
|
|
|
if char_class not in used_classes:
|
|
used_classes.append(char_class)
|
|
if credits[char_class] > 0:
|
|
score += 1
|
|
credits[char_class] -= 1
|
|
|
|
return score, len(used_classes)
|
|
|
|
def __init__(self, min_score=18, max_repeat=5, lcredit=0, ucredit=2, dcredit=2, ocredit=3, min_classes=2):
|
|
self.min_score = min_score
|
|
self.max_repeat = max_repeat
|
|
self.lcredit = lcredit
|
|
self.ucredit = ucredit
|
|
self.dcredit = dcredit
|
|
self.ocredit = ocredit
|
|
self.min_classes = min_classes
|
|
|
|
def validate(self, password, user=None):
|
|
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).'),
|
|
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).'),
|
|
code='too_little_score')
|
|
|
|
return score
|
|
|
|
def get_help_text(self):
|
|
text = u'%s\n%s' % (
|
|
_(u'The password must get a minimum score of %d points.') % self.min_score,
|
|
_(u'For each character the score is increased by 1 point.'),
|
|
)
|
|
if self.lcredit > 0:
|
|
text += '\n'
|
|
text += _(u'The first %d lower characters increase the score by 1 point each.') % self.lcredit
|
|
if self.ucredit > 0:
|
|
text += '\n'
|
|
text += _(u'The first %d upper characters increase the score by 1 point each.') % self.ucredit
|
|
if self.dcredit > 0:
|
|
text += '\n'
|
|
text += _(u'The first %d digits increase the score by 1 point each.') % self.dcredit
|
|
if self.ocredit > 0:
|
|
text += '\n'
|
|
text += _(u'The first %d non alpha numeric characters'
|
|
u' increase the score by 1 point each.') % self.ocredit
|
|
if self.max_repeat > 0:
|
|
text += '\n'
|
|
text += _(u'If a particular character is used more than %d times,'
|
|
u' it will not increase the score anymore.') % self.max_repeat
|
|
if self.min_classes > 0:
|
|
text += '\n'
|
|
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
|