mirror of https://github.com/django/django.git
Refs #31949 -- Enabled @sensitive_variables to work with async functions.
This commit is contained in:
parent
6087bc4e15
commit
23cbed2187
|
@ -1,5 +1,7 @@
|
|||
from functools import wraps
|
||||
|
||||
from asgiref.sync import iscoroutinefunction
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
|
@ -33,13 +35,25 @@ def sensitive_variables(*variables):
|
|||
)
|
||||
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def sensitive_variables_wrapper(*func_args, **func_kwargs):
|
||||
if variables:
|
||||
sensitive_variables_wrapper.sensitive_variables = variables
|
||||
else:
|
||||
sensitive_variables_wrapper.sensitive_variables = "__ALL__"
|
||||
return func(*func_args, **func_kwargs)
|
||||
if iscoroutinefunction(func):
|
||||
|
||||
@wraps(func)
|
||||
async def sensitive_variables_wrapper(*func_args, **func_kwargs):
|
||||
if variables:
|
||||
sensitive_variables_wrapper.sensitive_variables = variables
|
||||
else:
|
||||
sensitive_variables_wrapper.sensitive_variables = "__ALL__"
|
||||
return await func(*func_args, **func_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
@wraps(func)
|
||||
def sensitive_variables_wrapper(*func_args, **func_kwargs):
|
||||
if variables:
|
||||
sensitive_variables_wrapper.sensitive_variables = variables
|
||||
else:
|
||||
sensitive_variables_wrapper.sensitive_variables = "__ALL__"
|
||||
return func(*func_args, **func_kwargs)
|
||||
|
||||
return sensitive_variables_wrapper
|
||||
|
||||
|
@ -77,19 +91,37 @@ def sensitive_post_parameters(*parameters):
|
|||
)
|
||||
|
||||
def decorator(view):
|
||||
@wraps(view)
|
||||
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
|
||||
if not isinstance(request, HttpRequest):
|
||||
raise TypeError(
|
||||
"sensitive_post_parameters didn't receive an HttpRequest "
|
||||
"object. If you are decorating a classmethod, make sure "
|
||||
"to use @method_decorator."
|
||||
)
|
||||
if parameters:
|
||||
request.sensitive_post_parameters = parameters
|
||||
else:
|
||||
request.sensitive_post_parameters = "__ALL__"
|
||||
return view(request, *args, **kwargs)
|
||||
if iscoroutinefunction(view):
|
||||
|
||||
@wraps(view)
|
||||
async def sensitive_post_parameters_wrapper(request, *args, **kwargs):
|
||||
if not isinstance(request, HttpRequest):
|
||||
raise TypeError(
|
||||
"sensitive_post_parameters didn't receive an HttpRequest "
|
||||
"object. If you are decorating a classmethod, make sure "
|
||||
"to use @method_decorator."
|
||||
)
|
||||
if parameters:
|
||||
request.sensitive_post_parameters = parameters
|
||||
else:
|
||||
request.sensitive_post_parameters = "__ALL__"
|
||||
return await view(request, *args, **kwargs)
|
||||
|
||||
else:
|
||||
|
||||
@wraps(view)
|
||||
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
|
||||
if not isinstance(request, HttpRequest):
|
||||
raise TypeError(
|
||||
"sensitive_post_parameters didn't receive an HttpRequest "
|
||||
"object. If you are decorating a classmethod, make sure "
|
||||
"to use @method_decorator."
|
||||
)
|
||||
if parameters:
|
||||
request.sensitive_post_parameters = parameters
|
||||
else:
|
||||
request.sensitive_post_parameters = "__ALL__"
|
||||
return view(request, *args, **kwargs)
|
||||
|
||||
return sensitive_post_parameters_wrapper
|
||||
|
||||
|
|
|
@ -194,6 +194,10 @@ filtered out of error reports in a production environment (that is, where
|
|||
def process_info(user):
|
||||
...
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
:func:`sensitive_variables` can now be used to wrap ``async`` functions.
|
||||
|
||||
.. function:: sensitive_post_parameters(*parameters)
|
||||
|
||||
If one of your views receives an :class:`~django.http.HttpRequest` object
|
||||
|
@ -234,6 +238,10 @@ filtered out of error reports in a production environment (that is, where
|
|||
``user_change_password`` in the ``auth`` admin) to prevent the leaking of
|
||||
sensitive information such as user passwords.
|
||||
|
||||
.. versionchanged:: 5.0
|
||||
|
||||
:func:`sensitive_post_parameters` can now be used to wrap ``async`` functions.
|
||||
|
||||
.. _custom-error-reports:
|
||||
|
||||
Custom error reports
|
||||
|
|
|
@ -152,7 +152,9 @@ Email
|
|||
Error Reporting
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
* ...
|
||||
* :func:`~django.views.decorators.debug.sensitive_variables` and
|
||||
:func:`~django.views.decorators.debug.sensitive_post_parameters` can now be
|
||||
used with asynchronous functions.
|
||||
|
||||
File Storage
|
||||
~~~~~~~~~~~~
|
||||
|
|
|
@ -9,6 +9,8 @@ from io import StringIO
|
|||
from pathlib import Path
|
||||
from unittest import mock, skipIf, skipUnless
|
||||
|
||||
from asgiref.sync import async_to_sync, iscoroutinefunction
|
||||
|
||||
from django.core import mail
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db import DatabaseError, connection
|
||||
|
@ -39,6 +41,7 @@ from django.views.debug import (
|
|||
from django.views.decorators.debug import sensitive_post_parameters, sensitive_variables
|
||||
|
||||
from ..views import (
|
||||
async_sensitive_view,
|
||||
custom_exception_reporter_filter_view,
|
||||
index_page,
|
||||
multivalue_dict_key_error,
|
||||
|
@ -1351,7 +1354,10 @@ class ExceptionReportTestMixin:
|
|||
Asserts that potentially sensitive info are displayed in the response.
|
||||
"""
|
||||
request = self.rf.post("/some_url/", self.breakfast_data)
|
||||
response = view(request)
|
||||
if iscoroutinefunction(view):
|
||||
response = async_to_sync(view)(request)
|
||||
else:
|
||||
response = view(request)
|
||||
if check_for_vars:
|
||||
# All variables are shown.
|
||||
self.assertContains(response, "cooked_eggs", status_code=500)
|
||||
|
@ -1371,7 +1377,10 @@ class ExceptionReportTestMixin:
|
|||
Asserts that certain sensitive info are not displayed in the response.
|
||||
"""
|
||||
request = self.rf.post("/some_url/", self.breakfast_data)
|
||||
response = view(request)
|
||||
if iscoroutinefunction(view):
|
||||
response = async_to_sync(view)(request)
|
||||
else:
|
||||
response = view(request)
|
||||
if check_for_vars:
|
||||
# Non-sensitive variable's name and value are shown.
|
||||
self.assertContains(response, "cooked_eggs", status_code=500)
|
||||
|
@ -1418,7 +1427,10 @@ class ExceptionReportTestMixin:
|
|||
with self.settings(ADMINS=[("Admin", "admin@fattie-breakie.com")]):
|
||||
mail.outbox = [] # Empty outbox
|
||||
request = self.rf.post("/some_url/", self.breakfast_data)
|
||||
view(request)
|
||||
if iscoroutinefunction(view):
|
||||
async_to_sync(view)(request)
|
||||
else:
|
||||
view(request)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
email = mail.outbox[0]
|
||||
|
||||
|
@ -1451,7 +1463,10 @@ class ExceptionReportTestMixin:
|
|||
with self.settings(ADMINS=[("Admin", "admin@fattie-breakie.com")]):
|
||||
mail.outbox = [] # Empty outbox
|
||||
request = self.rf.post("/some_url/", self.breakfast_data)
|
||||
view(request)
|
||||
if iscoroutinefunction(view):
|
||||
async_to_sync(view)(request)
|
||||
else:
|
||||
view(request)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
email = mail.outbox[0]
|
||||
|
||||
|
@ -1543,6 +1558,15 @@ class ExceptionReporterFilterTests(
|
|||
self.verify_safe_response(sensitive_view)
|
||||
self.verify_safe_email(sensitive_view)
|
||||
|
||||
def test_async_sensitive_request(self):
|
||||
with self.settings(DEBUG=True):
|
||||
self.verify_unsafe_response(async_sensitive_view)
|
||||
self.verify_unsafe_email(async_sensitive_view)
|
||||
|
||||
with self.settings(DEBUG=False):
|
||||
self.verify_safe_response(async_sensitive_view)
|
||||
self.verify_safe_email(async_sensitive_view)
|
||||
|
||||
def test_paranoid_request(self):
|
||||
"""
|
||||
No POST parameters and frame variables can be seen in the
|
||||
|
@ -1890,6 +1914,17 @@ class NonHTMLResponseExceptionReporterFilter(
|
|||
with self.settings(DEBUG=False):
|
||||
self.verify_safe_response(sensitive_view, check_for_vars=False)
|
||||
|
||||
def test_async_sensitive_request(self):
|
||||
"""
|
||||
Sensitive POST parameters cannot be seen in the default
|
||||
error reports for sensitive requests.
|
||||
"""
|
||||
with self.settings(DEBUG=True):
|
||||
self.verify_unsafe_response(async_sensitive_view, check_for_vars=False)
|
||||
|
||||
with self.settings(DEBUG=False):
|
||||
self.verify_safe_response(async_sensitive_view, check_for_vars=False)
|
||||
|
||||
def test_paranoid_request(self):
|
||||
"""
|
||||
No POST parameters can be seen in the default error reports
|
||||
|
|
|
@ -178,6 +178,24 @@ def sensitive_view(request):
|
|||
return technical_500_response(request, *exc_info)
|
||||
|
||||
|
||||
@sensitive_variables("sauce")
|
||||
@sensitive_post_parameters("bacon-key", "sausage-key")
|
||||
async def async_sensitive_view(request):
|
||||
# Do not just use plain strings for the variables' values in the code
|
||||
# so that the tests don't return false positives when the function's source
|
||||
# is displayed in the exception report.
|
||||
cooked_eggs = "".join(["s", "c", "r", "a", "m", "b", "l", "e", "d"]) # NOQA
|
||||
sauce = "".join( # NOQA
|
||||
["w", "o", "r", "c", "e", "s", "t", "e", "r", "s", "h", "i", "r", "e"]
|
||||
)
|
||||
try:
|
||||
raise Exception
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
send_log(request, exc_info)
|
||||
return technical_500_response(request, *exc_info)
|
||||
|
||||
|
||||
@sensitive_variables()
|
||||
@sensitive_post_parameters()
|
||||
def paranoid_view(request):
|
||||
|
|
Loading…
Reference in New Issue