155 lines
5.5 KiB
Python
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')
|