policies/password: add minimum digits
closes #1952 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
7a73ddfb60
commit
61097b9400
|
@ -13,6 +13,7 @@ class PasswordPolicySerializer(PolicySerializer):
|
|||
model = PasswordPolicy
|
||||
fields = PolicySerializer.Meta.fields + [
|
||||
"password_field",
|
||||
"amount_digits",
|
||||
"amount_uppercase",
|
||||
"amount_lowercase",
|
||||
"amount_symbols",
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
40
schema.yml
40
schema.yml
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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}
|
||||
|
|
Reference in a new issue