🔒️(passwords) add validators for production
This enabled various password validators to enforce password complexity.
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to
|
||||
|
||||
### Added
|
||||
|
||||
- 🔒️(passwords) add validators for production #850
|
||||
- ✨(domains) allow to re-run check on domain if status is failed
|
||||
- ✨(organization) add `is_active` field
|
||||
- ✨(domains) notify support when domain status changes #668
|
||||
|
||||
50
src/backend/core/tests/test_password_validators.py
Normal file
50
src/backend/core/tests/test_password_validators.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Test the production settings for password validation is correct."""
|
||||
|
||||
from django.contrib.auth.password_validation import (
|
||||
get_default_password_validators,
|
||||
validate_password,
|
||||
)
|
||||
|
||||
import pytest
|
||||
|
||||
from core.factories import UserFactory
|
||||
|
||||
from people.settings import Production
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(name="use_production_password_validators")
|
||||
def use_production_password_validators_fixture(settings):
|
||||
"""Set the production password validators."""
|
||||
settings.AUTH_PASSWORD_VALIDATORS = Production.AUTH_PASSWORD_VALIDATORS
|
||||
|
||||
get_default_password_validators.cache_clear()
|
||||
assert len(get_default_password_validators()) == 5
|
||||
|
||||
yield
|
||||
|
||||
get_default_password_validators.cache_clear()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"password, error",
|
||||
[
|
||||
("password", "This password is too common."),
|
||||
("password123", "This password is too common."),
|
||||
("123", "This password is too common."),
|
||||
("coucou", "This password is too common."),
|
||||
("john doe 123", "The password is too similar to the name"),
|
||||
],
|
||||
)
|
||||
def test_validate_password_validator(
|
||||
use_production_password_validators, # pylint: disable=unused-argument
|
||||
password,
|
||||
error,
|
||||
):
|
||||
"""Test the Mailbox password validation."""
|
||||
user = UserFactory(name="John Doe")
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
validate_password(password, user)
|
||||
assert error in str(excinfo.value)
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Test the production settings for password validation is correct."""
|
||||
|
||||
from django.contrib.auth.password_validation import (
|
||||
get_default_password_validators,
|
||||
validate_password,
|
||||
)
|
||||
|
||||
import pytest
|
||||
|
||||
from mailbox_manager.factories import MailboxFactory
|
||||
from people.settings import Production
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture(name="use_production_password_validators")
|
||||
def use_production_password_validators_fixture(settings):
|
||||
"""Set the production password validators."""
|
||||
settings.AUTH_PASSWORD_VALIDATORS = Production.AUTH_PASSWORD_VALIDATORS
|
||||
|
||||
get_default_password_validators.cache_clear()
|
||||
assert len(get_default_password_validators()) == 5
|
||||
|
||||
yield
|
||||
|
||||
get_default_password_validators.cache_clear()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"password, error",
|
||||
[
|
||||
("password", "This password is too common."),
|
||||
("password123", "This password is too common."),
|
||||
("123", "This password is too common."),
|
||||
("coucou", "This password is too common."),
|
||||
("john doe 123", "The password is too similar to the"),
|
||||
],
|
||||
)
|
||||
def test_validate_password_validator(
|
||||
use_production_password_validators, # pylint: disable=unused-argument
|
||||
password,
|
||||
error,
|
||||
):
|
||||
"""Test the Mailbox password validation."""
|
||||
mailbox_1 = MailboxFactory(
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
mailbox_2 = MailboxFactory(
|
||||
local_part="john.doe",
|
||||
)
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
validate_password(password, mailbox_1)
|
||||
assert error in str(excinfo.value)
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
validate_password(password, mailbox_2)
|
||||
assert error in str(excinfo.value)
|
||||
@@ -231,6 +231,7 @@ class Base(Configuration):
|
||||
"mailbox_oauth2",
|
||||
*INSTALLED_PLUGINS,
|
||||
# Third party apps
|
||||
"django_zxcvbn_password_validator",
|
||||
"drf_spectacular",
|
||||
"drf_spectacular_sidecar", # required for Django collectstatic discovery
|
||||
"corsheaders",
|
||||
@@ -915,6 +916,47 @@ class Production(Base):
|
||||
CSRF_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
|
||||
# Password management
|
||||
|
||||
# - Password strength for ZxcvbnPasswordValidator
|
||||
# 0 too guessable: risky password. (guesses < 10^3)
|
||||
# 1 very guessable: protection from throttled online attacks. (guesses < 10^6)
|
||||
# 2 somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)
|
||||
# 3 safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)
|
||||
# 4 very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)
|
||||
PASSWORD_MINIMAL_STRENGTH = values.IntegerValue(
|
||||
default=3,
|
||||
environ_name="PASSWORD_MINIMAL_STRENGTH",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
"OPTIONS": {
|
||||
"user_attributes": (
|
||||
"email", # for core.User
|
||||
"name", # for core.User
|
||||
"first_name", # for mailbox_manager.Mailbox
|
||||
"last_name", # for mailbox_manager.Mailbox
|
||||
"local_part", # for mailbox_manager.Mailbox
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django_zxcvbn_password_validator.ZxcvbnPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
# For static files in production, we want to use a backend that includes a hash in
|
||||
# the filename, that is calculated from the file content, so that browsers always
|
||||
# get the updated version of each file.
|
||||
|
||||
@@ -40,6 +40,7 @@ dependencies = [
|
||||
"django-storages==1.14.5",
|
||||
"django-timezone-field>=5.1",
|
||||
"django-treebeard==4.7.1",
|
||||
"django-zxcvbn-password-validator==1.4.5",
|
||||
"django==5.1.7",
|
||||
"djangorestframework==3.15.2",
|
||||
"dockerflow==2024.4.2",
|
||||
|
||||
Reference in New Issue
Block a user