Commit f6996411 authored by Julien Phalip's avatar Julien Phalip
Browse files

Fixed #17138 -- Made the sensitive_variables decorator work with object methods.

parent f2923416
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
        Replaces the values of variables marked as sensitive with
        stars (*********).
        """
        func_name = tb_frame.f_code.co_name
        func = tb_frame.f_globals.get(func_name)
        sensitive_variables = getattr(func, 'sensitive_variables', [])
        # Loop through the frame's callers to see if the sensitive_variables
        # decorator was used.
        current_frame = tb_frame.f_back
        sensitive_variables = None
        while current_frame is not None:
            if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
                and 'sensitive_variables_wrapper' in current_frame.f_locals):
                # The sensitive_variables decorator was used, so we take note
                # of the sensitive variables' names.
                wrapper = current_frame.f_locals['sensitive_variables_wrapper']
                sensitive_variables = getattr(wrapper, 'sensitive_variables', None)
                break
            current_frame = current_frame.f_back

        cleansed = []
        if self.is_active(request) and sensitive_variables:
            if sensitive_variables == '__ALL__':
+6 −6
Original line number Diff line number Diff line
@@ -26,13 +26,13 @@ def sensitive_variables(*variables):
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
        def sensitive_variables_wrapper(*args, **kwargs):
            if variables:
                wrapper.sensitive_variables = variables
                sensitive_variables_wrapper.sensitive_variables = variables
            else:
                wrapper.sensitive_variables = '__ALL__'
                sensitive_variables_wrapper.sensitive_variables = '__ALL__'
            return func(*args, **kwargs)
        return wrapper
        return sensitive_variables_wrapper
    return decorator


@@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters):
    """
    def decorator(view):
        @functools.wraps(view)
        def wrapper(request, *args, **kwargs):
        def sensitive_post_parameters_wrapper(request, *args, **kwargs):
            if parameters:
                request.sensitive_post_parameters = parameters
            else:
                request.sensitive_post_parameters = '__ALL__'
            return view(request, *args, **kwargs)
        return wrapper
        return sensitive_post_parameters_wrapper
    return decorator
+63 −41
Original line number Diff line number Diff line
@@ -10,13 +10,12 @@ from django.test import TestCase, RequestFactory
from django.test.utils import (setup_test_template_loader,
                               restore_template_loaders)
from django.core.urlresolvers import reverse
from django.template import TemplateSyntaxError
from django.views.debug import ExceptionReporter
from django.core import mail

from .. import BrokenException, except_args
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
    custom_exception_reporter_filter_view)
    custom_exception_reporter_filter_view, sensitive_method_view)


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

    def verify_unsafe_response(self, view, check_for_vars=True):
    def verify_unsafe_response(self, view, check_for_vars=True,
                               check_for_POST_params=True):
        """
        Asserts that potentially sensitive info are displayed in the response.
        """
@@ -250,13 +250,14 @@ class ExceptionReportTestMixin(object):
            self.assertContains(response, 'scrambled', status_code=500)
            self.assertContains(response, 'sauce', status_code=500)
            self.assertContains(response, 'worcestershire', status_code=500)

        if check_for_POST_params:
            for k, v in self.breakfast_data.items():
                # All POST parameters are shown.
                self.assertContains(response, k, status_code=500)
                self.assertContains(response, v, status_code=500)

    def verify_safe_response(self, view, check_for_vars=True):
    def verify_safe_response(self, view, check_for_vars=True,
                             check_for_POST_params=True):
        """
        Asserts that certain sensitive info are not displayed in the response.
        """
@@ -269,7 +270,7 @@ class ExceptionReportTestMixin(object):
            # Sensitive variable's name is shown but not its value.
            self.assertContains(response, 'sauce', status_code=500)
            self.assertNotContains(response, 'worcestershire', status_code=500)

        if check_for_POST_params:
            for k, v in self.breakfast_data.items():
                # All POST parameters' names are shown.
                self.assertContains(response, k, status_code=500)
@@ -280,7 +281,8 @@ class ExceptionReportTestMixin(object):
            self.assertNotContains(response, 'sausage-value', status_code=500)
            self.assertNotContains(response, 'bacon-value', status_code=500)

    def verify_paranoid_response(self, view, check_for_vars=True):
    def verify_paranoid_response(self, view, check_for_vars=True,
                                 check_for_POST_params=True):
        """
        Asserts that no variables or POST parameters are displayed in the response.
        """
@@ -292,14 +294,14 @@ class ExceptionReportTestMixin(object):
            self.assertNotContains(response, 'scrambled', status_code=500)
            self.assertContains(response, 'sauce', status_code=500)
            self.assertNotContains(response, 'worcestershire', status_code=500)

        if check_for_POST_params:
            for k, v in self.breakfast_data.items():
                # All POST parameters' names are shown.
                self.assertContains(response, k, status_code=500)
                # No POST parameters' values are shown.
                self.assertNotContains(response, v, status_code=500)

    def verify_unsafe_email(self, view):
    def verify_unsafe_email(self, view, check_for_POST_params=True):
        """
        Asserts that potentially sensitive info are displayed in the email report.
        """
@@ -314,12 +316,13 @@ class ExceptionReportTestMixin(object):
            self.assertNotIn('scrambled', email.body)
            self.assertNotIn('sauce', email.body)
            self.assertNotIn('worcestershire', email.body)
            if check_for_POST_params:
                for k, v in self.breakfast_data.items():
                    # All POST parameters are shown.
                    self.assertIn(k, email.body)
                    self.assertIn(v, email.body)

    def verify_safe_email(self, view):
    def verify_safe_email(self, view, check_for_POST_params=True):
        """
        Asserts that certain sensitive info are not displayed in the email report.
        """
@@ -334,6 +337,7 @@ class ExceptionReportTestMixin(object):
            self.assertNotIn('scrambled', email.body)
            self.assertNotIn('sauce', email.body)
            self.assertNotIn('worcestershire', email.body)
            if check_for_POST_params:
                for k, v in self.breakfast_data.items():
                    # All POST parameters' names are shown.
                    self.assertIn(k, email.body)
@@ -425,6 +429,24 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin):
            self.verify_unsafe_response(custom_exception_reporter_filter_view)
            self.verify_unsafe_email(custom_exception_reporter_filter_view)

    def test_sensitive_method(self):
        """
        Ensure that the sensitive_variables decorator works with object
        methods.
        Refs #18379.
        """
        with self.settings(DEBUG=True):
            self.verify_unsafe_response(sensitive_method_view,
                                        check_for_POST_params=False)
            self.verify_unsafe_email(sensitive_method_view,
                                     check_for_POST_params=False)

        with self.settings(DEBUG=False):
            self.verify_safe_response(sensitive_method_view,
                                      check_for_POST_params=False)
            self.verify_safe_email(sensitive_method_view,
                                   check_for_POST_params=False)


class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
    """
+21 −2
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@ from __future__ import absolute_import

import sys

from django import forms
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import get_resolver
from django.http import HttpResponse, HttpResponseRedirect
@@ -14,7 +13,7 @@ from django.views.decorators.debug import (sensitive_post_parameters,
from django.utils.log import getLogger

from . import BrokenException, except_args
from .models import Article



def index_page(request):
@@ -209,3 +208,23 @@ def custom_exception_reporter_filter_view(request):
        exc_info = sys.exc_info()
        send_log(request, exc_info)
        return technical_500_response(request, *exc_info)


class Klass(object):

    @sensitive_variables('sauce')
    def method(self, request):
        # Do not just use plain strings for the variables' values in the code
        # so that the tests don't return false positives when the function's
        # source is displayed in the exception report.
        cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
        sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
        try:
            raise Exception
        except Exception:
            exc_info = sys.exc_info()
            send_log(request, exc_info)
            return technical_500_response(request, *exc_info)

def sensitive_method_view(request):
    return Klass().method(request)
 No newline at end of file