37c29a073e
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""user field matcher models"""
|
|
import re
|
|
|
|
from django.db import models
|
|
from django.utils.translation import gettext as _
|
|
from rest_framework.serializers import BaseSerializer
|
|
from structlog.stdlib import get_logger
|
|
|
|
from authentik.policies.models import Policy
|
|
from authentik.policies.types import PolicyRequest, PolicyResult
|
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
|
|
|
LOGGER = get_logger()
|
|
RE_LOWER = re.compile("[a-z]")
|
|
RE_UPPER = re.compile("[A-Z]")
|
|
|
|
|
|
class PasswordPolicy(Policy):
|
|
"""Policy to make sure passwords have certain properties"""
|
|
|
|
password_field = models.TextField(
|
|
default="password",
|
|
help_text=_("Field key to check, field keys defined in Prompt stages are available."),
|
|
)
|
|
|
|
amount_uppercase = models.IntegerField(default=0)
|
|
amount_lowercase = models.IntegerField(default=0)
|
|
amount_symbols = models.IntegerField(default=0)
|
|
length_min = models.IntegerField(default=0)
|
|
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
|
|
error_message = models.TextField()
|
|
|
|
@property
|
|
def serializer(self) -> BaseSerializer:
|
|
from authentik.policies.password.api import PasswordPolicySerializer
|
|
|
|
return PasswordPolicySerializer
|
|
|
|
@property
|
|
def component(self) -> str:
|
|
return "ak-policy-password-form"
|
|
|
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
|
if (
|
|
self.password_field not in request.context
|
|
and self.password_field not in request.context.get(PLAN_CONTEXT_PROMPT, {})
|
|
):
|
|
LOGGER.warning(
|
|
"Password field not set in Policy Request",
|
|
field=self.password_field,
|
|
fields=request.context.keys(),
|
|
prompt_fields=request.context.get(PLAN_CONTEXT_PROMPT, {}).keys(),
|
|
)
|
|
return PolicyResult(False, _("Password not set in context"))
|
|
|
|
if self.password_field in request.context:
|
|
password = request.context[self.password_field]
|
|
else:
|
|
password = request.context[PLAN_CONTEXT_PROMPT][self.password_field]
|
|
|
|
if len(password) < self.length_min:
|
|
LOGGER.debug("password failed", reason="length")
|
|
return PolicyResult(False, self.error_message)
|
|
|
|
if self.amount_lowercase > 0 and len(RE_LOWER.findall(password)) < self.amount_lowercase:
|
|
LOGGER.debug("password failed", reason="amount_lowercase")
|
|
return PolicyResult(False, self.error_message)
|
|
if self.amount_uppercase > 0 and len(RE_UPPER.findall(password)) < self.amount_lowercase:
|
|
LOGGER.debug("password failed", reason="amount_uppercase")
|
|
return PolicyResult(False, self.error_message)
|
|
if self.amount_symbols > 0:
|
|
count = 0
|
|
for symbol in self.symbol_charset:
|
|
count += password.count(symbol)
|
|
if count < self.amount_symbols:
|
|
LOGGER.debug("password failed", reason="amount_symbols")
|
|
return PolicyResult(False, self.error_message)
|
|
|
|
return PolicyResult(True)
|
|
|
|
class Meta:
|
|
|
|
verbose_name = _("Password Policy")
|
|
verbose_name_plural = _("Password Policies")
|