policies/password: add minimum digits

closes #1952

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-12-18 16:15:56 +01:00
parent 7a73ddfb60
commit 61097b9400
9 changed files with 115 additions and 21 deletions

View File

@ -13,6 +13,7 @@ class PasswordPolicySerializer(PolicySerializer):
model = PasswordPolicy
fields = PolicySerializer.Meta.fields + [
"password_field",
"amount_digits",
"amount_uppercase",
"amount_lowercase",
"amount_symbols",

View File

@ -0,0 +1,38 @@
# Generated by Django 4.0 on 2021-12-18 14:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_password", "0002_passwordpolicy_password_field"),
]
operations = [
migrations.AddField(
model_name="passwordpolicy",
name="amount_digits",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_lowercase",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_symbols",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="amount_uppercase",
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name="passwordpolicy",
name="length_min",
field=models.PositiveIntegerField(default=0),
),
]

View File

@ -13,6 +13,7 @@ from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
RE_LOWER = re.compile("[a-z]")
RE_UPPER = re.compile("[A-Z]")
RE_DIGITS = re.compile("[0-9]")
class PasswordPolicy(Policy):
@ -23,10 +24,11 @@ class PasswordPolicy(Policy):
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)
amount_digits = models.PositiveIntegerField(default=0)
amount_uppercase = models.PositiveIntegerField(default=0)
amount_lowercase = models.PositiveIntegerField(default=0)
amount_symbols = models.PositiveIntegerField(default=0)
length_min = models.PositiveIntegerField(default=0)
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
error_message = models.TextField()
@ -40,6 +42,7 @@ class PasswordPolicy(Policy):
def component(self) -> str:
return "ak-policy-password-form"
# pylint: disable=too-many-return-statements
def passes(self, request: PolicyRequest) -> PolicyResult:
if (
self.password_field not in request.context
@ -62,6 +65,9 @@ class PasswordPolicy(Policy):
LOGGER.debug("password failed", reason="length")
return PolicyResult(False, self.error_message)
if self.amount_digits > 0 and len(RE_DIGITS.findall(password)) < self.amount_digits:
LOGGER.debug("password failed", reason="amount_digits")
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)

View File

@ -13,6 +13,7 @@ class TestPasswordPolicy(TestCase):
def setUp(self) -> None:
self.policy = PasswordPolicy.objects.create(
name="test_false",
amount_digits=1,
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
@ -38,7 +39,7 @@ class TestPasswordPolicy(TestCase):
def test_failed_lowercase(self):
"""not enough lowercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe" # nosec
request.context["password"] = "1TTTTTTTTTTTTTTTTTTTTTTe" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
@ -46,15 +47,23 @@ class TestPasswordPolicy(TestCase):
def test_failed_uppercase(self):
"""not enough uppercase"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "tttttttttttttttttttttttE" # nosec
request.context["password"] = "1tttttttttttttttttttttE" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_symbols(self):
"""not enough uppercase"""
"""not enough symbols"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TETETETETETETETETETETETETe!!!" # nosec
request.context["password"] = "1ETETETETETETETETETETETETe!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_failed_digits(self):
"""not enough digits"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "TETETETETETETETETETETE1e!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
@ -62,7 +71,7 @@ class TestPasswordPolicy(TestCase):
def test_true(self):
"""Positive password case"""
request = PolicyRequest(get_anonymous_user())
request.context["password"] = generate_key() + "ee!!!" # nosec
request.context["password"] = generate_key() + "1ee!!!" # nosec
result: PolicyResult = self.policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -8309,6 +8309,10 @@ paths:
operationId: policies_password_list
description: Password Policy Viewset
parameters:
- in: query
name: amount_digits
schema:
type: integer
- in: query
name: amount_lowercase
schema:
@ -26413,22 +26417,26 @@ components:
type: string
description: Field key to check, field keys defined in Prompt stages are
available.
amount_digits:
type: integer
maximum: 2147483647
minimum: 0
amount_uppercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_lowercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_symbols:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
length_min:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
symbol_charset:
type: string
error_message:
@ -26457,22 +26465,26 @@ components:
minLength: 1
description: Field key to check, field keys defined in Prompt stages are
available.
amount_digits:
type: integer
maximum: 2147483647
minimum: 0
amount_uppercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_lowercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_symbols:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
length_min:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
symbol_charset:
type: string
minLength: 1
@ -27630,22 +27642,26 @@ components:
minLength: 1
description: Field key to check, field keys defined in Prompt stages are
available.
amount_digits:
type: integer
maximum: 2147483647
minimum: 0
amount_uppercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_lowercase:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
amount_symbols:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
length_min:
type: integer
maximum: 2147483647
minimum: -2147483648
minimum: 0
symbol_charset:
type: string
minLength: 1

View File

@ -2836,6 +2836,10 @@ msgstr "Messages"
msgid "Metadata"
msgstr "Metadata"
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Digits"
msgstr "Minimum amount of Digits"
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Lowercase Characters"
msgstr "Minimum amount of Lowercase Characters"

View File

@ -2815,6 +2815,10 @@ msgstr "Messages"
msgid "Metadata"
msgstr "Métadonnées"
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Digits"
msgstr ""
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Lowercase Characters"
msgstr "Nombre minimum de caractères minuscules"

View File

@ -2826,6 +2826,10 @@ msgstr ""
msgid "Metadata"
msgstr ""
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Digits"
msgstr ""
#: src/pages/policies/password/PasswordPolicyForm.ts
msgid "Minimum amount of Lowercase Characters"
msgstr ""

View File

@ -122,6 +122,18 @@ export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Digits`}
?required=${true}
name="amountDigits"
>
<input
type="number"
value="${first(this.instance?.amountDigits, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Symbols Characters`}
?required=${true}