Skip to content

Commit 4d2fdd4

Browse files
committed
[1.4.X] Fixed django#18379 -- Made the sensitive_variables decorator work with object methods.
1 parent 0f69a16 commit 4d2fdd4

File tree

4 files changed

+104
-52
lines changed

4 files changed

+104
-52
lines changed

django/views/debug.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,20 @@ def get_traceback_frame_variables(self, request, tb_frame):
155155
Replaces the values of variables marked as sensitive with
156156
stars (*********).
157157
"""
158-
func_name = tb_frame.f_code.co_name
159-
func = tb_frame.f_globals.get(func_name)
160-
sensitive_variables = getattr(func, 'sensitive_variables', [])
158+
# Loop through the frame's callers to see if the sensitive_variables
159+
# decorator was used.
160+
current_frame = tb_frame.f_back
161+
sensitive_variables = None
162+
while current_frame is not None:
163+
if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
164+
and 'sensitive_variables_wrapper' in current_frame.f_locals):
165+
# The sensitive_variables decorator was used, so we take note
166+
# of the sensitive variables' names.
167+
wrapper = current_frame.f_locals['sensitive_variables_wrapper']
168+
sensitive_variables = getattr(wrapper, 'sensitive_variables', None)
169+
break
170+
current_frame = current_frame.f_back
171+
161172
cleansed = []
162173
if self.is_active(request) and sensitive_variables:
163174
if sensitive_variables == '__ALL__':

django/views/decorators/debug.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ def my_function()
2626
"""
2727
def decorator(func):
2828
@functools.wraps(func)
29-
def wrapper(*args, **kwargs):
29+
def sensitive_variables_wrapper(*args, **kwargs):
3030
if variables:
31-
wrapper.sensitive_variables = variables
31+
sensitive_variables_wrapper.sensitive_variables = variables
3232
else:
33-
wrapper.sensitive_variables = '__ALL__'
33+
sensitive_variables_wrapper.sensitive_variables = '__ALL__'
3434
return func(*args, **kwargs)
35-
return wrapper
35+
return sensitive_variables_wrapper
3636
return decorator
3737

3838

@@ -61,11 +61,11 @@ def my_view(request)
6161
"""
6262
def decorator(view):
6363
@functools.wraps(view)
64-
def wrapper(request, *args, **kwargs):
64+
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
6565
if parameters:
6666
request.sensitive_post_parameters = parameters
6767
else:
6868
request.sensitive_post_parameters = '__ALL__'
6969
return view(request, *args, **kwargs)
70-
return wrapper
70+
return sensitive_post_parameters_wrapper
7171
return decorator

tests/regressiontests/views/tests/debug.py

Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
from django.test.utils import (setup_test_template_loader,
1111
restore_template_loaders)
1212
from django.core.urlresolvers import reverse
13-
from django.template import TemplateSyntaxError
1413
from django.views.debug import ExceptionReporter
1514
from django.core import mail
1615

1716
from .. import BrokenException, except_args
1817
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
19-
custom_exception_reporter_filter_view)
18+
custom_exception_reporter_filter_view, sensitive_method_view)
2019

2120

2221
class DebugViewTests(TestCase):
@@ -238,7 +237,8 @@ class ExceptionReportTestMixin(object):
238237
'hash-brown-key': 'hash-brown-value',
239238
'bacon-key': 'bacon-value',}
240239

241-
def verify_unsafe_response(self, view, check_for_vars=True):
240+
def verify_unsafe_response(self, view, check_for_vars=True,
241+
check_for_POST_params=True):
242242
"""
243243
Asserts that potentially sensitive info are displayed in the response.
244244
"""
@@ -250,13 +250,14 @@ def verify_unsafe_response(self, view, check_for_vars=True):
250250
self.assertContains(response, 'scrambled', status_code=500)
251251
self.assertContains(response, 'sauce', status_code=500)
252252
self.assertContains(response, 'worcestershire', status_code=500)
253+
if check_for_POST_params:
254+
for k, v in self.breakfast_data.items():
255+
# All POST parameters are shown.
256+
self.assertContains(response, k, status_code=500)
257+
self.assertContains(response, v, status_code=500)
253258

254-
for k, v in self.breakfast_data.items():
255-
# All POST parameters are shown.
256-
self.assertContains(response, k, status_code=500)
257-
self.assertContains(response, v, status_code=500)
258-
259-
def verify_safe_response(self, view, check_for_vars=True):
259+
def verify_safe_response(self, view, check_for_vars=True,
260+
check_for_POST_params=True):
260261
"""
261262
Asserts that certain sensitive info are not displayed in the response.
262263
"""
@@ -269,18 +270,19 @@ def verify_safe_response(self, view, check_for_vars=True):
269270
# Sensitive variable's name is shown but not its value.
270271
self.assertContains(response, 'sauce', status_code=500)
271272
self.assertNotContains(response, 'worcestershire', status_code=500)
273+
if check_for_POST_params:
274+
for k, v in self.breakfast_data.items():
275+
# All POST parameters' names are shown.
276+
self.assertContains(response, k, status_code=500)
277+
# Non-sensitive POST parameters' values are shown.
278+
self.assertContains(response, 'baked-beans-value', status_code=500)
279+
self.assertContains(response, 'hash-brown-value', status_code=500)
280+
# Sensitive POST parameters' values are not shown.
281+
self.assertNotContains(response, 'sausage-value', status_code=500)
282+
self.assertNotContains(response, 'bacon-value', status_code=500)
272283

273-
for k, v in self.breakfast_data.items():
274-
# All POST parameters' names are shown.
275-
self.assertContains(response, k, status_code=500)
276-
# Non-sensitive POST parameters' values are shown.
277-
self.assertContains(response, 'baked-beans-value', status_code=500)
278-
self.assertContains(response, 'hash-brown-value', status_code=500)
279-
# Sensitive POST parameters' values are not shown.
280-
self.assertNotContains(response, 'sausage-value', status_code=500)
281-
self.assertNotContains(response, 'bacon-value', status_code=500)
282-
283-
def verify_paranoid_response(self, view, check_for_vars=True):
284+
def verify_paranoid_response(self, view, check_for_vars=True,
285+
check_for_POST_params=True):
284286
"""
285287
Asserts that no variables or POST parameters are displayed in the response.
286288
"""
@@ -292,14 +294,14 @@ def verify_paranoid_response(self, view, check_for_vars=True):
292294
self.assertNotContains(response, 'scrambled', status_code=500)
293295
self.assertContains(response, 'sauce', status_code=500)
294296
self.assertNotContains(response, 'worcestershire', status_code=500)
297+
if check_for_POST_params:
298+
for k, v in self.breakfast_data.items():
299+
# All POST parameters' names are shown.
300+
self.assertContains(response, k, status_code=500)
301+
# No POST parameters' values are shown.
302+
self.assertNotContains(response, v, status_code=500)
295303

296-
for k, v in self.breakfast_data.items():
297-
# All POST parameters' names are shown.
298-
self.assertContains(response, k, status_code=500)
299-
# No POST parameters' values are shown.
300-
self.assertNotContains(response, v, status_code=500)
301-
302-
def verify_unsafe_email(self, view):
304+
def verify_unsafe_email(self, view, check_for_POST_params=True):
303305
"""
304306
Asserts that potentially sensitive info are displayed in the email report.
305307
"""
@@ -314,12 +316,13 @@ def verify_unsafe_email(self, view):
314316
self.assertNotIn('scrambled', email.body)
315317
self.assertNotIn('sauce', email.body)
316318
self.assertNotIn('worcestershire', email.body)
317-
for k, v in self.breakfast_data.items():
318-
# All POST parameters are shown.
319-
self.assertIn(k, email.body)
320-
self.assertIn(v, email.body)
319+
if check_for_POST_params:
320+
for k, v in self.breakfast_data.items():
321+
# All POST parameters are shown.
322+
self.assertIn(k, email.body)
323+
self.assertIn(v, email.body)
321324

322-
def verify_safe_email(self, view):
325+
def verify_safe_email(self, view, check_for_POST_params=True):
323326
"""
324327
Asserts that certain sensitive info are not displayed in the email report.
325328
"""
@@ -334,15 +337,16 @@ def verify_safe_email(self, view):
334337
self.assertNotIn('scrambled', email.body)
335338
self.assertNotIn('sauce', email.body)
336339
self.assertNotIn('worcestershire', email.body)
337-
for k, v in self.breakfast_data.items():
338-
# All POST parameters' names are shown.
339-
self.assertIn(k, email.body)
340-
# Non-sensitive POST parameters' values are shown.
341-
self.assertIn('baked-beans-value', email.body)
342-
self.assertIn('hash-brown-value', email.body)
343-
# Sensitive POST parameters' values are not shown.
344-
self.assertNotIn('sausage-value', email.body)
345-
self.assertNotIn('bacon-value', email.body)
340+
if check_for_POST_params:
341+
for k, v in self.breakfast_data.items():
342+
# All POST parameters' names are shown.
343+
self.assertIn(k, email.body)
344+
# Non-sensitive POST parameters' values are shown.
345+
self.assertIn('baked-beans-value', email.body)
346+
self.assertIn('hash-brown-value', email.body)
347+
# Sensitive POST parameters' values are not shown.
348+
self.assertNotIn('sausage-value', email.body)
349+
self.assertNotIn('bacon-value', email.body)
346350

347351
def verify_paranoid_email(self, view):
348352
"""
@@ -425,6 +429,24 @@ def test_custom_exception_reporter_filter(self):
425429
self.verify_unsafe_response(custom_exception_reporter_filter_view)
426430
self.verify_unsafe_email(custom_exception_reporter_filter_view)
427431

432+
def test_sensitive_method(self):
433+
"""
434+
Ensure that the sensitive_variables decorator works with object
435+
methods.
436+
Refs #18379.
437+
"""
438+
with self.settings(DEBUG=True):
439+
self.verify_unsafe_response(sensitive_method_view,
440+
check_for_POST_params=False)
441+
self.verify_unsafe_email(sensitive_method_view,
442+
check_for_POST_params=False)
443+
444+
with self.settings(DEBUG=False):
445+
self.verify_safe_response(sensitive_method_view,
446+
check_for_POST_params=False)
447+
self.verify_safe_email(sensitive_method_view,
448+
check_for_POST_params=False)
449+
428450

429451
class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
430452
"""

tests/regressiontests/views/views.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import sys
44

5-
from django import forms
65
from django.core.exceptions import PermissionDenied
76
from django.core.urlresolvers import get_resolver
87
from django.http import HttpResponse, HttpResponseRedirect
@@ -14,7 +13,7 @@
1413
from django.utils.log import getLogger
1514

1615
from . import BrokenException, except_args
17-
from .models import Article
16+
1817

1918

2019
def index_page(request):
@@ -228,3 +227,23 @@ def custom_exception_reporter_filter_view(request):
228227
exc_info = sys.exc_info()
229228
send_log(request, exc_info)
230229
return technical_500_response(request, *exc_info)
230+
231+
232+
class Klass(object):
233+
234+
@sensitive_variables('sauce')
235+
def method(self, request):
236+
# Do not just use plain strings for the variables' values in the code
237+
# so that the tests don't return false positives when the function's
238+
# source is displayed in the exception report.
239+
cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
240+
sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
241+
try:
242+
raise Exception
243+
except Exception:
244+
exc_info = sys.exc_info()
245+
send_log(request, exc_info)
246+
return technical_500_response(request, *exc_info)
247+
248+
def sensitive_method_view(request):
249+
return Klass().method(request)

0 commit comments

Comments
 (0)