diff options
| author | Jon Janzen <jon@jonjanzen.com> | 2023-05-06 20:20:00 -0700 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-06-23 13:29:40 +0200 |
| commit | 38e391e95fe5258bc6d2467332dc9cd44ce6ba52 (patch) | |
| tree | 68f0c84d7968703f213ea3492cdc985ccc993e48 /django | |
| parent | f8092ee9adafaa052172712349a32bd5889b5ccb (diff) | |
Refs #31949 -- Made @sensitive_variables/sensitive_post_parameters decorators to work with async functions.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Diffstat (limited to 'django')
| -rw-r--r-- | django/views/debug.py | 46 | ||||
| -rw-r--r-- | django/views/decorators/debug.py | 88 |
2 files changed, 102 insertions, 32 deletions
diff --git a/django/views/debug.py b/django/views/debug.py index 53b4125716..c1265bfe6b 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -1,4 +1,5 @@ import functools +import inspect import itertools import re import sys @@ -17,6 +18,7 @@ from django.utils.encoding import force_str from django.utils.module_loading import import_string from django.utils.regex_helper import _lazy_re_compile from django.utils.version import PY311, get_docs_version +from django.views.decorators.debug import coroutine_functions_to_sensitive_variables # Minimal Django templates engine to render the error templates # regardless of the project's TEMPLATES setting. Templates are @@ -239,21 +241,37 @@ class SafeExceptionReporterFilter: Replace the values of variables marked as sensitive with stars (*********). """ - # Loop through the frame's callers to see if the sensitive_variables - # decorator was used. - current_frame = tb_frame.f_back sensitive_variables = None - while current_frame is not None: - if ( - current_frame.f_code.co_name == "sensitive_variables_wrapper" - and "sensitive_variables_wrapper" in current_frame.f_locals - ): - # The sensitive_variables decorator was used, so we take note - # of the sensitive variables' names. - wrapper = current_frame.f_locals["sensitive_variables_wrapper"] - sensitive_variables = getattr(wrapper, "sensitive_variables", None) - break - current_frame = current_frame.f_back + + # Coroutines don't have a proper `f_back` so they need to be inspected + # separately. Handle this by stashing the registered sensitive + # variables in a global dict indexed by `hash(file_path:line_number)`. + if ( + tb_frame.f_code.co_flags & inspect.CO_COROUTINE != 0 + and tb_frame.f_code.co_name != "sensitive_variables_wrapper" + ): + key = hash( + f"{tb_frame.f_code.co_filename}:{tb_frame.f_code.co_firstlineno}" + ) + sensitive_variables = coroutine_functions_to_sensitive_variables.get( + key, None + ) + + if sensitive_variables is None: + # Loop through the frame's callers to see if the + # sensitive_variables decorator was used. + current_frame = tb_frame + while current_frame is not None: + if ( + current_frame.f_code.co_name == "sensitive_variables_wrapper" + and "sensitive_variables_wrapper" in current_frame.f_locals + ): + # The sensitive_variables decorator was used, so take note + # of the sensitive variables' names. + wrapper = current_frame.f_locals["sensitive_variables_wrapper"] + sensitive_variables = getattr(wrapper, "sensitive_variables", None) + break + current_frame = current_frame.f_back cleansed = {} if self.is_active(request) and sensitive_variables: diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index 5578a4995f..2d5d1ebd83 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -1,7 +1,12 @@ +import inspect from functools import wraps +from asgiref.sync import iscoroutinefunction + from django.http import HttpRequest +coroutine_functions_to_sensitive_variables = {} + def sensitive_variables(*variables): """ @@ -33,13 +38,42 @@ def sensitive_variables(*variables): ) def decorator(func): - @wraps(func) - def sensitive_variables_wrapper(*func_args, **func_kwargs): + if iscoroutinefunction(func): + + @wraps(func) + async def sensitive_variables_wrapper(*func_args, **func_kwargs): + return await func(*func_args, **func_kwargs) + + wrapped_func = func + while getattr(wrapped_func, "__wrapped__", None) is not None: + wrapped_func = wrapped_func.__wrapped__ + + try: + file_path = inspect.getfile(wrapped_func) + _, first_file_line = inspect.getsourcelines(wrapped_func) + except TypeError: # Raises for builtins or native functions. + raise ValueError( + f"{func.__name__} cannot safely be wrapped by " + "@sensitive_variables, make it either non-async or defined in a " + "Python file (not a builtin or from a native extension)." + ) + else: + key = hash(f"{file_path}:{first_file_line}") + if variables: - sensitive_variables_wrapper.sensitive_variables = variables + coroutine_functions_to_sensitive_variables[key] = variables else: - sensitive_variables_wrapper.sensitive_variables = "__ALL__" - return func(*func_args, **func_kwargs) + coroutine_functions_to_sensitive_variables[key] = "__ALL__" + + 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 +111,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 |
