Loading django/views/debug.py +14 −3 Original line number Diff line number Diff line Loading @@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter): Replaces the values of variables marked as sensitive with stars (*********). """ func_name = tb_frame.f_code.co_name func = tb_frame.f_globals.get(func_name) sensitive_variables = getattr(func, 'sensitive_variables', []) # 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 cleansed = [] if self.is_active(request) and sensitive_variables: if sensitive_variables == '__ALL__': Loading django/views/decorators/debug.py +6 −6 Original line number Diff line number Diff line Loading @@ -26,13 +26,13 @@ def sensitive_variables(*variables): """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): def sensitive_variables_wrapper(*args, **kwargs): if variables: wrapper.sensitive_variables = variables sensitive_variables_wrapper.sensitive_variables = variables else: wrapper.sensitive_variables = '__ALL__' sensitive_variables_wrapper.sensitive_variables = '__ALL__' return func(*args, **kwargs) return wrapper return sensitive_variables_wrapper return decorator Loading Loading @@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters): """ def decorator(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): def sensitive_post_parameters_wrapper(request, *args, **kwargs): if parameters: request.sensitive_post_parameters = parameters else: request.sensitive_post_parameters = '__ALL__' return view(request, *args, **kwargs) return wrapper return sensitive_post_parameters_wrapper return decorator tests/regressiontests/views/tests/debug.py +63 −41 Original line number Diff line number Diff line Loading @@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory from django.test.utils import (setup_test_template_loader, restore_template_loaders) from django.core.urlresolvers import reverse from django.template import TemplateSyntaxError from django.views.debug import ExceptionReporter from django.core import mail from .. import BrokenException, except_args from ..views import (sensitive_view, non_sensitive_view, paranoid_view, custom_exception_reporter_filter_view) custom_exception_reporter_filter_view, sensitive_method_view) class DebugViewTests(TestCase): Loading Loading @@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object): 'hash-brown-key': 'hash-brown-value', 'bacon-key': 'bacon-value',} def verify_unsafe_response(self, view, check_for_vars=True): def verify_unsafe_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the response. """ Loading @@ -250,13 +250,14 @@ class ExceptionReportTestMixin(object): self.assertContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters are shown. self.assertContains(response, k, status_code=500) self.assertContains(response, v, status_code=500) def verify_safe_response(self, view, check_for_vars=True): def verify_safe_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the response. """ Loading @@ -269,7 +270,7 @@ class ExceptionReportTestMixin(object): # Sensitive variable's name is shown but not its value. self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertContains(response, k, status_code=500) Loading @@ -280,7 +281,8 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'sausage-value', status_code=500) self.assertNotContains(response, 'bacon-value', status_code=500) def verify_paranoid_response(self, view, check_for_vars=True): def verify_paranoid_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that no variables or POST parameters are displayed in the response. """ Loading @@ -292,14 +294,14 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertContains(response, k, status_code=500) # No POST parameters' values are shown. self.assertNotContains(response, v, status_code=500) def verify_unsafe_email(self, view): def verify_unsafe_email(self, view, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the email report. """ Loading @@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters are shown. self.assertIn(k, email.body) self.assertIn(v, email.body) def verify_safe_email(self, view): def verify_safe_email(self, view, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the email report. """ Loading @@ -334,6 +337,7 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertIn(k, email.body) Loading Loading @@ -425,6 +429,24 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.verify_unsafe_response(custom_exception_reporter_filter_view) self.verify_unsafe_email(custom_exception_reporter_filter_view) def test_sensitive_method(self): """ Ensure that the sensitive_variables decorator works with object methods. Refs #18379. """ with self.settings(DEBUG=True): self.verify_unsafe_response(sensitive_method_view, check_for_POST_params=False) self.verify_unsafe_email(sensitive_method_view, check_for_POST_params=False) with self.settings(DEBUG=False): self.verify_safe_response(sensitive_method_view, check_for_POST_params=False) self.verify_safe_email(sensitive_method_view, check_for_POST_params=False) class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): """ Loading tests/regressiontests/views/views.py +21 −2 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ from __future__ import absolute_import import sys from django import forms from django.core.exceptions import PermissionDenied from django.core.urlresolvers import get_resolver from django.http import HttpResponse, HttpResponseRedirect Loading @@ -14,7 +13,7 @@ from django.views.decorators.debug import (sensitive_post_parameters, from django.utils.log import getLogger from . import BrokenException, except_args from .models import Article def index_page(request): Loading Loading @@ -228,3 +227,23 @@ def custom_exception_reporter_filter_view(request): exc_info = sys.exc_info() send_log(request, exc_info) return technical_500_response(request, *exc_info) class Klass(object): @sensitive_variables('sauce') def method(self, 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']) sauce = ''.join(['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) def sensitive_method_view(request): return Klass().method(request) Loading
django/views/debug.py +14 −3 Original line number Diff line number Diff line Loading @@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter): Replaces the values of variables marked as sensitive with stars (*********). """ func_name = tb_frame.f_code.co_name func = tb_frame.f_globals.get(func_name) sensitive_variables = getattr(func, 'sensitive_variables', []) # 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 cleansed = [] if self.is_active(request) and sensitive_variables: if sensitive_variables == '__ALL__': Loading
django/views/decorators/debug.py +6 −6 Original line number Diff line number Diff line Loading @@ -26,13 +26,13 @@ def sensitive_variables(*variables): """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): def sensitive_variables_wrapper(*args, **kwargs): if variables: wrapper.sensitive_variables = variables sensitive_variables_wrapper.sensitive_variables = variables else: wrapper.sensitive_variables = '__ALL__' sensitive_variables_wrapper.sensitive_variables = '__ALL__' return func(*args, **kwargs) return wrapper return sensitive_variables_wrapper return decorator Loading Loading @@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters): """ def decorator(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): def sensitive_post_parameters_wrapper(request, *args, **kwargs): if parameters: request.sensitive_post_parameters = parameters else: request.sensitive_post_parameters = '__ALL__' return view(request, *args, **kwargs) return wrapper return sensitive_post_parameters_wrapper return decorator
tests/regressiontests/views/tests/debug.py +63 −41 Original line number Diff line number Diff line Loading @@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory from django.test.utils import (setup_test_template_loader, restore_template_loaders) from django.core.urlresolvers import reverse from django.template import TemplateSyntaxError from django.views.debug import ExceptionReporter from django.core import mail from .. import BrokenException, except_args from ..views import (sensitive_view, non_sensitive_view, paranoid_view, custom_exception_reporter_filter_view) custom_exception_reporter_filter_view, sensitive_method_view) class DebugViewTests(TestCase): Loading Loading @@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object): 'hash-brown-key': 'hash-brown-value', 'bacon-key': 'bacon-value',} def verify_unsafe_response(self, view, check_for_vars=True): def verify_unsafe_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the response. """ Loading @@ -250,13 +250,14 @@ class ExceptionReportTestMixin(object): self.assertContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters are shown. self.assertContains(response, k, status_code=500) self.assertContains(response, v, status_code=500) def verify_safe_response(self, view, check_for_vars=True): def verify_safe_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the response. """ Loading @@ -269,7 +270,7 @@ class ExceptionReportTestMixin(object): # Sensitive variable's name is shown but not its value. self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertContains(response, k, status_code=500) Loading @@ -280,7 +281,8 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'sausage-value', status_code=500) self.assertNotContains(response, 'bacon-value', status_code=500) def verify_paranoid_response(self, view, check_for_vars=True): def verify_paranoid_response(self, view, check_for_vars=True, check_for_POST_params=True): """ Asserts that no variables or POST parameters are displayed in the response. """ Loading @@ -292,14 +294,14 @@ class ExceptionReportTestMixin(object): self.assertNotContains(response, 'scrambled', status_code=500) self.assertContains(response, 'sauce', status_code=500) self.assertNotContains(response, 'worcestershire', status_code=500) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertContains(response, k, status_code=500) # No POST parameters' values are shown. self.assertNotContains(response, v, status_code=500) def verify_unsafe_email(self, view): def verify_unsafe_email(self, view, check_for_POST_params=True): """ Asserts that potentially sensitive info are displayed in the email report. """ Loading @@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters are shown. self.assertIn(k, email.body) self.assertIn(v, email.body) def verify_safe_email(self, view): def verify_safe_email(self, view, check_for_POST_params=True): """ Asserts that certain sensitive info are not displayed in the email report. """ Loading @@ -334,6 +337,7 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) if check_for_POST_params: for k, v in self.breakfast_data.items(): # All POST parameters' names are shown. self.assertIn(k, email.body) Loading Loading @@ -425,6 +429,24 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin): self.verify_unsafe_response(custom_exception_reporter_filter_view) self.verify_unsafe_email(custom_exception_reporter_filter_view) def test_sensitive_method(self): """ Ensure that the sensitive_variables decorator works with object methods. Refs #18379. """ with self.settings(DEBUG=True): self.verify_unsafe_response(sensitive_method_view, check_for_POST_params=False) self.verify_unsafe_email(sensitive_method_view, check_for_POST_params=False) with self.settings(DEBUG=False): self.verify_safe_response(sensitive_method_view, check_for_POST_params=False) self.verify_safe_email(sensitive_method_view, check_for_POST_params=False) class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin): """ Loading
tests/regressiontests/views/views.py +21 −2 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ from __future__ import absolute_import import sys from django import forms from django.core.exceptions import PermissionDenied from django.core.urlresolvers import get_resolver from django.http import HttpResponse, HttpResponseRedirect Loading @@ -14,7 +13,7 @@ from django.views.decorators.debug import (sensitive_post_parameters, from django.utils.log import getLogger from . import BrokenException, except_args from .models import Article def index_page(request): Loading Loading @@ -228,3 +227,23 @@ def custom_exception_reporter_filter_view(request): exc_info = sys.exc_info() send_log(request, exc_info) return technical_500_response(request, *exc_info) class Klass(object): @sensitive_variables('sauce') def method(self, 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']) sauce = ''.join(['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) def sensitive_method_view(request): return Klass().method(request)