authentic/src/authentic2/passwords.py

136 lines
4.4 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import abc
import random
import re
import string
from django.core.exceptions import ValidationError
from django.utils.functional import lazy
from django.utils.module_loading import import_string
from django.utils.translation import ugettext as _
from . import app_settings
def generate_password():
"""Generate a password that validates current password policy.
Beware that A2_PASSWORD_POLICY_REGEX cannot be validated.
"""
digits = string.digits
lower = string.ascii_lowercase
upper = string.ascii_uppercase
punc = string.punctuation
min_len = max(app_settings.A2_PASSWORD_POLICY_MIN_LENGTH, 8)
min_class_count = max(app_settings.A2_PASSWORD_POLICY_MIN_CLASSES, 3)
new_password = []
generator = random.SystemRandom()
while len(new_password) < min_len:
for cls in (digits, lower, upper, punc)[:min_class_count]:
new_password.append(generator.choice(cls))
generator.shuffle(new_password)
return ''.join(new_password)
class PasswordChecker(object, metaclass=abc.ABCMeta):
class Check(object):
def __init__(self, label, result):
self.label = label
self.result = result
def __init__(self, *args, **kwargs):
pass
@abc.abstractmethod
def __call__(self, password, **kwargs):
"""Return an iterable of Check objects giving the list of checks and
their result."""
return []
class DefaultPasswordChecker(PasswordChecker):
@property
def min_length(self):
return app_settings.A2_PASSWORD_POLICY_MIN_LENGTH
@property
def at_least_one_lowercase(self):
return app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 0
@property
def at_least_one_digit(self):
return app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 1
@property
def at_least_one_uppercase(self):
return app_settings.A2_PASSWORD_POLICY_MIN_CLASSES > 2
@property
def regexp(self):
return app_settings.A2_PASSWORD_POLICY_REGEX
@property
def regexp_label(self):
return app_settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG
def __call__(self, password, **kwargs):
if self.min_length:
yield self.Check(
result=len(password) >= self.min_length, label=_('%s characters') % self.min_length
)
if self.at_least_one_lowercase:
yield self.Check(result=any(c.islower() for c in password), label=_('1 lowercase letter'))
if self.at_least_one_digit:
yield self.Check(result=any(c.isdigit() for c in password), label=_('1 digit'))
if self.at_least_one_uppercase:
yield self.Check(result=any(c.isupper() for c in password), label=_('1 uppercase letter'))
if self.regexp and self.regexp_label:
yield self.Check(result=bool(re.match(self.regexp, password)), label=self.regexp_label)
def get_password_checker(*args, **kwargs):
return import_string(app_settings.A2_PASSWORD_POLICY_CLASS)(*args, **kwargs)
def validate_password(password):
error = password_help_text(password, only_errors=True)
if error:
raise ValidationError(_('This password is not accepted.'))
def password_help_text(password='', only_errors=False):
password_checker = get_password_checker()
criteria = [check.label for check in password_checker(password) if not (only_errors and check.result)]
if criteria:
html_criteria = [u'<span class="a2-password-policy-rule">%s</span>' % criter for criter in criteria]
return _(
'In order to create a secure password, please use at least: '
'<span class="a2-password-policy-container">%s</span>'
) % (''.join(html_criteria))
else:
return ''
password_help_text = lazy(password_help_text, str)