Skip to content

Commit af09795

Browse files
committed
Make settings overrideable instead of replaceable
1 parent 5cbf023 commit af09795

File tree

9 files changed

+53
-23
lines changed

9 files changed

+53
-23
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Modify your `settings.py` to install the app and enable the validator:
2020
.. code-block:: python
2121
2222
INSTALLED_APPS = [
23-
'pwned',
23+
'pwned.apps.PwnedConfig',
2424
...
2525
]
2626

pwned/app_settings.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

pwned/client.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
import logging
33
import requests
44

5-
from . import app_settings
5+
from .settings import get_config
66

77

88
logger = logging.getLogger(__name__)
99

1010

1111
session = requests.Session()
12-
session.headers.update({'User-Agent': app_settings.PWNED['USER-AGENT']})
12+
session.headers.update({'User-Agent': get_config()['USER_AGENT']})
1313

1414

1515
class PwnedClient:
1616

1717
def fetch_range(self, prefix):
1818
try:
19-
url = ''.join([app_settings.PWNED['ENDPOINT'], prefix])
20-
resp = session.get(url, timeout=app_settings.PWNED['TIMEOUT'])
19+
url = ''.join([get_config()['ENDPOINT'], prefix])
20+
resp = session.get(url, timeout=get_config()['TIMEOUT'])
2121
return resp.text
2222
except requests.exceptions.RequestException as e:
2323
logger.exception("Request to Pwned Password API failed. Validation skipped.")
@@ -45,7 +45,7 @@ def make_hash(self, password):
4545
return sha1(password.encode()).hexdigest().upper()
4646

4747
def split_hash(self, hashed_password):
48-
length = app_settings.PWNED['PREFIX_LENGTH']
48+
length = get_config()['PREFIX_LENGTH']
4949
return hashed_password[:length], hashed_password[length:]
5050

5151
def count_occurrences(self, password):

pwned/settings.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from functools import lru_cache
2+
3+
from django.conf import settings
4+
5+
6+
DEFAULTS = {
7+
'ENDPOINT': 'https://api.pwnedpasswords.com/range/',
8+
'TIMEOUT': 2, # The default is conservative but will cut off some requests; average is 280ms
9+
'PREFIX_LENGTH': 5,
10+
'OCCURRENCE_THRESHOLD': 1, # How many occurrences is too many
11+
'USER_AGENT': 'github.com/craigloftus/django-pwned-validator',
12+
}
13+
14+
15+
@lru_cache()
16+
def get_config():
17+
SETTINGS = DEFAULTS.copy()
18+
# Override with any user settings
19+
SETTINGS.update(getattr(settings, 'PWNED', {}))
20+
return SETTINGS

pwned/validators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.utils.translation import gettext_lazy as _
44

55
from .client import PwnedClient
6-
from . import app_settings
6+
from .settings import get_config
77

88

99
@deconstructible
@@ -15,5 +15,5 @@ class PwnedValidator:
1515
def validate(self, password, user=None):
1616
pwned_client = self.client()
1717
count = pwned_client.count_occurrences(password)
18-
if count >= app_settings.PWNED['OCCURRENCE_THRESHOLD']:
18+
if count >= get_config()['OCCURRENCE_THRESHOLD']:
1919
raise ValidationError(self.message, code=self.code)

tests/__init__.py

Whitespace-only changes.

tests/fixtures.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import pytest
2+
3+
4+
@pytest.fixture
5+
def bypass_config_cache():
6+
from pwned.settings import get_config
7+
get_config.cache_clear()
8+
yield
9+
get_config.cache_clear()

tests/test_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
from pwned.client import PwnedClient
44

5+
from .fixtures import bypass_config_cache
6+
7+
58
def test_parse_range_valid():
69
client = PwnedClient()
710
raw_range = """
@@ -40,12 +43,13 @@ def test_split_hash():
4043
assert client.split_hash(hashed_password) == ('8CEF1', 'E00B20F463C1E48B589B03660D4E3B9EF7A')
4144

4245

43-
def test_split_hash_prefix_length(monkeypatch):
44-
monkeypatch.setattr('pwned.app_settings.PWNED', {'PREFIX_LENGTH': 10})
46+
def test_split_hash_prefix_length(settings, bypass_config_cache):
47+
settings.PWNED = {'PREFIX_LENGTH': 10}
4548
client = PwnedClient()
4649
hashed_password = '8CEF1E00B20F463C1E48B589B03660D4E3B9EF7A'
4750
prefix, suffix = client.split_hash(hashed_password)
4851
assert len(prefix) == 10
52+
assert len(suffix) == 30
4953

5054

5155
@pytest.mark.vcr

tests/test_settings.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pwned.settings import get_config
2+
3+
from .fixtures import bypass_config_cache
4+
5+
6+
def test_override_individual_settings(settings, bypass_config_cache):
7+
settings.PWNED = {
8+
'TIMEOUT': 10,
9+
}
10+
assert get_config()['TIMEOUT'] == 10

0 commit comments

Comments
 (0)