Ticket #14614: 14614.exception-reporter-filter.3.diff
File 14614.exception-reporter-filter.3.diff, 44.0 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..237b153 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.debug 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..6a97ffa 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.debug 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..c1cd328 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(request) 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..5e3900c 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 from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound 9 from django.http import (HttpResponse, HttpResponseServerError, 10 HttpResponseNotFound, HttpRequest) 9 11 from django.template import (Template, Context, TemplateDoesNotExist, 10 12 TemplateSyntaxError) 11 13 from django.template.defaultfilters import force_escape, pprint … … from django.utils.encoding import smart_unicode, smart_str 15 17 16 18 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE') 17 19 20 CLEANSED_SUBSTITUTE = u'********************' 21 18 22 def linebreak_iter(template_source): 19 23 yield 0 20 24 p = template_source.find('\n') … … def cleanse_setting(key, value): 31 35 """ 32 36 try: 33 37 if HIDDEN_SETTINGS.search(key): 34 cleansed = '********************'38 cleansed = CLEANSED_SUBSTITUTE 35 39 else: 36 40 if isinstance(value, dict): 37 41 cleansed = dict((k, cleanse_setting(k, v)) for k,v in value.items()) … … def technical_500_response(request, exc_type, exc_value, tb): 59 63 html = reporter.get_traceback_html() 60 64 return HttpResponseServerError(html, mimetype='text/html') 61 65 66 # Cache for the default exception reporter filter instance. 67 default_exception_reporter_filter = None 68 69 def get_exception_reporter_filter(request): 70 global default_exception_reporter_filter 71 if default_exception_reporter_filter is None: 72 # Load the default filter for the first time and cache it. 73 modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER 74 modname, classname = modpath.rsplit('.', 1) 75 try: 76 mod = import_module(modname) 77 except ImportError, e: 78 raise ImproperlyConfigured( 79 'Error importing default exception reporter filter %s: "%s"' % (modpath, e)) 80 try: 81 default_exception_reporter_filter = getattr(mod, classname)() 82 except AttributeError: 83 raise exceptions.ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname)) 84 if request: 85 return getattr(request, 'exception_reporter_filter', default_exception_reporter_filter) 86 else: 87 return default_exception_reporter_filter 88 89 class ExceptionReporterFilter(object): 90 """ 91 Base for all exception reporter filter classes. All overridable hooks 92 contain lenient default behaviours. 93 """ 94 95 def get_request_repr(self, request): 96 if request is None: 97 return repr(None) 98 else: 99 # Since this is called as part of error handling, we need to be very 100 # robust against potentially malformed input. 101 try: 102 get = pformat(request.GET) 103 except: 104 get = '<could not parse>' 105 if request._post_parse_error: 106 post = '<could not parse>' 107 else: 108 try: 109 post = pformat(self.get_post_parameters(request)) 110 except: 111 post = '<could not parse>' 112 try: 113 cookies = pformat(request.COOKIES) 114 except: 115 cookies = '<could not parse>' 116 try: 117 meta = pformat(request.META) 118 except: 119 meta = '<could not parse>' 120 return smart_str(u'<%s\npath:%s,\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % 121 (request.__class__.__name__, 122 request.path, 123 unicode(get), 124 unicode(post), 125 unicode(cookies), 126 unicode(meta))) 127 128 def get_post_parameters(self, request): 129 if request is None: 130 return {} 131 else: 132 return request.POST 133 134 def get_traceback_frame_variables(self, request, tb_frame): 135 return tb_frame.f_locals.items() 136 137 class SafeExceptionReporterFilter(ExceptionReporterFilter): 138 """ 139 Use annotations made by the sensitive_post_parameters and 140 sensitive_variables decorators to filter out sensitive information. 141 """ 142 143 def is_active(self, request): 144 """ 145 This filter is to add safety in production environments (i.e. DEBUG 146 is False). If DEBUG is True then your site is not safe anyway. 147 This hook is provided as a convenience to easily activate or 148 deactivate the filter on a per request basis. 149 """ 150 return settings.DEBUG is False 151 152 def get_post_parameters(self, request): 153 """ 154 Replaces the values of POST parameters marked as sensitive with 155 stars (*********). 156 """ 157 if request is None: 158 return {} 159 else: 160 sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', []) 161 if self.is_active(request) and sensitive_post_parameters: 162 cleansed = request.POST.copy() 163 if sensitive_post_parameters == '__ALL__': 164 # Cleanse all parameters. 165 for k, v in cleansed.items(): 166 cleansed[k] = CLEANSED_SUBSTITUTE 167 return cleansed 168 else: 169 # Cleanse only the specified parameters. 170 for param in sensitive_post_parameters: 171 if cleansed.has_key(param): 172 cleansed[param] = CLEANSED_SUBSTITUTE 173 return cleansed 174 else: 175 return request.POST 176 177 def get_traceback_frame_variables(self, request, tb_frame): 178 """ 179 Replaces the values of variables marked as sensitive with 180 stars (*********). 181 """ 182 func_name = tb_frame.f_code.co_name 183 func = tb_frame.f_globals.get(func_name) 184 sensitive_variables = getattr(func, 'sensitive_variables', []) 185 cleansed = [] 186 if self.is_active(request) and sensitive_variables: 187 if sensitive_variables == '__ALL__': 188 # Cleanse all variables 189 for name, value in tb_frame.f_locals.items(): 190 cleansed.append((name, CLEANSED_SUBSTITUTE)) 191 return cleansed 192 else: 193 # Cleanse specified variables 194 for name, value in tb_frame.f_locals.items(): 195 if name in sensitive_variables: 196 value = CLEANSED_SUBSTITUTE 197 elif isinstance(value, HttpRequest): 198 # Cleanse the request's POST parameters. 199 value = self.get_request_repr(value) 200 cleansed.append((name, value)) 201 return cleansed 202 else: 203 # Potentially cleanse only the request if it's one of the frame variables. 204 for name, value in tb_frame.f_locals.items(): 205 if isinstance(value, HttpRequest): 206 # Cleanse the request's POST parameters. 207 value = self.get_request_repr(value) 208 cleansed.append((name, value)) 209 return cleansed 210 62 211 class ExceptionReporter(object): 63 212 """ 64 213 A class to organize and coordinate reporting on exceptions. 65 214 """ 66 215 def __init__(self, request, exc_type, exc_value, tb, is_email=False): 67 216 self.request = request 217 self.filter = get_exception_reporter_filter(self.request) 68 218 self.exc_type = exc_type 69 219 self.exc_value = exc_value 70 220 self.tb = tb … … class ExceptionReporter(object): 124 274 'unicode_hint': unicode_hint, 125 275 'frames': frames, 126 276 'request': self.request, 277 'filtered_POST': self.filter.get_post_parameters(self.request), 127 278 'settings': get_safe_settings(), 128 279 'sys_executable': sys.executable, 129 280 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], … … class ExceptionReporter(object): 222 373 frames = [] 223 374 tb = self.tb 224 375 while tb is not None: 225 # support for __traceback_hide__ which is used by a few libraries376 # Support for __traceback_hide__ which is used by a few libraries 226 377 # to hide internal frames. 227 378 if tb.tb_frame.f_locals.get('__traceback_hide__'): 228 379 tb = tb.tb_next … … class ExceptionReporter(object): 239 390 'filename': filename, 240 391 'function': function, 241 392 'lineno': lineno + 1, 242 'vars': tb.tb_frame.f_locals.items(),393 'vars': self.filter.get_traceback_frame_variables(self.request, tb.tb_frame), 243 394 'id': id(tb), 244 395 'pre_context': pre_context, 245 396 'context_line': context_line, … … class ExceptionReporter(object): 247 398 'pre_context_lineno': pre_context_lineno + 1, 248 399 }) 249 400 tb = tb.tb_next 250 251 401 return frames 252 402 253 403 def format_exception(self): … … Exception Value: {{ exception_value|force_escape }} 643 793 {% endif %} 644 794 645 795 <h3 id="post-info">POST</h3> 646 {% if request.POST %}796 {% if filtered_POST %} 647 797 <table class="req"> 648 798 <thead> 649 799 <tr> … … Exception Value: {{ exception_value|force_escape }} 652 802 </tr> 653 803 </thead> 654 804 <tbody> 655 {% for var in request.POST.items %}805 {% for var in filtered_POST.items %} 656 806 <tr> 657 807 <td>{{ var.0 }}</td> 658 808 <td class="code"><pre>{{ var.1|pprint }}</pre></td> -
new file django/views/decorators/debug.py
diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py new file mode 100644 index 0000000..9974629
- + 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('user', 'password', 'credit_card') 15 def my_function(user): 16 password = user.pass_word 17 credit_card = user.credit_card_number 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 = '__ALL__' 34 return func(*args, **kwargs) 35 return wrapper 36 return decorator 37 38 39 def sensitive_post_parameters(*parameters): 40 """ 41 Indicates which POST parameters used in the decorated view are sensitive, 42 so that those parameters can later be treated in a special way, for example 43 by hiding them when logging unhandled exceptions. 44 45 Two forms are accepted: 46 47 * with specified parameters: 48 49 @sensitive_post_parameters('password', 'credit_card') 50 def my_view(request): 51 pw = request.POST['password'] 52 cc = request.POST['credit_card'] 53 ... 54 55 * without any specified parameters, in which case it is assumed that 56 all parameters are considered sensitive: 57 58 @sensitive_post_parameters() 59 def my_view(request) 60 ... 61 """ 62 def decorator(view): 63 @functools.wraps(view) 64 def wrapper(request, *args, **kwargs): 65 if parameters: 66 request.sensitive_post_parameters = parameters 67 else: 68 request.sensitive_post_parameters = '__ALL__' 69 return view(request, *args, **kwargs) 70 return wrapper 71 return decorator -
docs/howto/error-reporting.txt
diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index c15c1d8..f87c57c 100644
a b 1 Error reporting via email2 =============== ==========1 Error reporting 2 =============== 3 3 4 4 When you're running a public site you should always turn off the 5 5 :setting:`DEBUG` setting. That will make your server run much faster, and will … … revealed by the error pages. 9 9 However, running with :setting:`DEBUG` set to ``False`` means you'll never see 10 10 errors generated by your site -- everyone will just see your public error pages. 11 11 You need to keep track of errors that occur in deployed sites, so Django can be 12 configured to email you details ofthose errors.12 configured to create reports with details about those errors. 13 13 14 Server errors14 Email reports 15 15 ------------- 16 16 17 Server errors 18 ~~~~~~~~~~~~~ 19 17 20 When :setting:`DEBUG` is ``False``, Django will email the users listed in the 18 21 :setting:`ADMINS` setting whenever your code raises an unhandled exception and 19 22 results in an internal server error (HTTP status code 500). This gives the … … setting. 48 51 </topics/logging>`. 49 52 50 53 404 errors 51 ---------- 54 ~~~~~~~~~~ 52 55 53 56 Django can also be configured to email errors about broken links (404 "page 54 57 not found" errors). Django sends emails about 404 errors when: … … The best way to disable this behavior is to set 96 99 97 100 .. seealso:: 98 101 99 You can also set up custom error reporting by writing a custom piece of100 :ref:`exception middleware <exception-middleware>`. If you do write custom101 error handling, it's a good idea to emulate Django's built-in error handling102 and only report/log errors if :setting:`DEBUG` is ``False``.103 104 .. seealso::105 106 102 .. versionadded:: 1.3 107 103 108 104 404 errors are logged using the logging framework. By default, these log … … The best way to disable this behavior is to set 116 112 Previously, two settings were used to control which URLs not to report: 117 113 :setting:`IGNORABLE_404_STARTS` and :setting:`IGNORABLE_404_ENDS`. They 118 114 were replaced by :setting:`IGNORABLE_404_URLS`. 115 116 .. _filtering-error-reports: 117 118 Filtering error reports 119 ----------------------- 120 121 .. versionadded:: 1.4 122 123 Filtering sensitive information 124 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 125 126 Error reports are really helpful for debugging errors, so it is generally 127 useful to record as much relevant information about those errors as possible. 128 For example, by default Django records the `full traceback`_ for the 129 exception raised, each `traceback frame`_'s local variables, and the 130 :class:`HttpRequest`'s :ref:`attributes<httprequest-attributes>`. 131 132 However, sometimes certain types of information may be too sensitive and thus 133 may not be appropriate to be kept track of, for example a user's password or 134 credit card number. So Django offers a set of function decorators to help you 135 control which information should be filtered out of error reports in a 136 production environment (that is, where :setting:`DEBUG` is set to ``False``): 137 :func:`sensitive_variables` and :func:`sensitive_post_parameters`. 138 139 .. _`full traceback`: http://en.wikipedia.org/wiki/Stack_trace 140 .. _`traceback frame`: http://en.wikipedia.org/wiki/Stack_frame 141 142 .. function:: sensitive_variables(*variables) 143 144 If a function (either a view or any regular callback) in your code uses 145 local variables susceptible to contain sensitive information, you may 146 prevent the values of those variables from being included in error reports 147 using the ``sensitive_variables`` decorator: 148 149 .. code-block:: python 150 151 from django.views.decorators.debug import sensitive_variables 152 153 @sensitive_variables('user', 'pw', 'cc') 154 def process_info(user): 155 pw = user.pass_word 156 cc = user.credit_card_number 157 name = user.name 158 ... 159 160 In the above example, the values for the ``user``, ``pw`` and ``cc`` 161 variables will be hidden and replaced with stars (`**********`) in the 162 error reports, whereas the value of the ``name`` variable will be 163 disclosed. 164 165 To systematically hide all local variables of a function from error logs, 166 do not provide any argument to the ``sensitive_variables`` decorator: 167 168 .. code-block:: python 169 170 @sensitive_variables() 171 def my_function(): 172 ... 173 174 .. function:: sensitive_post_parameters(*parameters) 175 176 If one of your views receives an :class:`HttpRequest` object with 177 :attr:`POST parameters<HttpRequest.POST>` susceptible to contain sensitive 178 information, you may prevent the values of those parameters from being 179 included in the error reports using the ``sensitive_post_parameters`` 180 decorator: 181 182 .. code-block:: python 183 184 from django.views.decorators.debug import sensitive_post_parameters 185 186 @sensitive_post_parameters('pass_word', 'credit_card_number') 187 def record_user_profile(request): 188 UserProfile.create(user=request.user, 189 password=request.POST['pass_word'], 190 credit_card=request.POST['credit_card_number'], 191 name=request.POST['name']) 192 ... 193 194 In the above example, the values for the ``pass_word`` and 195 ``credit_card_number`` POST parameters will be hidden and replaced with 196 stars (`**********`) in the request's representation inside the error 197 reports, whereas the value of the ``name`` parameter will be disclosed. 198 199 To systematically hide all POST parameters of a request in error reports, 200 do not provide any argument to the ``sensitive_post_parameters`` decorator: 201 202 .. code-block:: python 203 204 @sensitive_post_parameters() 205 def my_view(request): 206 ... 207 208 .. note:: 209 210 .. versionchanged:: 1.4 211 212 Since version 1.4, all POST parameters are systematically filtered out of 213 error reports for certain :mod:`contrib.views.auth` views (``login``, 214 ``password_reset_confirm``, ``password_change``, and ``add_view`` and 215 ``user_change_password`` in the ``auth`` admin) to prevent the leaking of 216 sensitive information such as user passwords. 217 218 .. _custom-error-reports: 219 220 Custom error reports 221 ~~~~~~~~~~~~~~~~~~~~ 222 223 All :func:`sensitive_variables` and :func:`sensitive_post_parameters` do is, 224 respectively, annotate the decorated function with the names of sensitive 225 variables and annotate the ``HttpRequest`` object with the names of sensitive 226 POST parameters, so that this sensitive information can later be filtered out 227 of reports when an error occurs. The actual filtering is done by Django's 228 default error reporter filter: 229 :class:`django.views.debug.SafeExceptionReporterFilter`. This filter uses the 230 decorators' annotations to replace the corresponding values with stars 231 (`**********`) when the error reports are produced. If you wish to override or 232 customize this default behavior for your entire site, you need to define your 233 own filter class and tell Django to use it via the 234 :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` setting: 235 236 .. code-block:: python 237 238 DEFAULT_EXCEPTION_REPORTER_FILTER = 'path.to.your.CustomExceptionReporterFilter' 239 240 You may also control in a more granular way which filter to use within any 241 given view by setting the ``HttpRequest``'s ``exception_reporter_filter`` 242 attribute: 243 244 .. code-block:: python 245 246 def my_view(request): 247 if request.user.is_authenticated(): 248 request.exception_reporter_filter = CustomExceptionReporterFilter() 249 ... 250 251 Your custom filter class needs to inherit from 252 :class:`django.views.debug.SafeExceptionReporterFilter` and may override the 253 following methods: 254 255 .. class:: django.views.debug.SafeExceptionReporterFilter 256 257 .. method:: SafeExceptionReporterFilter.is_active(self, request) 258 259 Returns ``True`` to activate the filtering operated in the other methods. 260 By default the filter is active if :setting:`DEBUG` is ``False``. 261 262 .. method:: SafeExceptionReporterFilter.get_request_repr(self, request) 263 264 Returns the representation string of the request object, that is, the 265 value that would be returned by ``repr(request)``, except it uses the 266 filtered dictionary of POST parameters as determined by 267 :meth:`SafeExceptionReporterFilter.get_post_parameters`. 268 269 .. method:: SafeExceptionReporterFilter.get_post_parameters(self, request) 270 271 Returns the filtered dictionary of POST parameters. By default it replaces 272 the values of sensitive parameters with stars (`**********`). 273 274 .. method:: SafeExceptionReporterFilter.get_traceback_frame_variables(self, request, tb_frame) 275 276 Returns the filtered dictionary of local variables for the given traceback 277 frame. By default it replaces the values of sensitive variables with stars 278 (`**********`). 279 280 .. seealso:: 281 282 You can also set up custom error reporting by writing a custom piece of 283 :ref:`exception middleware <exception-middleware>`. If you do write custom 284 error handling, it's a good idea to emulate Django's built-in error handling 285 and only report/log errors if :setting:`DEBUG` is ``False``. -
docs/ref/request-response.txt
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 72872d5..ff04e25 100644
a b HttpRequest objects 23 23 24 24 .. class:: HttpRequest 25 25 26 .. _httprequest-attributes: 27 26 28 Attributes 27 29 ---------- 28 30 -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 816c3e9..e95c165 100644
a b Default content type to use for all ``HttpResponse`` objects, if a MIME type 772 772 isn't manually specified. Used with :setting:`DEFAULT_CHARSET` to construct 773 773 the ``Content-Type`` header. 774 774 775 .. setting:: DEFAULT_EXCEPTION_REPORTER_FILTER 776 777 DEFAULT_EXCEPTION_REPORTER_FILTER 778 --------------------------------- 779 780 Default: :class:`django.views.debug.SafeExceptionReporterFilter` 781 782 Default exception reporter filter class to be used if none has been assigned to 783 the :class:`HttpRequest` instance yet. 784 See :ref:`Filtering error reports<filtering-error-reports>`. 785 775 786 .. setting:: DEFAULT_FILE_STORAGE 776 787 777 788 DEFAULT_FILE_STORAGE -
docs/releases/1.4.txt
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index bddb15c..a0eb55a 100644
a b help with AJAX heavy sites, protection for PUT and DELETE, and settings 112 112 the security and usefulness of the CSRF protection. See the :doc:`CSRF docs 113 113 </ref/contrib/csrf>` for more information. 114 114 115 Error report filtering 116 ~~~~~~~~~~~~~~~~~~~~~~ 117 118 Two new function decorators, :func:`sensitive_variables` and 119 :func:`sensitive_post_parameters`, were added to allow designating the 120 traceback frames' local variables and request's POST parameters susceptible 121 to contain sensitive information and that should be filtered out of error 122 reports. 123 124 All POST parameters are now systematically filtered out of error reports for 125 certain :mod:`contrib.views.auth` views (``login``, ``password_reset_confirm``, 126 ``password_change``, and ``add_view`` and ``user_change_password`` in the 127 ``auth`` admin) to prevent the leaking of sensitive information such as user 128 passwords. 129 130 You may override or customize the default filtering by writing a 131 :ref:`custom filter<custom-error-reports>`. Learn more on 132 :ref:`Filtering error reports<filtering-error-reports>`. 133 134 115 135 .. _backwards-incompatible-changes-1.4: 116 136 117 137 Backwards incompatible changes in 1.4 -
docs/topics/logging.txt
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 651a92f..e2c9f72 100644
a b Python logging module. 504 504 sensitive, and you may not want to send it over email. Consider using 505 505 something such as `django-sentry`_ to get the best of both worlds -- the 506 506 rich information of full tracebacks plus the security of *not* sending the 507 information over email. 507 information over email. You may also explicitly designate certain 508 sensitive information to be filtered out of error reports -- learn more on 509 :ref:`Filtering error reports<filtering-error-reports>`. 508 510 509 511 .. _django-sentry: http://pypi.python.org/pypi/django-sentry -
tests/regressiontests/views/tests/debug.py
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py index 6dd4bd4..da71aaf 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, custom_exception_reporter_filter_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 """ 155 Ensure that sensitive information can be filtered out of error reports. 156 Refs #14614. 157 """ 158 rf = RequestFactory() 159 breakfast_data = {'sausage-key': 'sausage-value', 160 'baked-beans-key': 'baked-beans-value', 161 'hash-brown-key': 'hash-brown-value', 162 'bacon-key': 'bacon-value',} 163 164 def verify_unsafe_response(self, view): 165 """ 166 Asserts that potentially sensitive info are displayed in the response. 167 """ 168 request = self.rf.post('/some_url/', self.breakfast_data) 169 response = view(request) 170 # All variables are shown. 171 self.assertContains(response, 'cooked_eggs', status_code=500) 172 self.assertContains(response, 'scrambled', status_code=500) 173 self.assertContains(response, 'sauce', status_code=500) 174 self.assertContains(response, 'worcestershire', status_code=500) 175 for k, v in self.breakfast_data.items(): 176 # All POST parameters are shown. 177 self.assertContains(response, k, status_code=500) 178 self.assertContains(response, v, status_code=500) 179 180 def verify_safe_response(self, view): 181 """ 182 Asserts that certain sensitive info are not displayed in the response. 183 """ 184 request = self.rf.post('/some_url/', self.breakfast_data) 185 response = view(request) 186 # Non-sensitive variable's name and value are shown. 187 self.assertContains(response, 'cooked_eggs', status_code=500) 188 self.assertContains(response, 'scrambled', status_code=500) 189 # Sensitive variable's name is shown but not its value. 190 self.assertContains(response, 'sauce', status_code=500) 191 self.assertNotContains(response, 'worcestershire', status_code=500) 192 for k, v in self.breakfast_data.items(): 193 # All POST parameters' names are shown. 194 self.assertContains(response, k, status_code=500) 195 # Non-sensitive POST parameters' values are shown. 196 self.assertContains(response, 'baked-beans-value', status_code=500) 197 self.assertContains(response, 'hash-brown-value', status_code=500) 198 # Sensitive POST parameters' values are not shown. 199 self.assertNotContains(response, 'sausage-value', status_code=500) 200 self.assertNotContains(response, 'bacon-value', status_code=500) 201 202 def verify_paranoid_response(self, view): 203 """ 204 Asserts that no variables or POST parameters are displayed in the response. 205 """ 206 request = self.rf.post('/some_url/', self.breakfast_data) 207 response = view(request) 208 # Show variable names but not their values. 209 self.assertContains(response, 'cooked_eggs', status_code=500) 210 self.assertNotContains(response, 'scrambled', status_code=500) 211 self.assertContains(response, 'sauce', status_code=500) 212 self.assertNotContains(response, 'worcestershire', status_code=500) 213 for k, v in self.breakfast_data.items(): 214 # All POST parameters' names are shown. 215 self.assertContains(response, k, status_code=500) 216 # No POST parameters' values are shown. 217 self.assertNotContains(response, v, status_code=500) 218 219 def verify_unsafe_email(self, view): 220 """ 221 Asserts that potentially sensitive info are displayed in the email report. 222 """ 223 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 224 mail.outbox = [] # Empty outbox 225 request = self.rf.post('/some_url/', self.breakfast_data) 226 response = view(request) 227 self.assertEquals(len(mail.outbox), 1) 228 email = mail.outbox[0] 229 # Frames vars are never shown in plain text email reports. 230 self.assertNotIn('cooked_eggs', email.body) 231 self.assertNotIn('scrambled', email.body) 232 self.assertNotIn('sauce', email.body) 233 self.assertNotIn('worcestershire', email.body) 234 for k, v in self.breakfast_data.items(): 235 # All POST parameters are shown. 236 self.assertIn(k, email.body) 237 self.assertIn(v, email.body) 238 239 def verify_safe_email(self, view): 240 """ 241 Asserts that certain sensitive info are not displayed in the email report. 242 """ 243 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 244 mail.outbox = [] # Empty outbox 245 request = self.rf.post('/some_url/', self.breakfast_data) 246 response = view(request) 247 self.assertEquals(len(mail.outbox), 1) 248 email = mail.outbox[0] 249 # Frames vars are never shown in plain text email reports. 250 self.assertNotIn('cooked_eggs', email.body) 251 self.assertNotIn('scrambled', email.body) 252 self.assertNotIn('sauce', email.body) 253 self.assertNotIn('worcestershire', email.body) 254 for k, v in self.breakfast_data.items(): 255 # All POST parameters' names are shown. 256 self.assertIn(k, email.body) 257 # Non-sensitive POST parameters' values are shown. 258 self.assertIn('baked-beans-value', email.body) 259 self.assertIn('hash-brown-value', email.body) 260 # Sensitive POST parameters' values are not shown. 261 self.assertNotIn('sausage-value', email.body) 262 self.assertNotIn('bacon-value', email.body) 263 264 def verify_paranoid_email(self, view): 265 """ 266 Asserts that no variables or POST parameters are displayed in the email report. 267 """ 268 with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)): 269 mail.outbox = [] # Empty outbox 270 request = self.rf.post('/some_url/', self.breakfast_data) 271 response = view(request) 272 self.assertEquals(len(mail.outbox), 1) 273 email = mail.outbox[0] 274 # Frames vars are never shown in plain text email reports. 275 self.assertNotIn('cooked_eggs', email.body) 276 self.assertNotIn('scrambled', email.body) 277 self.assertNotIn('sauce', email.body) 278 self.assertNotIn('worcestershire', email.body) 279 for k, v in self.breakfast_data.items(): 280 # All POST parameters' names are shown. 281 self.assertIn(k, email.body) 282 # No POST parameters' values are shown. 283 self.assertNotIn(v, email.body) 284 285 def test_non_sensitive_request(self): 286 """ 287 Ensure that everything (request info and frame variables) can bee seen 288 in the default error reports for non-sensitive requests. 289 """ 290 with self.settings(DEBUG=True): 291 self.verify_unsafe_response(non_sensitive_view) 292 self.verify_unsafe_email(non_sensitive_view) 293 294 with self.settings(DEBUG=False): 295 self.verify_unsafe_response(non_sensitive_view) 296 self.verify_unsafe_email(non_sensitive_view) 297 298 def test_sensitive_request(self): 299 """ 300 Ensure that sensitive POST parameters and frame variables cannot be 301 seen in the default error reports for sensitive requests. 302 """ 303 with self.settings(DEBUG=True): 304 self.verify_unsafe_response(sensitive_view) 305 self.verify_unsafe_email(sensitive_view) 306 307 with self.settings(DEBUG=False): 308 self.verify_safe_response(sensitive_view) 309 self.verify_safe_email(sensitive_view) 310 311 def test_paranoid_request(self): 312 """ 313 Ensure that no POST parameters and frame variables can be seen in the 314 default error reports for "paranoid" requests. 315 """ 316 with self.settings(DEBUG=True): 317 self.verify_unsafe_response(paranoid_view) 318 self.verify_unsafe_email(paranoid_view) 319 320 with self.settings(DEBUG=False): 321 self.verify_paranoid_response(paranoid_view) 322 self.verify_paranoid_email(paranoid_view) 323 324 def test_custom_exception_reporter_filter(self): 325 """ 326 Ensure that it's possible to assign an exception reporter filter to 327 the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER. 328 """ 329 with self.settings(DEBUG=True): 330 self.verify_unsafe_response(custom_exception_reporter_filter_view) 331 self.verify_unsafe_email(custom_exception_reporter_filter_view) 332 333 with self.settings(DEBUG=False): 334 self.verify_unsafe_response(custom_exception_reporter_filter_view) 335 self.verify_unsafe_email(custom_exception_reporter_filter_view) -
tests/regressiontests/views/views.py
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py index 11d289f..6c2f324 100644
a b from django.http import HttpResponse, HttpResponseRedirect 5 5 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 from django.views.debug import technical_500_response 8 from django.views.debug import technical_500_response, SafeExceptionReporterFilter 9 from django.views.decorators.debug 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) 187 188 class UnsafeExceptionReporterFilter(SafeExceptionReporterFilter): 189 """ 190 Ignores all the filtering done by its parent class. 191 """ 192 193 def get_post_parameters(self, request): 194 return request.POST 195 196 def get_traceback_frame_variables(self, request, tb_frame): 197 return tb_frame.f_locals.items() 198 199 200 @sensitive_variables() 201 @sensitive_post_parameters() 202 def custom_exception_reporter_filter_view(request): 203 # Do not just use plain strings for the variables' values in the code 204 # so that the tests don't return false positives when the function's source 205 # is displayed in the exception report. 206 cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd']) 207 sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']) 208 request.exception_reporter_filter = UnsafeExceptionReporterFilter() 209 try: 210 raise Exception 211 except Exception: 212 exc_info = sys.exc_info() 213 send_log(request, exc_info) 214 return technical_500_response(request, *exc_info)