# -*- coding: utf-8 -*- import re from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ 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 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