Ticket #14614: 14614.sensitive-request.diff
File 14614.sensitive-request.diff, 13.2 KB (added by , 14 years ago) |
---|
-
django/contrib/auth/views.py
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index cfb2659..72ca81c 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.debug import sensitive 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 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 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 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 f216886..6ccf5b2 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..3899b8c 100644
a b 1 1 import logging 2 2 import sys 3 3 4 from django.core import mail 5 from django.conf import settings 4 6 5 7 # Make sure a NullHandler is available 6 8 # This was added in Python 2.7/3.2 … … class AdminEmailHandler(logging.Handler): 42 44 from django.conf import settings 43 45 from django.views.debug import ExceptionReporter 44 46 45 try: 46 request = record.request 47 subject = '%s (%s IP): %s' % ( 48 record.levelname, 49 (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), 50 record.msg 51 ) 52 request_repr = repr(request) 53 except: 47 if (not settings.DEBUG and 48 hasattr(record, 'request') and 49 hasattr(record.request, 'is_sensitive') and 50 record.request.is_sensitive): 54 51 subject = '%s: %s' % ( 55 record.levelname, 56 record.msg 57 ) 58 52 record.levelname, 53 record.msg 54 ) 59 55 request = None 60 request_repr = "Request repr() unavailable" 61 56 request_repr = "Request is sensitive, so it is not provided in this report." 57 else: 58 try: 59 request = record.request 60 subject = '%s (%s IP): %s' % ( 61 record.levelname, 62 (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'), 63 record.msg 64 ) 65 request_repr = repr(request) 66 except: 67 subject = '%s: %s' % ( 68 record.levelname, 69 record.msg 70 ) 71 request = None 72 request_repr = "Request repr() unavailable." 73 62 74 if record.exc_info: 63 75 exc_info = record.exc_info 64 76 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..11bcb07 100644
a b import os 3 3 import re 4 4 import sys 5 5 import types 6 import functools 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 def sensitive(view): 20 @functools.wraps(view) 21 def wrapper(request, *args, **kwargs): 22 request.is_sensitive = True 23 return view(request, *args, **kwargs) 24 return wrapper 25 18 26 def linebreak_iter(template_source): 19 27 yield 0 20 28 p = template_source.find('\n') … … class ExceptionReporter(object): 123 131 'is_email': self.is_email, 124 132 'unicode_hint': unicode_hint, 125 133 'frames': frames, 126 'request': self.request ,134 'request': self.request if self.display_sensitive_info() else None, 127 135 'settings': get_safe_settings(), 128 136 'sys_executable': sys.executable, 129 137 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], … … class ExceptionReporter(object): 143 151 c['lastframe'] = frames[-1] 144 152 return t.render(c) 145 153 154 def display_sensitive_info(self): 155 if (not settings.DEBUG and 156 self.request is not None and 157 hasattr(self.request, 'is_sensitive') and 158 self.request.is_sensitive): 159 return False 160 else: 161 return True 162 146 163 def get_template_exception_info(self): 147 164 origin, (start, end) = self.exc_value.source 148 165 template_source = origin.reload() … … class ExceptionReporter(object): 239 256 'filename': filename, 240 257 'function': function, 241 258 'lineno': lineno + 1, 242 'vars': tb.tb_frame.f_locals.items() ,259 'vars': tb.tb_frame.f_locals.items() if self.display_sensitive_info() else [], 243 260 'id': id(tb), 244 261 'pre_context': pre_context, 245 262 'context_line': context_line, -
tests/regressiontests/views/tests/debug.py
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index d778311..3ea5d90 100644
a b 1 from __future__ import with_statement 1 2 import inspect 2 3 import sys 3 4 4 5 from django.conf import settings 6 from django.core.exceptions import ImproperlyConfigured 5 7 from django.core.files.uploadedfile import SimpleUploadedFile 6 8 from django.test import TestCase, RequestFactory 7 9 from django.core.urlresolvers import reverse 8 10 from django.template import TemplateSyntaxError 9 11 from django.views.debug import ExceptionReporter 12 from django.core import mail 10 13 11 14 from regressiontests.views import BrokenException, except_args 15 from regressiontests.views.views import sensitive_view, non_sensitive_view 12 16 13 17 14 18 class DebugViewTests(TestCase): … … class DebugViewTests(TestCase): 61 65 62 66 class ExceptionReporterTests(TestCase): 63 67 rf = RequestFactory() 68 breakfast_data = {'sausage-key': 'sausage-value', 69 'baked-beans-key': 'baked-beans-value', 70 'hash-brown-key': 'hash-brown-value', 71 'bacon-key': 'bacon-value',} 64 72 65 73 def test_request_and_exception(self): 66 74 "A simple exception report can be generated" … … class ExceptionReporterTests(TestCase): 81 89 self.assertIn('<h2>Request information</h2>', html) 82 90 self.assertNotIn('<p>Request data not supplied</p>', html) 83 91 92 def verify_unsafe_response(self, view): 93 """ 94 Asserts that potentially sensitive info are displayed in the response. 95 """ 96 request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data)) 97 response = view(request) 98 self.assertContains(response, 'cooked_eggs', status_code=500) 99 self.assertContains(response, 'scrambled', status_code=500) 100 for k, v in self.breakfast_data.items(): 101 self.assertContains(response, k, status_code=500) 102 self.assertContains(response, v, status_code=500) 103 104 def verify_unsafe_email(self, view): 105 """ 106 Asserts that potentially sensitive info are displayed in the error email. 107 """ 108 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 109 mail.outbox = [] # Empty outbox 110 request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data)) 111 response = view(request) 112 113 # TODO: Figure out why 2 emails get sent: len(outbox)==2 114 # Frames vars are never shown in plain text error emails 115 email = mail.outbox[0] 116 self.assertIn('cooked_eggs', email.body) 117 self.assertIn('scrambled', email.body) 118 for k, v in self.breakfast_data.items(): 119 self.assertIn(k, email.body) 120 self.assertIn(v, email.body) 121 122 def verify_safe_response(self, view): 123 """ 124 Asserts that potentially sensitive info are not displayed in the response. 125 """ 126 request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data)) 127 response = view(request) 128 self.assertContains(response, 'cooked_eggs', status_code=500) 129 self.assertNotContains(response, 'scrambled', status_code=500) 130 for k, v in self.breakfast_data.items(): 131 self.assertNotContains(response, k, status_code=500) 132 self.assertNotContains(response, v, status_code=500) 133 134 def verify_safe_email(self, view): 135 """ 136 Asserts that potentially sensitive info are not displayed in the error email. 137 """ 138 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 139 mail.outbox = [] # Empty outbox 140 request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data)) 141 response = view(request) 142 143 # TODO: Figure out why 2 emails get sent: len(outbox)==2 144 # Frames vars are never shown in plain text error emails 145 email = mail.outbox[0] 146 self.assertIn('cooked_eggs', email.body) 147 self.assertNotIn('scrambled', email.body) 148 for k, v in self.breakfast_data.items(): 149 self.assertNotIn(k, email.body) 150 self.assertNotIn(v, email.body) 151 152 def test_non_sensitive_request(self): 153 """ 154 Ensure that everything (request info and frame vars) can bee seen 155 in the default exception reporters for views that are not marked as 156 sensitive. 157 Refs #14614. 158 """ 159 with self.settings(DEBUG=True): 160 self.verify_unsafe_response(non_sensitive_view) 161 self.verify_unsafe_email(non_sensitive_view) 162 163 with self.settings(DEBUG=False): 164 self.verify_unsafe_response(non_sensitive_view) 165 self.verify_unsafe_email(non_sensitive_view) 166 167 def test_sensitive_request(self): 168 """ 169 Ensure that the request info and frame vars cannot be seen 170 in the default exception reporters for views that are marked as 171 sensitive. 172 Refs #14614. 173 """ 174 with self.settings(DEBUG=True): 175 self.verify_unsafe_response(sensitive_view) 176 self.verify_unsafe_email(sensitive_view) 177 178 with self.settings(DEBUG=False): 179 self.verify_safe_response(sensitive_view) 180 self.verify_safe_email(sensitive_view) 181 84 182 def test_no_request(self): 85 183 "An exception report can be generated without request" 86 184 try: -
tests/regressiontests/views/views.py
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 11d289f..e36f919 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.debug import sensitive 10 from django.utils.log import getLogger 9 11 10 12 from regressiontests.views import BrokenException, except_args 11 13 … … def raises_template_does_not_exist(request): 128 130 return render_to_response('i_dont_exist.html') 129 131 except TemplateDoesNotExist: 130 132 return technical_500_response(request, *sys.exc_info()) 133 134 def non_sensitive_view(request): 135 try: 136 cooked_eggs = request.get('cooked_eggs') 137 raise Exception 138 except Exception: 139 logger = getLogger('django.request') 140 logger.error('Internal Server Error: %s' % request.path, 141 exc_info=sys.exc_info(), 142 extra={ 143 'status_code': 500, 144 'request': request 145 } 146 ) 147 return technical_500_response(request, *sys.exc_info()) 148 149 @sensitive 150 def sensitive_view(request): 151 return non_sensitive_view(request)