Fixed #17138 -- 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 f29234167a
commit f699641161
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
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__':

View File

@ -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

View File

@ -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):
"""

View File

@ -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)