authentic/tests/test_passwords.py

155 lines
5.5 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 string
import pytest
from django.core.exceptions import ValidationError
from authentic2 import app_settings
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
from authentic2.models import Attribute
from authentic2.passwords import generate_password, init_password_dictionaries, validate_password
from authentic2.utils.misc import get_password_authenticator
def test_generate_password(db):
passwords = {generate_password() for i in range(10)}
char_classes = [string.digits, string.ascii_lowercase, string.ascii_uppercase, string.punctuation]
assert len(passwords) == 10
for password in passwords:
assert len(password) >= max(get_password_authenticator().password_min_length, 8)
assert sum(any(char in char_class for char in password) for char_class in char_classes) == max(
app_settings.A2_PASSWORD_POLICY_MIN_CLASSES, 3
)
def test_validate_password_default_policy(db, settings):
with pytest.raises(ValidationError):
validate_password('aaaaaZZZZZZ')
with pytest.raises(ValidationError):
validate_password('00000aaaaaa')
with pytest.raises(ValidationError):
validate_password('00000ZZZZZZ')
validate_password('000aaaaZZZZ')
def test_digits_password_policy(db, settings):
LoginPasswordAuthenticator.objects.update(
password_regex='^[0-9]{8}$', password_regex_error_msg='pasbon', password_min_length=0
)
settings.A2_PASSWORD_POLICY_MIN_CLASSES = 0
with pytest.raises(ValidationError):
validate_password('aaa')
validate_password('12345678')
@pytest.mark.parametrize(
'password,min_strength',
[
('?', 0),
('?JR!', 1),
('?JR!p4A', 2),
('?JR!p4A2i', 3),
('?JR!p4A2i:#', 4),
],
)
def test_validate_password_strength(db, settings, password, min_strength):
LoginPasswordAuthenticator.objects.update(
password_min_length=len(password), min_password_strength=min_strength
)
validate_password(password)
with pytest.raises(ValidationError):
LoginPasswordAuthenticator.objects.update(password_min_length=len(password) + 1)
validate_password(password)
if min_strength < 4:
LoginPasswordAuthenticator.objects.update(
password_min_length=len(password), min_password_strength=min_strength + 1
)
with pytest.raises(ValidationError):
validate_password(password)
def test_validate_password_strength_user_attributes(db, simple_user):
LoginPasswordAuthenticator.objects.update(min_password_strength=3)
simple_user.attributes.last_name = 'Kaczynski'
validate_password('Kaczynski')
with pytest.raises(ValidationError):
validate_password('Kaczynski', inputs={'last_name': 'Kaczynski'})
# each word of input should be matched
with pytest.raises(ValidationError):
validate_password('Kaczynski', inputs={'last_name': 'Kaczynski Faas-Hardegger'})
with pytest.raises(ValidationError):
validate_password('Kaczynski Faas-Hardegger', inputs={'last_name': 'Kaczynski Faas-Hardegger'})
simple_user.attributes.last_name = 'Kaczynski Faas-Hardegger'
with pytest.raises(ValidationError):
validate_password('Kaczynski', user=simple_user)
with pytest.raises(ValidationError):
validate_password('Kaczynski Faas-Hardegger', user=simple_user)
simple_user.attributes.last_name = 'Kaczynski'
# inputs dict should override user attributes
validate_password('Kaczynski', user=simple_user, inputs={'last_name': 'Faas-Hardegger'})
def test_validate_password_strength_custom_attribute(db, simple_user):
LoginPasswordAuthenticator.objects.update(min_password_strength=3)
Attribute.objects.create(
kind='string',
name='favourite_song',
)
validate_password('0opS 1 D1t iT @GAiN', user=simple_user)
simple_user.attributes.favourite_song = '0opS 1 D1t iT @GAiN'
with pytest.raises(ValidationError):
validate_password('0opS 1 D1t iT @GAiN', user=simple_user)
def test_init_password_dictionaries(db, tmp_path):
test_dictionary = tmp_path / 'dict'
test_dictionary.write_text('0opS 1 D1t iT @GAiN\n1 pl@yed with your He@rt\n')
LoginPasswordAuthenticator.objects.update(min_password_strength=3)
validate_password('0opS 1 D1t iT @GAiN')
validate_password('1 pl@yed with your He@rt')
app_settings.A2_PASSWORD_POLICY_DICTIONARIES = {'3r1tney': test_dictionary}
init_password_dictionaries()
with pytest.raises(ValidationError):
validate_password('0opS 1 D1t iT @GAiN')
with pytest.raises(ValidationError):
validate_password('1 pl@yed with your He@rt')
app_settings.A2_PASSWORD_POLICY_DICTIONARIES = {}
init_password_dictionaries()
validate_password('0opS 1 D1t iT @GAiN')
validate_password('1 pl@yed with your He@rt')