add experimental HaveIBeenPwned Password Policy

This commit is contained in:
Jens Langhammer 2019-02-25 17:21:56 +01:00
parent 8c94aef6d0
commit 10d6a30f2c
9 changed files with 109 additions and 0 deletions

View file

@ -73,6 +73,7 @@ INSTALLED_APPS = [
'passbook.saml_idp.apps.PassbookSAMLIDPConfig', 'passbook.saml_idp.apps.PassbookSAMLIDPConfig',
'passbook.otp.apps.PassbookOTPConfig', 'passbook.otp.apps.PassbookOTPConfig',
'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig', 'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig',
'passbook.hibp_policy.apps.PassbookHIBPConfig',
] ]
# Message Tag fix for bootstrap CSS Classes # Message Tag fix for bootstrap CSS Classes

View file

View file

@ -0,0 +1,2 @@
"""passbook hibp_policy"""
__version__ = '0.0.7-alpha'

View file

@ -0,0 +1,5 @@
"""Passbook HIBP Admin"""
from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_hibp_policy')

View file

@ -0,0 +1,11 @@
"""Passbook hibp app config"""
from django.apps import AppConfig
class PassbookHIBPConfig(AppConfig):
"""Passbook hibp app config"""
name = 'passbook.hibp_policy'
label = 'passbook_hibp_policy'
verbose_name = 'passbook HaveIBeenPwned Policy'

View file

@ -0,0 +1,19 @@
"""passbook HaveIBeenPwned Policy forms"""
from django import forms
from passbook.core.forms.policies import GENERAL_FIELDS
from passbook.hibp_policy.models import HaveIBeenPwendPolicy
class HaveIBeenPwnedPolicyForm(forms.ModelForm):
"""Edit HaveIBeenPwendPolicy instances"""
class Meta:
model = HaveIBeenPwendPolicy
fields = GENERAL_FIELDS + ['allowed_count']
widgets = {
'name': forms.TextInput(),
'order': forms.NumberInput(),
}

View file

@ -0,0 +1,28 @@
# Generated by Django 2.1.7 on 2019-02-25 15:50
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('passbook_core', '0011_auto_20190225_1438'),
]
operations = [
migrations.CreateModel(
name='HaveIBeenPwendPolicy',
fields=[
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
('allowed_count', models.IntegerField(default=0)),
],
options={
'verbose_name': 'HaveIBeenPwned Policy',
'verbose_name_plural': 'HaveIBeenPwned Policies',
},
bases=('passbook_core.policy',),
),
]

View file

@ -0,0 +1,43 @@
"""passbook HIBP Models"""
from hashlib import sha1
from django.db import models
from django.utils.translation import gettext as _
from requests import get
from passbook.core.models import Policy, User
class HaveIBeenPwendPolicy(Policy):
"""Check if password is on HaveIBeenPwned's list by upload the first
5 characters of the SHA1 Hash."""
allowed_count = models.IntegerField(default=0)
form = 'passbook.hibp_policy.forms.HaveIBeenPwnedPolicyForm'
def passes(self, user: User) -> bool:
"""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 True
password = getattr(user, '__password__')
pw_hash = sha1(password.encode('utf-8')).hexdigest()
url = 'https://api.pwnedpasswords.com/range/%s' % pw_hash[:5]
result = get(url).text
final_count = 0
for line in result.split('\r\n'):
full_hash, count = line.split(':')
if pw_hash[5:] == full_hash.lower():
final_count = int(count)
if final_count > self.allowed_count:
return False
return True
class Meta:
verbose_name = _('have i been pwned Policy')
verbose_name_plural = _('have i been pwned Policies')