2019-02-25 16:21:56 +00:00
|
|
|
"""passbook HIBP Models"""
|
|
|
|
from hashlib import sha1
|
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
from django.utils.translation import gettext as _
|
|
|
|
from requests import get
|
2019-10-01 09:24:10 +01:00
|
|
|
from structlog import get_logger
|
2019-02-25 16:21:56 +00:00
|
|
|
|
2019-10-01 09:17:39 +01:00
|
|
|
from passbook.core.models import Policy, PolicyResult, User
|
2019-02-25 16:21:56 +00:00
|
|
|
|
2019-10-01 09:24:10 +01:00
|
|
|
LOGGER = get_logger(__name__)
|
2019-02-25 16:21:56 +00:00
|
|
|
|
|
|
|
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'
|
|
|
|
|
2019-10-01 09:17:39 +01:00
|
|
|
def passes(self, user: User) -> PolicyResult:
|
2019-02-25 16:21:56 +00:00
|
|
|
"""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__'):
|
2019-10-01 09:17:39 +01:00
|
|
|
return PolicyResult(True)
|
2019-02-25 16:21:56 +00:00
|
|
|
password = getattr(user, '__password__')
|
2019-02-25 16:23:42 +00:00
|
|
|
pw_hash = sha1(password.encode('utf-8')).hexdigest() # nosec
|
2019-02-25 16:21:56 +00:00
|
|
|
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)
|
2019-02-26 14:40:58 +00:00
|
|
|
LOGGER.debug("Got count %d for hash %s", final_count, pw_hash[:5])
|
2019-02-25 16:21:56 +00:00
|
|
|
if final_count > self.allowed_count:
|
2019-10-01 09:17:39 +01:00
|
|
|
message = _("Password exists on %(count)d online lists." % {'count': final_count})
|
|
|
|
return PolicyResult(False, message)
|
|
|
|
return PolicyResult(True)
|
2019-02-25 16:21:56 +00:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
2019-02-27 15:06:20 +00:00
|
|
|
verbose_name = _('Have I Been Pwned Policy')
|
|
|
|
verbose_name_plural = _('Have I Been Pwned Policies')
|