policies/hibp: update for flows, add unittests
This commit is contained in:
parent
5bcf2aef8c
commit
d74366f413
|
@ -139,7 +139,7 @@ stages:
|
|||
displayName: Run full test suite
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run coverage run ./manage.py test --failfast passbook
|
||||
pipenv run coverage run ./manage.py test passbook
|
||||
mkdir output-unittest
|
||||
mv unittest.xml output-unittest/unittest.xml
|
||||
mv .coverage output-unittest/coverage
|
||||
|
@ -182,7 +182,7 @@ stages:
|
|||
displayName: Run full test suite
|
||||
inputs:
|
||||
script: |
|
||||
pipenv run coverage run ./manage.py test --failfast e2e
|
||||
pipenv run coverage run ./manage.py test e2e
|
||||
mkdir output-e2e
|
||||
mv unittest.xml output-e2e/unittest.xml
|
||||
mv .coverage output-e2e/coverage
|
||||
|
|
|
@ -11,7 +11,7 @@ class HaveIBeenPwendPolicySerializer(ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = HaveIBeenPwendPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ["allowed_count"]
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ["password_field", "allowed_count"]
|
||||
|
||||
|
||||
class HaveIBeenPwendPolicyViewSet(ModelViewSet):
|
||||
|
|
|
@ -14,9 +14,9 @@ class HaveIBeenPwnedPolicyForm(forms.ModelForm):
|
|||
class Meta:
|
||||
|
||||
model = HaveIBeenPwendPolicy
|
||||
fields = GENERAL_FIELDS + ["allowed_count"]
|
||||
fields = GENERAL_FIELDS + ["password_field", "allowed_count"]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"order": forms.NumberInput(),
|
||||
"password_field": forms.TextInput(),
|
||||
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Django 3.0.8 on 2020-07-10 18:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("passbook_policies_hibp", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="haveibeenpwendpolicy",
|
||||
name="password_field",
|
||||
field=models.TextField(
|
||||
default="password",
|
||||
help_text="Field key to check, field keys defined in Prompt stages are available.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -6,8 +6,8 @@ from django.utils.translation import gettext as _
|
|||
from requests import get
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
from passbook.policies.models import Policy, PolicyResult
|
||||
from passbook.policies.types import PolicyRequest
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
@ -16,20 +16,31 @@ class HaveIBeenPwendPolicy(Policy):
|
|||
"""Check if password is on HaveIBeenPwned's list by uploading the first
|
||||
5 characters of the SHA1 Hash."""
|
||||
|
||||
password_field = models.TextField(
|
||||
default="password",
|
||||
help_text=_(
|
||||
"Field key to check, field keys defined in Prompt stages are available."
|
||||
),
|
||||
)
|
||||
|
||||
allowed_count = models.IntegerField(default=0)
|
||||
|
||||
form = "passbook.policies.hibp.forms.HaveIBeenPwnedPolicyForm"
|
||||
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
"""Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5
|
||||
characters of Password in request and checks if full hash is in response. Returns 0
|
||||
if Password is not in result otherwise the count of how many times it was used."""
|
||||
# Only check if password is being set
|
||||
if not hasattr(user, "__password__"):
|
||||
return PolicyResult(True)
|
||||
password = getattr(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]
|
||||
|
||||
pw_hash = sha1(password.encode("utf-8")).hexdigest() # nosec
|
||||
url = "https://api.pwnedpasswords.com/range/%s" % pw_hash[:5]
|
||||
url = f"https://api.pwnedpasswords.com/range/{pw_hash[:5]}"
|
||||
result = get(url).text
|
||||
final_count = 0
|
||||
for line in result.split("\r\n"):
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
"""HIBP Policy tests"""
|
||||
from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from oauth2_provider.generators import generate_client_secret
|
||||
|
||||
from passbook.policies.hibp.models import HaveIBeenPwendPolicy
|
||||
from passbook.policies.types import PolicyRequest, PolicyResult
|
||||
|
||||
|
||||
class TestHIBPPolicy(TestCase):
|
||||
"""Test HIBP Policy"""
|
||||
|
||||
def test_false(self):
|
||||
"""Failing password case"""
|
||||
policy = HaveIBeenPwendPolicy.objects.create(name="test_false",)
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = "password"
|
||||
result: PolicyResult = policy.passes(request)
|
||||
self.assertFalse(result.passing)
|
||||
self.assertTrue(result.messages[0].startswith("Password exists on "))
|
||||
|
||||
def test_true(self):
|
||||
"""Positive password case"""
|
||||
policy = HaveIBeenPwendPolicy.objects.create(name="test_true",)
|
||||
request = PolicyRequest(get_anonymous_user())
|
||||
request.context["password"] = generate_client_secret()
|
||||
result: PolicyResult = policy.passes(request)
|
||||
self.assertTrue(result.passing)
|
||||
self.assertEqual(result.messages, tuple())
|
|
@ -5819,6 +5819,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
|
||||
allowed_count:
|
||||
title: Allowed count
|
||||
type: integer
|
||||
|
|
Reference in New Issue