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:
|
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",
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
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)
|
||||||
|
|
|
@ -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
|
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
|
||||||
|
|
Reference in New Issue