summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md1
-rw-r--r--example_project/settings.py22
-rw-r--r--ishtar_common/tests.py15
-rw-r--r--ishtar_common/utils.py44
4 files changed, 80 insertions, 2 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 8a21b2e98..4301e95c4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,7 @@ Ishtar changelog
- Load task refactoring - manage external_id regen with tasks
- Containers: manage history
- Manage expiration of passwords
+- Manage strong password policy (ISHTAR_STRONG_PASSWORD_POLICY) with "Each character type" validator
### Bug fixes ###
- Json fields: fix bad save of multi values
diff --git a/example_project/settings.py b/example_project/settings.py
index 934582b9b..705e92154 100644
--- a/example_project/settings.py
+++ b/example_project/settings.py
@@ -295,6 +295,7 @@ ISHTAR_PASSWORD_EXPIRATION_DAYS = None
ISHTAR_SEARCH_LANGUAGE = "french"
ISHTAR_SECURE = True
ISHTAR_SECURE_OPTIONS = False
+ISHTAR_STRONG_PASSWORD_POLICY = False
ISHTAR_DPTS = []
MAX_ATTEMPTS = 1 # django background tasks
@@ -314,6 +315,8 @@ DISTRIBUTION = "source"
LIB_BASE_PATH = ROOT_PATH + "../"
FIXTURE_AUTH_PATH = ROOT_PATH + "../"
+AUTH_PASSWORD_VALIDATORS = []
+
try:
from custom_settings import *
except ImportError:
@@ -463,3 +466,22 @@ if ISHTAR_SECURE_OPTIONS:
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = "DENY"
+
+if ISHTAR_STRONG_PASSWORD_POLICY and not AUTH_PASSWORD_VALIDATORS:
+ AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'OPTIONS': {
+ 'min_length': 12,
+ }
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'ishtar_common.utils.EachCharacterTypeValidator',
+ },
+ ]
diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py
index e7e665a6f..774fab2a2 100644
--- a/ishtar_common/tests.py
+++ b/ishtar_common/tests.py
@@ -93,6 +93,7 @@ from ishtar_common.utils import (
rename_and_simplify_media_name,
try_fix_file,
reverse_coordinates,
+ EachCharacterTypeValidator,
)
from ishtar_common.tasks import launch_export
from ishtar_common import utils_secretary
@@ -4040,3 +4041,17 @@ class TemplateGenerationTest(TestCase):
filtr = doc.get_filter(template, filter_re)
for key in expected_keys:
self.assertIn(key, filtr)
+
+
+class PasswordValidatorTest(TestCase):
+ def test_eachcharactertypevalidator(self):
+ validator = EachCharacterTypeValidator()
+ self.assertRaises(ValidationError, validator.validate, "")
+ self.assertRaises(ValidationError, validator.validate, "1")
+ self.assertRaises(ValidationError, validator.validate, "1a")
+ self.assertRaises(ValidationError, validator.validate, "1aA")
+ try:
+ validator.validate("1aA.")
+ except ValidationError:
+ self.fail("Each character class is put, ValidationError should "
+ "not raise.")
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 65b937339..12ab2e646 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -34,6 +34,7 @@ import re
import requests
from secretary import Renderer as MainSecretaryRenderer, UndefinedSilently
import shutil
+import string
import subprocess
import sys
import tempfile
@@ -51,7 +52,8 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.sessions.backends.db import SessionStore
from django.core.cache import cache
-from django.core.exceptions import SuspiciousOperation, ObjectDoesNotExist
+from django.core.exceptions import SuspiciousOperation, ObjectDoesNotExist, \
+ ValidationError
from django.core.files import File
from django.core.files.storage import FileSystemStorage
from django.core.validators import EMPTY_VALUES
@@ -2249,4 +2251,42 @@ def reverse_coordinates(wkt):
def reverse_list_coordinates(lst):
- return list(reversed(lst)) \ No newline at end of file
+ return list(reversed(lst))
+
+
+class EachCharacterTypeValidator:
+ def __init__(self, character_types=None):
+ if not character_types:
+ character_types = (
+ (_("uppercase letter"), string.ascii_uppercase),
+ (_("lowercase letter"), string.ascii_lowercase),
+ (_("number"), [str(i) for i in range(10)]),
+ (_("punctuation sign"), string.punctuation)
+ )
+ self.character_types = character_types
+
+ def validate(self, password, user=None):
+ ok = set()
+ for letter in password:
+ for idx, character_type in enumerate(self.character_types):
+ __, character_type = character_type
+ if idx in ok:
+ continue
+ if letter in character_type:
+ ok.add(idx)
+ missing = [
+ str(character_type[0])
+ for idx, character_type in enumerate(self.character_types)
+ if idx not in ok]
+ if not missing:
+ return
+ msg = str(_("This password must contain one of this character: ")) + ", ".join(
+ missing) + str(_("."))
+ raise ValidationError(msg, code='missing_character_types')
+
+ def get_help_text(self):
+ return str(
+ _("Your password must contain each of these characters: ")
+ ) + ", ".join(
+ [str(character_type[0]) for character_type in self.character_types]
+ ) + str(_("."))