[1.4.X] Fixed #18379 -- Made the sensitive_variables decorator work with object methods.

This commit is contained in:
Julien Phalip 2012-06-03 17:33:09 -07:00
parent 0f69a16785
commit 4d2fdd4185
4 changed files with 104 additions and 52 deletions

View File

@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
Replaces the values of variables marked as sensitive with Replaces the values of variables marked as sensitive with
stars (*********). stars (*********).
""" """
func_name = tb_frame.f_code.co_name # Loop through the frame's callers to see if the sensitive_variables
func = tb_frame.f_globals.get(func_name) # decorator was used.
sensitive_variables = getattr(func, 'sensitive_variables', []) 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 = [] cleansed = []
if self.is_active(request) and sensitive_variables: if self.is_active(request) and sensitive_variables:
if sensitive_variables == '__ALL__': if sensitive_variables == '__ALL__':

View File

@ -26,13 +26,13 @@ def sensitive_variables(*variables):
""" """
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def sensitive_variables_wrapper(*args, **kwargs):
if variables: if variables:
wrapper.sensitive_variables = variables sensitive_variables_wrapper.sensitive_variables = variables
else: else:
wrapper.sensitive_variables = '__ALL__' sensitive_variables_wrapper.sensitive_variables = '__ALL__'
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper return sensitive_variables_wrapper
return decorator return decorator
@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters):
""" """
def decorator(view): def decorator(view):
@functools.wraps(view) @functools.wraps(view)
def wrapper(request, *args, **kwargs): def sensitive_post_parameters_wrapper(request, *args, **kwargs):
if parameters: if parameters:
request.sensitive_post_parameters = parameters request.sensitive_post_parameters = parameters
else: else:
request.sensitive_post_parameters = '__ALL__' request.sensitive_post_parameters = '__ALL__'
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
return wrapper return sensitive_post_parameters_wrapper
return decorator return decorator

View File

@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory
from django.test.utils import (setup_test_template_loader, from django.test.utils import (setup_test_template_loader,
restore_template_loaders) restore_template_loaders)
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template import TemplateSyntaxError
from django.views.debug import ExceptionReporter from django.views.debug import ExceptionReporter
from django.core import mail from django.core import mail
from .. import BrokenException, except_args from .. import BrokenException, except_args
from ..views import (sensitive_view, non_sensitive_view, paranoid_view, 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): class DebugViewTests(TestCase):
@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object):
'hash-brown-key': 'hash-brown-value', 'hash-brown-key': 'hash-brown-value',
'bacon-key': 'bacon-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. 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, 'scrambled', status_code=500)
self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'sauce', status_code=500)
self.assertContains(response, 'worcestershire', 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(): def verify_safe_response(self, view, check_for_vars=True,
# All POST parameters are shown. check_for_POST_params=True):
self.assertContains(response, k, status_code=500)
self.assertContains(response, v, status_code=500)
def verify_safe_response(self, view, check_for_vars=True):
""" """
Asserts that certain sensitive info are not displayed in the response. 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. # Sensitive variable's name is shown but not its value.
self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'sauce', status_code=500)
self.assertNotContains(response, 'worcestershire', 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(): def verify_paranoid_response(self, view, check_for_vars=True,
# All POST parameters' names are shown. check_for_POST_params=True):
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):
""" """
Asserts that no variables or POST parameters are displayed in the response. 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.assertNotContains(response, 'scrambled', status_code=500)
self.assertContains(response, 'sauce', status_code=500) self.assertContains(response, 'sauce', status_code=500)
self.assertNotContains(response, 'worcestershire', 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(): def verify_unsafe_email(self, view, check_for_POST_params=True):
# 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):
""" """
Asserts that potentially sensitive info are displayed in the email report. 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('scrambled', email.body)
self.assertNotIn('sauce', email.body) self.assertNotIn('sauce', email.body)
self.assertNotIn('worcestershire', email.body) self.assertNotIn('worcestershire', email.body)
for k, v in self.breakfast_data.items(): if check_for_POST_params:
# All POST parameters are shown. for k, v in self.breakfast_data.items():
self.assertIn(k, email.body) # All POST parameters are shown.
self.assertIn(v, email.body) 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. 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('scrambled', email.body)
self.assertNotIn('sauce', email.body) self.assertNotIn('sauce', email.body)
self.assertNotIn('worcestershire', email.body) self.assertNotIn('worcestershire', email.body)
for k, v in self.breakfast_data.items(): if check_for_POST_params:
# All POST parameters' names are shown. for k, v in self.breakfast_data.items():
self.assertIn(k, email.body) # All POST parameters' names are shown.
# Non-sensitive POST parameters' values are shown. self.assertIn(k, email.body)
self.assertIn('baked-beans-value', email.body) # Non-sensitive POST parameters' values are shown.
self.assertIn('hash-brown-value', email.body) self.assertIn('baked-beans-value', email.body)
# Sensitive POST parameters' values are not shown. self.assertIn('hash-brown-value', email.body)
self.assertNotIn('sausage-value', email.body) # Sensitive POST parameters' values are not shown.
self.assertNotIn('bacon-value', email.body) self.assertNotIn('sausage-value', email.body)
self.assertNotIn('bacon-value', email.body)
def verify_paranoid_email(self, view): 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_response(custom_exception_reporter_filter_view)
self.verify_unsafe_email(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): class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
""" """

View File

@ -2,7 +2,6 @@ from __future__ import absolute_import
import sys import sys
from django import forms
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import get_resolver from django.core.urlresolvers import get_resolver
from django.http import HttpResponse, HttpResponseRedirect 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 django.utils.log import getLogger
from . import BrokenException, except_args from . import BrokenException, except_args
from .models import Article
def index_page(request): def index_page(request):
@ -228,3 +227,23 @@ def custom_exception_reporter_filter_view(request):
exc_info = sys.exc_info() exc_info = sys.exc_info()
send_log(request, exc_info) send_log(request, exc_info)
return technical_500_response(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)