policies/password: Add Password Policy tests, update password policy for flows
This commit is contained in:
parent
8de3c4fbd6
commit
5bcf2aef8c
|
@ -12,6 +12,7 @@ class PasswordPolicySerializer(ModelSerializer):
|
|||
class Meta:
|
||||
model = PasswordPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + [
|
||||
"password_field",
|
||||
"amount_uppercase",
|
||||
"amount_lowercase",
|
||||
"amount_symbols",
|
||||
|
|
|
@ -14,6 +14,7 @@ class PasswordPolicyForm(forms.ModelForm):
|
|||
|
||||
model = PasswordPolicy
|
||||
fields = GENERAL_FIELDS + [
|
||||
"password_field",
|
||||
"amount_uppercase",
|
||||
"amount_lowercase",
|
||||
"amount_symbols",
|
||||
|
@ -23,6 +24,7 @@ class PasswordPolicyForm(forms.ModelForm):
|
|||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"password_field": forms.TextInput(),
|
||||
"symbol_charset": forms.TextInput(),
|
||||
"error_message": forms.TextInput(),
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -14,6 +14,13 @@ LOGGER = get_logger()
|
|||
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)
|
||||
|
@ -24,19 +31,29 @@ class PasswordPolicy(Policy):
|
|||
form = "passbook.policies.password.forms.PasswordPolicyForm"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
# Only check if password is being set
|
||||
if not hasattr(request.user, "__password__"):
|
||||
return PolicyResult(True)
|
||||
password = getattr(request.user, "__password__")
|
||||
if self.password_field not in request.context:
|
||||
LOGGER.warning(
|
||||
"Password field not set in Policy Request",
|
||||
field=self.password_field,
|
||||
fields=request.context.keys(),
|
||||
)
|
||||
password = request.context[self.password_field]
|
||||
|
||||
filter_regex = r""
|
||||
filter_regex = []
|
||||
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:
|
||||
filter_regex += r"[A-Z]{%d,}" % self.amount_uppercase
|
||||
filter_regex.append(r"[A-Z]{%d,}" % self.amount_uppercase)
|
||||
if self.amount_symbols > 0:
|
||||
filter_regex += r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols)
|
||||
result = bool(re.compile(filter_regex).match(password))
|
||||
filter_regex.append(
|
||||
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:
|
||||
return PolicyResult(result, self.error_message)
|
||||
return PolicyResult(result)
|
||||
|
|
|
@ -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())
|
|
@ -5838,6 +5838,11 @@ definitions:
|
|||
title: Name
|
||||
type: string
|
||||
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:
|
||||
title: Amount uppercase
|
||||
type: integer
|
||||
|
|
Reference in New Issue