policies/password: Add Password Policy tests, update password policy for flows

This commit is contained in:
Jens Langhammer 2020-07-10 20:43:35 +02:00
parent 8de3c4fbd6
commit 5bcf2aef8c
6 changed files with 97 additions and 9 deletions

View File

@ -12,6 +12,7 @@ class PasswordPolicySerializer(ModelSerializer):
class Meta: class Meta:
model = PasswordPolicy model = PasswordPolicy
fields = GENERAL_SERIALIZER_FIELDS + [ fields = GENERAL_SERIALIZER_FIELDS + [
"password_field",
"amount_uppercase", "amount_uppercase",
"amount_lowercase", "amount_lowercase",
"amount_symbols", "amount_symbols",

View File

@ -14,6 +14,7 @@ class PasswordPolicyForm(forms.ModelForm):
model = PasswordPolicy model = PasswordPolicy
fields = GENERAL_FIELDS + [ fields = GENERAL_FIELDS + [
"password_field",
"amount_uppercase", "amount_uppercase",
"amount_lowercase", "amount_lowercase",
"amount_symbols", "amount_symbols",
@ -23,6 +24,7 @@ class PasswordPolicyForm(forms.ModelForm):
] ]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
"password_field": forms.TextInput(),
"symbol_charset": forms.TextInput(), "symbol_charset": forms.TextInput(),
"error_message": forms.TextInput(), "error_message": forms.TextInput(),
} }

View File

@ -0,0 +1,21 @@
# Generated by Django 3.0.8 on 2020-07-10 18:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_policies_password", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="passwordpolicy",
name="password_field",
field=models.TextField(
default="password",
help_text="Field key to check, field keys defined in Prompt stages are available.",
),
),
]

View File

@ -14,6 +14,13 @@ LOGGER = get_logger()
class PasswordPolicy(Policy): class PasswordPolicy(Policy):
"""Policy to make sure passwords have certain properties""" """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_uppercase = models.IntegerField(default=0)
amount_lowercase = models.IntegerField(default=0) amount_lowercase = models.IntegerField(default=0)
amount_symbols = models.IntegerField(default=0) amount_symbols = models.IntegerField(default=0)
@ -24,19 +31,29 @@ class PasswordPolicy(Policy):
form = "passbook.policies.password.forms.PasswordPolicyForm" form = "passbook.policies.password.forms.PasswordPolicyForm"
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
# Only check if password is being set if self.password_field not in request.context:
if not hasattr(request.user, "__password__"): LOGGER.warning(
return PolicyResult(True) "Password field not set in Policy Request",
password = getattr(request.user, "__password__") field=self.password_field,
fields=request.context.keys(),
)
password = request.context[self.password_field]
filter_regex = r"" filter_regex = []
if self.amount_lowercase > 0: if self.amount_lowercase > 0:
filter_regex += r"[a-z]{%d,}" % self.amount_lowercase filter_regex.append(r"[a-z]{%d,}" % self.amount_lowercase)
if self.amount_uppercase > 0: if self.amount_uppercase > 0:
filter_regex += r"[A-Z]{%d,}" % self.amount_uppercase filter_regex.append(r"[A-Z]{%d,}" % self.amount_uppercase)
if self.amount_symbols > 0: if self.amount_symbols > 0:
filter_regex += r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols) filter_regex.append(
result = bool(re.compile(filter_regex).match(password)) r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols)
)
full_regex = "|".join(filter_regex)
LOGGER.debug("Built regex", regexp=full_regex)
result = bool(re.compile(full_regex).match(password))
result = result and len(password) >= self.length_min
if not result: if not result:
return PolicyResult(result, self.error_message) return PolicyResult(result, self.error_message)
return PolicyResult(result) return PolicyResult(result)

View File

@ -0,0 +1,42 @@
"""Password Policy tests"""
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from passbook.policies.password.models import PasswordPolicy
from passbook.policies.types import PolicyRequest, PolicyResult
class TestPasswordPolicy(TestCase):
"""Test Password Policy"""
def test_false(self):
"""Failing password case"""
policy = PasswordPolicy.objects.create(
name="test_false",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=24,
error_message="test message",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "test"
result: PolicyResult = policy.passes(request)
self.assertFalse(result.passing)
self.assertEqual(result.messages, ("test message",))
def test_true(self):
"""Positive password case"""
policy = PasswordPolicy.objects.create(
name="test_true",
amount_uppercase=1,
amount_lowercase=2,
amount_symbols=3,
length_min=3,
error_message="test message",
)
request = PolicyRequest(get_anonymous_user())
request.context["password"] = "Test()!"
result: PolicyResult = policy.passes(request)
self.assertTrue(result.passing)
self.assertEqual(result.messages, tuple())

View File

@ -5838,6 +5838,11 @@ definitions:
title: Name title: Name
type: string type: string
x-nullable: true x-nullable: true
password_field:
title: Password field
description: Field key to check, field keys defined in Prompt stages are available.
type: string
minLength: 1
amount_uppercase: amount_uppercase:
title: Amount uppercase title: Amount uppercase
type: integer type: integer