Ticket #14614: 14614.exception-reporter-filter.diff
File 14614.exception-reporter-filter.diff, 30.9 KB (added by , 13 years ago) |
---|
-
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 288dc9a..1300e86 100644
a b LOGGING = { 537 537 } 538 538 } 539 539 540 # Default exception reporter filter class used in case none has been 541 # specifically assigned to the HttpRequest instance. 542 DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter' 543 540 544 ########### 541 545 # TESTING # 542 546 ########### -
django/contrib/auth/admin.py
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index 7d855d8..dc04e7c 100644
a b from django.utils.html import escape 12 12 from django.utils.decorators import method_decorator 13 13 from django.utils.translation import ugettext, ugettext_lazy as _ 14 14 from django.views.decorators.csrf import csrf_protect 15 from django.views.decorators.security import sensitive_post_parameters 15 16 16 17 csrf_protect_m = method_decorator(csrf_protect) 17 18 … … class UserAdmin(admin.ModelAdmin): 78 79 (r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password)) 79 80 ) + super(UserAdmin, self).get_urls() 80 81 82 @sensitive_post_parameters 81 83 @csrf_protect_m 82 84 @transaction.commit_on_success 83 85 def add_view(self, request, form_url='', extra_context=None): … … class UserAdmin(admin.ModelAdmin): 102 104 extra_context.update(defaults) 103 105 return super(UserAdmin, self).add_view(request, form_url, extra_context) 104 106 107 @sensitive_post_parameters 105 108 def user_change_password(self, request, id): 106 109 if not self.has_change_permission(request): 107 110 raise PermissionDenied -
django/contrib/auth/views.py
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index cfb2659..75c4a13 100644
a b from django.http import HttpResponseRedirect, QueryDict 6 6 from django.template.response import TemplateResponse 7 7 from django.utils.http import base36_to_int 8 8 from django.utils.translation import ugettext as _ 9 from django.views.decorators.security import sensitive_post_parameters 9 10 from django.views.decorators.cache import never_cache 10 11 from django.views.decorators.csrf import csrf_protect 11 12 … … from django.contrib.auth.models import User 17 18 from django.contrib.auth.tokens import default_token_generator 18 19 from django.contrib.sites.models import get_current_site 19 20 20 21 @sensitive_post_parameters 21 22 @csrf_protect 22 23 @never_cache 23 24 def login(request, template_name='registration/login.html', … … def password_reset_done(request, 175 176 current_app=current_app) 176 177 177 178 # Doesn't need csrf_protect since no-one can guess the URL 179 @sensitive_post_parameters 178 180 @never_cache 179 181 def password_reset_confirm(request, uidb36=None, token=None, 180 182 template_name='registration/password_reset_confirm.html', … … def password_reset_complete(request, 227 229 return TemplateResponse(request, template_name, context, 228 230 current_app=current_app) 229 231 232 @sensitive_post_parameters 230 233 @csrf_protect 231 234 @login_required 232 235 def password_change(request, -
django/core/handlers/base.py
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index d653860..7cae42e 100644
a b class BaseHandler(object): 206 206 exc_info=exc_info, 207 207 extra={ 208 208 'status_code': 500, 209 'request': request209 'request': request 210 210 } 211 211 ) 212 212 -
django/utils/log.py
diff --git a/django/utils/log.py b/django/utils/log.py index 93e38d1..738606b 100644
a b 1 1 import logging 2 2 import sys 3 import traceback 4 5 from django.conf import settings 3 6 from django.core import mail 7 from django.views.debug import ExceptionReporter, get_exception_reporter_filter 4 8 5 9 # Make sure a NullHandler is available 6 10 # This was added in Python 2.7/3.2 … … class AdminEmailHandler(logging.Handler): 35 39 """An exception log handler that emails log entries to site admins. 36 40 37 41 If the request is passed as the first argument to the log record, 38 request data will be provided in the 42 request data will be provided in the email report. 39 43 """ 40 44 def emit(self, record): 41 import traceback42 from django.conf import settings43 from django.views.debug import ExceptionReporter44 45 45 try: 46 46 request = record.request 47 47 subject = '%s (%s IP): %s' % ( … … class AdminEmailHandler(logging.Handler): 49 49 (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), 50 50 record.msg 51 51 ) 52 request_repr = repr(request) 52 filter = get_exception_reporter_filter(request) 53 request_repr = filter.get_request_repr() 53 54 except: 54 55 subject = '%s: %s' % ( 55 56 record.levelname, 56 57 record.msg 57 58 ) 58 59 59 request = None 60 request_repr = "Request repr() unavailable "61 60 request_repr = "Request repr() unavailable." 61 62 62 if record.exc_info: 63 63 exc_info = record.exc_info 64 64 stack_trace = '\n'.join(traceback.format_exception(*record.exc_info)) -
django/views/debug.py
diff --git a/django/views/debug.py b/django/views/debug.py index 67f25b3..1ea7f52 100644
a b import os 3 3 import re 4 4 import sys 5 5 import types 6 from pprint import pformat 6 7 7 8 from django.conf import settings 8 9 from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound … … from django.utils.encoding import smart_unicode, smart_str 15 16 16 17 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE') 17 18 19 CLEANSED_SUBSTITUTE = u'********************' 20 18 21 def linebreak_iter(template_source): 19 22 yield 0 20 23 p = template_source.find('\n') … … def cleanse_setting(key, value): 31 34 """ 32 35 try: 33 36 if HIDDEN_SETTINGS.search(key): 34 cleansed = '********************'37 cleansed = CLEANSED_SUBSTITUTE 35 38 else: 36 39 if isinstance(value, dict): 37 40 cleansed = dict((k, cleanse_setting(k, v)) for k,v in value.items()) … … def technical_500_response(request, exc_type, exc_value, tb): 59 62 html = reporter.get_traceback_html() 60 63 return HttpResponseServerError(html, mimetype='text/html') 61 64 65 # Cache for the default exception reporter filter class. 66 default_exception_reporter_filter = None 67 68 def get_exception_reporter_filter(request): 69 global default_exception_reporter_filter 70 if default_exception_reporter_filter is None: 71 # Load the default filter for the first time and cache it. 72 modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER 73 modname, classname = modpath.rsplit('.', 1) 74 try: 75 mod = import_module(modname) 76 except ImportError, e: 77 raise ImproperlyConfigured( 78 'Error importing default exception reporter filter %s: "%s"' % (modpath, e)) 79 try: 80 default_exception_reporter_filter = getattr(mod, classname) 81 except AttributeError: 82 raise exceptions.ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname)) 83 if request: 84 filter = getattr(request, 'exception_reporter_filter', None) 85 if filter is None: 86 # The request hasn't been assigned a filter class yet, so assign it the default one. 87 request.exception_reporter_filter = default_exception_reporter_filter 88 return request.exception_reporter_filter(request) 89 else: 90 return default_exception_reporter_filter() 91 92 class ExceptionReporterFilterBase(object): 93 """ 94 Base for all exception reporter filter classes. All overridable hooks 95 contain lenient default behaviours. 96 """ 97 def __init__(self, request=None): 98 self.request = request 99 100 def show_request(self): 101 return self.request is not None 102 103 def get_request_repr(self): 104 if self.show_request(): 105 # Mimic HttpRequest's own __repr__() except filter the POST parameters. 106 return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \ 107 (pformat(self.request.GET), 108 pformat(self.get_filtered_post_dict()), 109 pformat(self.request.COOKIES), 110 pformat(self.request.META)) 111 else: 112 return u'' 113 114 def show_source(self): 115 return True 116 117 def get_filtered_post_dict(self): 118 if self.show_request(): 119 return self.request.POST 120 else: 121 return {} 122 123 def show_traceback_frames(self): 124 return True 125 126 def show_traceback_frame(self, tb_frame): 127 # Support for __traceback_hide__ which is used by a few libraries 128 # to hide internal frames. 129 return self.show_traceback_frames() and not tb_frame.f_locals.get('__traceback_hide__') 130 131 def show_traceback_variables(self, tb_frame): 132 return self.show_traceback_frame(tb_frame) 133 134 def get_filtered_traceback_variables(self, tb_frame): 135 if self.show_traceback_variables(): 136 return tb_frame.f_locals.items() 137 else: 138 return {} 139 140 class SafeExceptionReporterFilter(ExceptionReporterFilterBase): 141 """ 142 Use annotations made by the sensitive_post_parameters and 143 sensitive_variables decorators to filter out sensitive information. 144 """ 145 def get_filtered_post_dict(self): 146 if not self.show_request(): 147 return {} 148 else: 149 sensitive_post_parameters = getattr(self.request, 'sensitive_post_parameters', []) 150 if settings.DEBUG or not sensitive_post_parameters: 151 return self.request.POST 152 else: 153 cleansed = self.request.POST.copy() 154 if sensitive_post_parameters == '__EVERYTHING__': 155 # Cleanse all parameters 156 for k, v in cleansed.items(): 157 cleansed[k] = CLEANSED_SUBSTITUTE 158 return cleansed 159 else: 160 # Cleanse specified parameters 161 for param in sensitive_post_parameters: 162 if cleansed.has_key(param): 163 cleansed[param] = CLEANSED_SUBSTITUTE 164 return cleansed 165 166 def get_filtered_traceback_variables(self, tb_frame): 167 if not self.show_traceback_variables(tb_frame): 168 return {} 169 else: 170 func_name = tb_frame.f_code.co_name 171 func = tb_frame.f_globals.get(func_name) 172 sensitive_variables = getattr(func, 'sensitive_variables', []) 173 cleansed = [] 174 if settings.DEBUG or not sensitive_variables: 175 # Potentially clean only the request if it's one of the frame variables. 176 for name, value in tb_frame.f_locals.items(): 177 if value == self.request: 178 value = self.get_request_repr() 179 cleansed.append((name, value)) 180 return cleansed 181 else: 182 if sensitive_variables == '__EVERYTHING__': 183 # Cleanse all variables 184 for name, value in tb_frame.f_locals.items(): 185 cleansed.append((name, CLEANSED_SUBSTITUTE)) 186 return cleansed 187 else: 188 # Cleanse specified variables 189 for name, value in tb_frame.f_locals.items(): 190 if name in sensitive_variables: 191 value = CLEANSED_SUBSTITUTE 192 elif value == self.request: 193 # Clean the request's POST parameters. 194 value = self.get_request_repr() 195 cleansed.append((name, value)) 196 return cleansed 197 62 198 class ExceptionReporter(object): 63 199 """ 64 200 A class to organize and coordinate reporting on exceptions. 65 201 """ 66 202 def __init__(self, request, exc_type, exc_value, tb, is_email=False): 67 203 self.request = request 204 self.exception_reporter_filter = get_exception_reporter_filter(request) 68 205 self.exc_type = exc_type 69 206 self.exc_value = exc_value 70 207 self.tb = tb … … class ExceptionReporter(object): 123 260 'is_email': self.is_email, 124 261 'unicode_hint': unicode_hint, 125 262 'frames': frames, 126 'request': self.request, 263 'request': self.request if self.exception_reporter_filter.show_request() else None, 264 'filtered_POST': self.exception_reporter_filter.get_filtered_post_dict(), 127 265 'settings': get_safe_settings(), 128 266 'sys_executable': sys.executable, 129 267 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], … … class ExceptionReporter(object): 142 280 if frames: 143 281 c['lastframe'] = frames[-1] 144 282 return t.render(c) 145 283 146 284 def get_template_exception_info(self): 147 285 origin, (start, end) = self.exc_value.source 148 286 template_source = origin.reload() … … class ExceptionReporter(object): 219 357 return lower_bound, pre_context, context_line, post_context 220 358 221 359 def get_traceback_frames(self): 360 if not self.exception_reporter_filter.show_traceback_frames(): 361 return [] 222 362 frames = [] 223 363 tb = self.tb 224 364 while tb is not None: 225 # support for __traceback_hide__ which is used by a few libraries 226 # to hide internal frames. 227 if tb.tb_frame.f_locals.get('__traceback_hide__'): 365 if not self.exception_reporter_filter.show_traceback_frame(tb.tb_frame): 228 366 tb = tb.tb_next 229 367 continue 230 368 filename = tb.tb_frame.f_code.co_filename … … class ExceptionReporter(object): 232 370 lineno = tb.tb_lineno - 1 233 371 loader = tb.tb_frame.f_globals.get('__loader__') 234 372 module_name = tb.tb_frame.f_globals.get('__name__') 235 pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name) 373 if self.exception_reporter_filter.show_source(): 374 pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name) 375 else: 376 pre_context_lineno, pre_context, context_line, post_context = '', '', '', '' 236 377 if pre_context_lineno is not None: 237 378 frames.append({ 238 379 'tb': tb, 239 380 'filename': filename, 240 381 'function': function, 241 382 'lineno': lineno + 1, 242 'vars': tb.tb_frame.f_locals.items(),383 'vars': self.exception_reporter_filter.get_filtered_traceback_variables(tb.tb_frame), 243 384 'id': id(tb), 244 385 'pre_context': pre_context, 245 386 'context_line': context_line, … … Exception Value: {{ exception_value|force_escape }} 643 784 {% endif %} 644 785 645 786 <h3 id="post-info">POST</h3> 646 {% if request.POST %}787 {% if filtered_POST %} 647 788 <table class="req"> 648 789 <thead> 649 790 <tr> … … Exception Value: {{ exception_value|force_escape }} 652 793 </tr> 653 794 </thead> 654 795 <tbody> 655 {% for var in request.POST.items %}796 {% for var in filtered_POST.items %} 656 797 <tr> 657 798 <td>{{ var.0 }}</td> 658 799 <td class="code"><pre>{{ var.1|pprint }}</pre></td> -
new file django/views/decorators/security.py
diff --git a/django/views/decorators/security.py b/django/views/decorators/security.py new file mode 100644 index 0000000..8c92ec7
- + 1 import functools 2 3 4 def sensitive_variables(*variables): 5 """ 6 Indicates which variables used in the decorated function are sensitive, so 7 that those variables can later be treated in a special way, for example 8 by hiding them when logging unhandled exceptions. 9 10 Two forms are accepted: 11 12 * with specified variable names: 13 14 @sensitive_variables('password', 'credit_card') 15 def my_function() 16 password = 'foo' 17 credit_card = '5555 5555 5555 5555' 18 ... 19 20 * without any specified variable names, in which case it is assumed that 21 all variables are considered sensitive: 22 23 @sensitive_variables 24 def my_function() 25 ... 26 """ 27 def decorator(func): 28 @functools.wraps(func) 29 def wrapper(*args, **kwargs): 30 if variables: 31 wrapper.sensitive_variables = variables 32 else: 33 wrapper.sensitive_variables = '__EVERYTHING__' 34 return func(*args, **kwargs) 35 return wrapper 36 if len(variables) == 1 and callable(variables[0]): 37 # This is of the form: func = sensitive_variables(func) 38 func = variables[0] 39 variables = [] 40 return decorator(func) 41 else: 42 # This is of the form: func = sensitive_variables(...)(func) 43 return decorator 44 45 46 def sensitive_post_parameters(*params): 47 """ 48 Indicates which POST parameters used in the decorated view are sensitive, 49 so that those parameters can later be treated in a special way, for example 50 by hiding them when logging unhandled exceptions. 51 52 Two forms are accepted: 53 54 * with specified parameters: 55 56 @sensitive_post_parameters('password', 'credit_card') 57 def my_view(request) 58 pw = request.POST['password'] 59 cc = request.POST['credit_card'] 60 ... 61 62 * without any specified parameters, in which case it is assumed that 63 all parameters are considered sensitive: 64 65 @sensitive_post_parameters 66 def my_view(request) 67 ... 68 """ 69 def decorator(view): 70 @functools.wraps(view) 71 def wrapper(request, *args, **kwargs): 72 if params: 73 request.sensitive_post_parameters = params 74 else: 75 request.sensitive_post_parameters = '__EVERYTHING__' 76 return view(request, *args, **kwargs) 77 return wrapper 78 if len(params) == 1 and callable(params[0]): 79 # This is of the form: view = sensitive_post_parameters(view) 80 view = params[0] 81 params = [] 82 return decorator(view) 83 else: 84 # This is of the form: view = sensitive_post_parameters(...)(view) 85 return decorator -
tests/regressiontests/views/tests/debug.py
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index 6dd4bd4..44186b6 100644
a b 1 from __future__ import with_statement 1 2 import inspect 2 3 import os 3 4 import sys … … from django.test import TestCase, RequestFactory 8 9 from django.core.urlresolvers import reverse 9 10 from django.template import TemplateSyntaxError 10 11 from django.views.debug import ExceptionReporter 12 from django.core.exceptions import ImproperlyConfigured 13 from django.core import mail 11 14 12 15 from regressiontests.views import BrokenException, except_args 16 from regressiontests.views.views import (sensitive_view, non_sensitive_view, 17 paranoid_view) 13 18 14 19 15 20 class DebugViewTests(TestCase): … … class ExceptionReporterTests(TestCase): 143 148 self.assertNotIn('<h2>Traceback ', html) 144 149 self.assertIn('<h2>Request information</h2>', html) 145 150 self.assertIn('<p>Request data not supplied</p>', html) 151 152 153 class ExceptionReporterFilterTests(TestCase): 154 rf = RequestFactory() 155 breakfast_data = {'sausage-key': 'sausage-value', 156 'baked-beans-key': 'baked-beans-value', 157 'hash-brown-key': 'hash-brown-value', 158 'bacon-key': 'bacon-value',} 159 160 def verify_unsafe_response(self, view): 161 """ 162 Asserts that potentially sensitive info are displayed in the response. 163 """ 164 request = self.rf.post('/some_url/', self.breakfast_data) 165 response = view(request) 166 # All variables are shown. 167 self.assertContains(response, 'cooked_eggs', status_code=500) 168 self.assertContains(response, 'scrambled', status_code=500) 169 self.assertContains(response, 'sauce', status_code=500) 170 self.assertContains(response, 'worcestershire', status_code=500) 171 for k, v in self.breakfast_data.items(): 172 # All POST parameters are shown. 173 self.assertContains(response, k, status_code=500) 174 self.assertContains(response, v, status_code=500) 175 176 def verify_safe_response(self, view): 177 """ 178 Asserts that certain sensitive info are not displayed in the response. 179 """ 180 request = self.rf.post('/some_url/', self.breakfast_data) 181 response = view(request) 182 # Non-sensitive variable's name and value are shown. 183 self.assertContains(response, 'cooked_eggs', status_code=500) 184 self.assertContains(response, 'scrambled', status_code=500) 185 # Sensitive variable's name is shown but not its value. 186 self.assertContains(response, 'sauce', status_code=500) 187 self.assertNotContains(response, 'worcestershire', status_code=500) 188 for k, v in self.breakfast_data.items(): 189 # All POST parameters' names are shown. 190 self.assertContains(response, k, status_code=500) 191 # Non-sensitive POST parameters' values are shown. 192 self.assertContains(response, 'baked-beans-value', status_code=500) 193 self.assertContains(response, 'hash-brown-value', status_code=500) 194 # Sensitive POST parameters' values are not shown. 195 self.assertNotContains(response, 'sausage-value', status_code=500) 196 self.assertNotContains(response, 'bacon-value', status_code=500) 197 198 def verify_paranoid_response(self, view): 199 """ 200 Asserts that no variables or POST parameters are displayed in the response. 201 """ 202 request = self.rf.post('/some_url/', self.breakfast_data) 203 response = view(request) 204 # Show variable names but not their values. 205 self.assertContains(response, 'cooked_eggs', status_code=500) 206 self.assertNotContains(response, 'scrambled', status_code=500) 207 self.assertContains(response, 'sauce', status_code=500) 208 self.assertNotContains(response, 'worcestershire', status_code=500) 209 for k, v in self.breakfast_data.items(): 210 # All POST parameters' names are shown. 211 self.assertContains(response, k, status_code=500) 212 # No POST parameters' values are shown. 213 self.assertNotContains(response, v, status_code=500) 214 215 def verify_unsafe_email(self, view): 216 """ 217 Asserts that potentially sensitive info are displayed in the email report. 218 """ 219 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 220 mail.outbox = [] # Empty outbox 221 request = self.rf.post('/some_url/', self.breakfast_data) 222 response = view(request) 223 self.assertEquals(len(mail.outbox), 1) 224 email = mail.outbox[0] 225 # Frames vars are never shown in plain text email reports. 226 self.assertNotIn('cooked_eggs', email.body) 227 self.assertNotIn('scrambled', email.body) 228 self.assertNotIn('sauce', email.body) 229 self.assertNotIn('worcestershire', email.body) 230 for k, v in self.breakfast_data.items(): 231 # All POST parameters are shown. 232 self.assertIn(k, email.body) 233 self.assertIn(v, email.body) 234 235 def verify_safe_email(self, view): 236 """ 237 Asserts that certain sensitive info are not displayed in the email report. 238 """ 239 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 240 mail.outbox = [] # Empty outbox 241 request = self.rf.post('/some_url/', self.breakfast_data) 242 response = view(request) 243 self.assertEquals(len(mail.outbox), 1) 244 email = mail.outbox[0] 245 # Frames vars are never shown in plain text email reports. 246 self.assertNotIn('cooked_eggs', email.body) 247 self.assertNotIn('scrambled', email.body) 248 self.assertNotIn('sauce', email.body) 249 self.assertNotIn('worcestershire', email.body) 250 for k, v in self.breakfast_data.items(): 251 # All POST parameters' names are shown. 252 self.assertIn(k, email.body) 253 # Non-sensitive POST parameters' values are shown. 254 self.assertIn('baked-beans-value', email.body) 255 self.assertIn('hash-brown-value', email.body) 256 # Sensitive POST parameters' values are not shown. 257 self.assertNotIn('sausage-value', email.body) 258 self.assertNotIn('bacon-value', email.body) 259 260 def verify_paranoid_email(self, view): 261 """ 262 Asserts that no variables or POST parameters are displayed in the email report. 263 """ 264 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 265 mail.outbox = [] # Empty outbox 266 request = self.rf.post('/some_url/', self.breakfast_data) 267 response = view(request) 268 self.assertEquals(len(mail.outbox), 1) 269 email = mail.outbox[0] 270 # Frames vars are never shown in plain text email reports. 271 self.assertNotIn('cooked_eggs', email.body) 272 self.assertNotIn('scrambled', email.body) 273 self.assertNotIn('sauce', email.body) 274 self.assertNotIn('worcestershire', email.body) 275 for k, v in self.breakfast_data.items(): 276 # All POST parameters' names are shown. 277 self.assertIn(k, email.body) 278 # No POST parameters' values are shown. 279 self.assertNotIn(v, email.body) 280 281 def test_non_sensitive_request(self): 282 """ 283 Ensure that everything (request info and frame variables) can bee seen 284 in the default error reports for non-sensitive requests. 285 Refs #14614. 286 """ 287 with self.settings(DEBUG=True): 288 self.verify_unsafe_response(non_sensitive_view) 289 self.verify_unsafe_email(non_sensitive_view) 290 291 with self.settings(DEBUG=False): 292 self.verify_unsafe_response(non_sensitive_view) 293 self.verify_unsafe_email(non_sensitive_view) 294 295 def test_sensitive_request(self): 296 """ 297 Ensure that sensitive POST parameters and frame variables cannot be 298 seen in the default error reports for sensitive requests. 299 Refs #14614. 300 """ 301 with self.settings(DEBUG=True): 302 self.verify_unsafe_response(sensitive_view) 303 self.verify_unsafe_email(sensitive_view) 304 305 with self.settings(DEBUG=False): 306 self.verify_safe_response(sensitive_view) 307 self.verify_safe_email(sensitive_view) 308 309 def test_paranoid_request(self): 310 """ 311 Ensure that no POST parameters and frame variables can be seen in the 312 default error reports for "paranoid" requests. 313 Refs #14614. 314 """ 315 with self.settings(DEBUG=True): 316 self.verify_unsafe_response(paranoid_view) 317 self.verify_unsafe_email(paranoid_view) 318 319 with self.settings(DEBUG=False): 320 self.verify_paranoid_response(paranoid_view) 321 self.verify_paranoid_email(paranoid_view) -
tests/regressiontests/views/views.py
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 11d289f..7f274c3 100644
a b from django.core.urlresolvers import get_resolver 6 6 from django.shortcuts import render_to_response, render 7 7 from django.template import Context, RequestContext, TemplateDoesNotExist 8 8 from django.views.debug import technical_500_response 9 from django.views.decorators.security import (sensitive_post_parameters, 10 sensitive_variables) 11 from django.utils.log import getLogger 9 12 10 13 from regressiontests.views import BrokenException, except_args 11 14 … … def raises_template_does_not_exist(request): 128 131 return render_to_response('i_dont_exist.html') 129 132 except TemplateDoesNotExist: 130 133 return technical_500_response(request, *sys.exc_info()) 134 135 def send_log(request, exc_info): 136 logger = getLogger('django.request') 137 logger.error('Internal Server Error: %s' % request.path, 138 exc_info=exc_info, 139 extra={ 140 'status_code': 500, 141 'request': request 142 } 143 ) 144 145 def non_sensitive_view(request): 146 # Do not just use plain strings for the variables' values in the code 147 # so that the tests don't return false positives when the function's source 148 # is displayed in the exception report. 149 cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) 150 sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) 151 try: 152 raise Exception 153 except Exception: 154 exc_info = sys.exc_info() 155 send_log(request, exc_info) 156 return technical_500_response(request, *exc_info) 157 158 @sensitive_variables('sauce') 159 @sensitive_post_parameters('bacon-key', 'sausage-key') 160 def sensitive_view(request): 161 # Do not just use plain strings for the variables' values in the code 162 # so that the tests don't return false positives when the function's source 163 # is displayed in the exception report. 164 cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) 165 sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) 166 try: 167 raise Exception 168 except Exception: 169 exc_info = sys.exc_info() 170 send_log(request, exc_info) 171 return technical_500_response(request, *exc_info) 172 173 @sensitive_variables 174 @sensitive_post_parameters 175 def paranoid_view(request): 176 # Do not just use plain strings for the variables' values in the code 177 # so that the tests don't return false positives when the function's source 178 # is displayed in the exception report. 179 cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) 180 sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) 181 try: 182 raise Exception 183 except Exception: 184 exc_info = sys.exc_info() 185 send_log(request, exc_info) 186 return technical_500_response(request, *exc_info)