mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
Integrate AXS_SENSITIVE_PARAMETERS functionality with AXES_PASSWORD_FORM_FIELD
This commit is contained in:
parent
f54c4f095b
commit
170dacc112
5 changed files with 53 additions and 44 deletions
|
|
@ -123,7 +123,5 @@ settings.AXES_ALLOWED_CORS_ORIGINS = getattr(settings, "AXES_ALLOWED_CORS_ORIGIN
|
|||
settings.AXES_SENSITIVE_PARAMETERS = getattr(
|
||||
settings,
|
||||
"AXES_SENSITIVE_PARAMETERS",
|
||||
[
|
||||
"password",
|
||||
],
|
||||
[],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from axes.helpers import (
|
|||
get_credentials,
|
||||
get_failure_limit,
|
||||
get_query_str,
|
||||
cleanse_params,
|
||||
)
|
||||
from axes.models import AccessLog, AccessAttempt
|
||||
from axes.signals import user_locked_out
|
||||
|
|
@ -110,8 +109,8 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
|
|||
)
|
||||
|
||||
# This replaces null byte chars that crash saving failures, meaning an attacker doesn't get locked out.
|
||||
get_data = get_query_str(cleanse_params(request.GET)).replace("\0", "0x00")
|
||||
post_data = get_query_str(cleanse_params(request.POST)).replace("\0", "0x00")
|
||||
get_data = get_query_str(request.GET).replace("\0", "0x00")
|
||||
post_data = get_query_str(request.POST).replace("\0", "0x00")
|
||||
|
||||
if self.is_whitelisted(request, credentials):
|
||||
log.info("AXES: Login failed from whitelisted client %s.", client_str)
|
||||
|
|
|
|||
|
|
@ -277,18 +277,42 @@ def get_client_str(
|
|||
return client_str
|
||||
|
||||
|
||||
def cleanse_parameters(params: dict) -> dict:
|
||||
"""
|
||||
Replace sensitive parameter values in a parameter dict with
|
||||
a safe placeholder value.
|
||||
|
||||
Parameters name ``'password'`` will always be cleansed. Additionally,
|
||||
parameters named in ``settings.AXES_SENSITIVE_PARAMETERS`` and
|
||||
``settings.AXES_PASSWORD_FORM_FIELD will be cleansed.
|
||||
|
||||
This is used to prevent passwords and similar values from
|
||||
being logged in cleartext.
|
||||
"""
|
||||
sensitive_parameters = ["password"] + settings.AXES_SENSITIVE_PARAMETERS
|
||||
if settings.AXES_PASSWORD_FORM_FIELD:
|
||||
sensitive_parameters.append(settings.AXES_PASSWORD_FORM_FIELD)
|
||||
|
||||
if sensitive_parameters:
|
||||
cleansed = params.copy()
|
||||
for param in sensitive_parameters:
|
||||
if param in cleansed:
|
||||
cleansed[param] = "********************"
|
||||
return cleansed
|
||||
return params
|
||||
|
||||
|
||||
def get_query_str(query: Type[QueryDict], max_length: int = 1024) -> str:
|
||||
"""
|
||||
Turns a query dictionary into an easy-to-read list of key-value pairs.
|
||||
|
||||
If a field is called either ``'password'`` or ``settings.AXES_PASSWORD_FORM_FIELD`` it will be excluded.
|
||||
If a field is called either ``'password'`` or ``settings.AXES_PASSWORD_FORM_FIELD`` or if the fieldname is included
|
||||
in ``settings.AXES_SENSITIVE_PARAMETERS`` its value will be masked.
|
||||
|
||||
The length of the output is limited to max_length to avoid a DoS attack via excessively large payloads.
|
||||
"""
|
||||
|
||||
query_dict = query.copy()
|
||||
query_dict.pop("password", None)
|
||||
query_dict.pop(settings.AXES_PASSWORD_FORM_FIELD, None)
|
||||
query_dict = cleanse_parameters(query.copy())
|
||||
|
||||
template = Template("$key=$value")
|
||||
items = [{"key": k, "value": v} for k, v in query_dict.items()]
|
||||
|
|
@ -476,24 +500,3 @@ def toggleable(func) -> Callable:
|
|||
return func(*args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def cleanse_params(params: dict) -> dict:
|
||||
"""
|
||||
Replace sensitive parameter values in a parameter dict with
|
||||
a safe placeholder value.
|
||||
|
||||
Parameters to be cleansed are named in
|
||||
``settings.AXES_SENSITIVE_PARAMETERS``. If this setting is
|
||||
empty, no parameters will be replaced.
|
||||
|
||||
This is used to prevent passwords and similar values from
|
||||
being logged in cleartext.
|
||||
"""
|
||||
if settings.AXES_SENSITIVE_PARAMETERS:
|
||||
cleansed = params.copy()
|
||||
for param in settings.AXES_SENSITIVE_PARAMETERS:
|
||||
if param in cleansed:
|
||||
cleansed[param] = "********************"
|
||||
return cleansed
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
Default: ``None``
|
||||
* ``AXES_PASSWORD_FORM_FIELD``: the name of the form or credentials field that contains your users password.
|
||||
Default: ``password``
|
||||
* ``AXES_SENSITIVE_PARAMETERS``: Configures POST and GET parameter values (in addition to the value of
|
||||
``AXES_PASSWORD_FORM_FIELD``) to mask in login attempt logging.
|
||||
Default: ``[]``
|
||||
* ``AXES_NEVER_LOCKOUT_GET``: If ``True``, Axes will never lock out HTTP GET requests.
|
||||
Default: ``False``
|
||||
* ``AXES_NEVER_LOCKOUT_WHITELIST``: If ``True``, users can always login from whitelisted IP addresses.
|
||||
|
|
@ -111,8 +114,6 @@ The following ``settings.py`` options are available for customizing Axes behavio
|
|||
Default: ``False``
|
||||
* ``AXES_ALLOWED_CORS_ORIGINS``: Configures lockout response CORS headers for XHR requests.
|
||||
Default: ``*``
|
||||
* ``AXES_SENSITIVE_PARAMS``: Configures POST and GET parameter values to mask in login attempt logging.
|
||||
Default: ``['password',]``
|
||||
|
||||
The configuration option precedences for the access attempt monitoring are:
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from axes.helpers import (
|
|||
is_ip_address_in_whitelist,
|
||||
is_user_attempt_whitelisted,
|
||||
toggleable,
|
||||
cleanse_params,
|
||||
cleanse_parameters,
|
||||
)
|
||||
from axes.models import AccessAttempt
|
||||
from tests.base import AxesTestCase
|
||||
|
|
@ -687,28 +687,36 @@ class AxesLockoutTestCase(AxesTestCase):
|
|||
|
||||
class AxesCleanseParamsTestCase(AxesTestCase):
|
||||
def setUp(self):
|
||||
self.params = {
|
||||
self.parameters = {
|
||||
"username": "test_user",
|
||||
"password": "test_password",
|
||||
"other_sensitive_data": "sensitive",
|
||||
}
|
||||
|
||||
def test_cleanse_params(self):
|
||||
cleansed = cleanse_params(self.params)
|
||||
def test_cleanse_parameters(self):
|
||||
cleansed = cleanse_parameters(self.parameters)
|
||||
self.assertEqual("test_user", cleansed["username"])
|
||||
self.assertEqual("********************", cleansed["password"])
|
||||
self.assertEqual("sensitive", cleansed["other_sensitive_data"])
|
||||
|
||||
@override_settings(AXES_SENSITIVE_PARAMETERS=["other_sensitive_data"])
|
||||
def test_cleanse_params_override(self):
|
||||
cleansed = cleanse_params(self.params)
|
||||
def test_cleanse_parameters_override_sensitive(self):
|
||||
cleansed = cleanse_parameters(self.parameters)
|
||||
self.assertEqual("test_user", cleansed["username"])
|
||||
self.assertEqual("test_password", cleansed["password"])
|
||||
self.assertEqual("********************", cleansed["password"])
|
||||
self.assertEqual("********************", cleansed["other_sensitive_data"])
|
||||
|
||||
@override_settings(AXES_SENSITIVE_PARAMETERS=[])
|
||||
def test_cleanse_params_override_empty(self):
|
||||
cleansed = cleanse_params(self.params)
|
||||
@override_settings(AXES_SENSITIVE_PARAMETERS=["other_sensitive_data"])
|
||||
@override_settings(AXES_PASSWORD_FORM_FIELD="username")
|
||||
def test_cleanse_parameters_override_both(self):
|
||||
cleansed = cleanse_parameters(self.parameters)
|
||||
self.assertEqual("********************", cleansed["username"])
|
||||
self.assertEqual("********************", cleansed["password"])
|
||||
self.assertEqual("********************", cleansed["other_sensitive_data"])
|
||||
|
||||
@override_settings(AXES_PASSWORD_FORM_FIELD=None)
|
||||
def test_cleanse_parameters_override_empty(self):
|
||||
cleansed = cleanse_parameters(self.parameters)
|
||||
self.assertEqual("test_user", cleansed["username"])
|
||||
self.assertEqual("test_password", cleansed["password"])
|
||||
self.assertEqual("********************", cleansed["password"])
|
||||
self.assertEqual("sensitive", cleansed["other_sensitive_data"])
|
||||
|
|
|
|||
Loading…
Reference in a new issue