Browse Source

added have i been pawend api call

funding-tag
Holger Frey 5 years ago
parent
commit
8c9ee1668c
  1. 3
      ordr3/models.py
  2. 59
      ordr3/services.py
  3. 74
      tests/test_services.py

3
ordr3/models.py

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
import enum
from datetime import datetime
from collections import namedtuple
FlashMessage = namedtuple("FlashMessage", ["message", "description"])
@enum.unique

59
ordr3/services.py

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import hashlib
from datetime import datetime
from collections import namedtuple
from urllib.parse import urlparse
from sqlalchemy.orm.exc import NoResultFound
import requests
from . import models
from . import models, security
CONSUMABLE_STATI = {
models.OrderStatus.ORDERED,
@ -43,7 +44,7 @@ def create_log_entry(order, status, user): @@ -43,7 +44,7 @@ def create_log_entry(order, status, user):
def verify_credentials(repo, pass_ctx, username, password):
try:
user = repo.get_user_by_username(username)
except (NoResultFound, StopIteration):
except StopIteration:
# user was not found
return None
valid, new_hash = pass_ctx.verify_and_update(password, user.password)
@ -87,3 +88,55 @@ def check_vendor_name(repo, to_check): @@ -87,3 +88,55 @@ def check_vendor_name(repo, to_check):
return CheckVendorResult(canonical_name, False)
else:
return CheckVendorResult(vendor, True)
def set_new_password(user, password):
crypt_context = security.get_passlib_context()
user.password = crypt_context.hash(password)
messages = []
pwd_length = len(password)
if pwd_length < 16:
messages.append(
models.FlashMessage(
f"Your password is quite short.",
(
f"Your Password has only {pwd_length} characters. "
'Have a look at <a href="https://www.xkcd.com/936/">'
"this comic strip</a> why longer passwords are better."
),
)
)
sha1_hash = hashlib.sha1(password.encode()).hexdigest()
if have_i_been_pwned(sha1_hash):
messages.append(
models.FlashMessage(
"This password appears in a breach or has been compromised.",
(
"Please consider changing your password. "
'<a href="/breached">Read this page</a> on why you '
"see this message."
),
)
)
return messages
def have_i_been_pwned(password_hash):
head, tails = password_hash[:5], password_hash[5:].lower()
url = f"https://api.pwnedpasswords.com/range/{head}"
headers = {"Add-Padding": "true"}
try:
response = requests.get(url, headers=headers, timeout=2)
response.raise_for_status()
except requests.RequestException:
return False
lower_case_lines = (line.lower() for line in response.text.splitlines())
for line in lower_case_lines:
if line.startswith(tails):
return True
return False

74
tests/test_services.py

@ -216,3 +216,77 @@ def test_check_vendor_name(input, name, found): @@ -216,3 +216,77 @@ def test_check_vendor_name(input, name, found):
assert result.name == name
assert result.found == found
def test_have_i_been_pwned_ok():
from ordr3.services import have_i_been_pwned
assert not have_i_been_pwned("21BD2x")
def test_have_i_been_pwned_not_ok():
from ordr3.services import have_i_been_pwned
assert have_i_been_pwned("21BD2008F2FF3F9F3AE0A2072D19CD17E971B33A")
def test_have_i_been_pwned_request_exception():
from ordr3.services import have_i_been_pwned
assert not have_i_been_pwned("xxxxx")
def test_set_new_password_ok(monkeypatch):
from ordr3 import services
from ordr3.models import User
from ordr3.security import get_passlib_context
user = User(*list("ABCDEFG"))
monkeypatch.setattr(services, "have_i_been_pwned", lambda x: False)
result = services.set_new_password(user, "1234567890123456")
assert get_passlib_context().verify("1234567890123456", user.password)
assert result == []
def test_set_new_password_to_short(monkeypatch):
from ordr3 import services
from ordr3.models import User
from ordr3.security import get_passlib_context
user = User(*list("ABCDEFG"))
monkeypatch.setattr(services, "have_i_been_pwned", lambda x: False)
result = services.set_new_password(user, "1")
assert get_passlib_context().verify("1", user.password)
assert len(result) == 1
assert result[0].message.startswith("Your password is quite short")
def test_set_new_password_breached(monkeypatch):
from ordr3 import services
from ordr3.models import User
from ordr3.security import get_passlib_context
user = User(*list("ABCDEFG"))
monkeypatch.setattr(services, "have_i_been_pwned", lambda x: True)
result = services.set_new_password(user, "1234567890123456")
assert get_passlib_context().verify("1234567890123456", user.password)
assert len(result) == 1
assert result[0].message.startswith("This password appears in a breach")
def test_set_new_password_to_short_and_breached(monkeypatch):
from ordr3 import services
from ordr3.models import User
from ordr3.security import get_passlib_context
user = User(*list("ABCDEFG"))
monkeypatch.setattr(services, "have_i_been_pwned", lambda x: True)
result = services.set_new_password(user, "1")
assert get_passlib_context().verify("1", user.password)
assert len(result) == 2
assert result[0].message.startswith("Your password is quite short")
assert result[1].message.startswith("This password appears in a breach")

Loading…
Cancel
Save