From f699641161a4ec8b6cbee938fd3a4379e7889ff2 Mon Sep 17 00:00:00 2001 From: Julien Phalip Date: Sun, 3 Jun 2012 17:33:09 -0700 Subject: [PATCH] Fixed #17138 -- Made the sensitive_variables decorator work with object methods. --- django/views/debug.py | 17 +++- django/views/decorators/debug.py | 12 +-- tests/regressiontests/views/tests/debug.py | 104 +++++++++++++-------- tests/regressiontests/views/views.py | 23 ++++- 4 files changed, 104 insertions(+), 52 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index 7bdf0d25ee6..d95cd620175 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -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__': diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index d04967ef09e..5c222963d37 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -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 @@ -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 diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index c8358d334f6..e8a7d49e79b 100644 --- a/tests/regressiontests/views/tests/debug.py +++ b/tests/regressiontests/views/tests/debug.py @@ -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): @@ -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. """ @@ -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) - 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. """ @@ -269,18 +270,19 @@ 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) + # Non-sensitive POST parameters' values are shown. + self.assertContains(response, 'baked-beans-value', status_code=500) + self.assertContains(response, 'hash-brown-value', status_code=500) + # Sensitive POST parameters' values are not shown. + self.assertNotContains(response, 'sausage-value', status_code=500) + self.assertNotContains(response, 'bacon-value', status_code=500) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertContains(response, k, status_code=500) - # Non-sensitive POST parameters' values are shown. - self.assertContains(response, 'baked-beans-value', status_code=500) - self.assertContains(response, 'hash-brown-value', status_code=500) - # Sensitive POST parameters' values are not shown. - 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. """ @@ -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) - 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. """ @@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters are shown. - self.assertIn(k, email.body) - self.assertIn(v, 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. """ @@ -334,15 +337,16 @@ class ExceptionReportTestMixin(object): self.assertNotIn('scrambled', email.body) self.assertNotIn('sauce', email.body) self.assertNotIn('worcestershire', email.body) - for k, v in self.breakfast_data.items(): - # All POST parameters' names are shown. - self.assertIn(k, email.body) - # Non-sensitive POST parameters' values are shown. - self.assertIn('baked-beans-value', email.body) - self.assertIn('hash-brown-value', email.body) - # Sensitive POST parameters' values are not shown. - self.assertNotIn('sausage-value', email.body) - self.assertNotIn('bacon-value', 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) + # Non-sensitive POST parameters' values are shown. + self.assertIn('baked-beans-value', email.body) + self.assertIn('hash-brown-value', email.body) + # Sensitive POST parameters' values are not shown. + self.assertNotIn('sausage-value', email.body) + self.assertNotIn('bacon-value', email.body) def verify_paranoid_email(self, view): """ @@ -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): """ diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 8e530cd2d8b..2836d1bdde6 100644 --- a/tests/regressiontests/views/views.py +++ b/tests/regressiontests/views/views.py @@ -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 @@ -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): @@ -209,3 +208,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) \ No newline at end of file