Ticket #14261: clickjack.diff

File clickjack.diff, 12.4 KB (added by rniemeyer, 13 years ago)

Improved patch: added decorators, tests and some flexibility

  • tests/regressiontests/middleware/tests.py

    ### Eclipse Workspace Patch 1.0
    #P django-the-trunk
     
    22
    33from django.conf import settings
    44from django.http import HttpRequest
     5from django.http import HttpResponse
     6from django.middleware.clickjacking import XFrameOptionsMiddleware
    57from django.middleware.common import CommonMiddleware
    68from django.middleware.http import ConditionalGetMiddleware
    79from django.test import TestCase
     
    334336        self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT'
    335337        self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
    336338        self.assertEqual(self.resp.status_code, 200)
     339
     340class XFrameOptionsMiddlewareTest(TestCase):
     341    def tearDown(self):
     342        if hasattr(settings, 'X_FRAME_OPTIONS'):
     343            delattr(settings, 'X_FRAME_OPTIONS')
     344   
     345    def test_same_origin(self):
     346        """
     347        Tests that the X_FRAME_OPTIONS setting can be set to SAMEORIGIN to
     348        have the middleware use that value for the HTTP header.
     349        """
     350        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
     351        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     352                                                       HttpResponse())
     353        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     354       
     355        settings.X_FRAME_OPTIONS = 'sameorigin'
     356        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     357                                                       HttpResponse())
     358        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     359   
     360    def test_deny(self):
     361        """
     362        Tests that the X_FRAME_OPTIONS setting can be set to DENY to
     363        have the middleware use that value for the HTTP header.
     364        """
     365        settings.X_FRAME_OPTIONS = 'DENY'
     366        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     367                                                       HttpResponse())
     368        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     369       
     370        settings.X_FRAME_OPTIONS = 'deny'
     371        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     372                                                       HttpResponse())
     373        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     374   
     375    def test_defaults_deny(self):
     376        """
     377        Tests that if the X_FRAME_OPTIONS setting is not set then it defaults
     378        to DENY.
     379        """
     380        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     381                                                       HttpResponse())
     382        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     383       
     384    def test_dont_set_if_set(self):
     385        """
     386        Tests that if the X-FRAME-OPTIONS header is already set then the
     387        middleware does not attempt to override it.
     388        """
     389        settings.X_FRAME_OPTIONS = 'DENY'
     390        response = HttpResponse()
     391        response['X-FRAME-OPTIONS'] = 'SAMEORIGIN'
     392        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     393                                                       response)
     394        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     395       
     396        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
     397        response = HttpResponse()
     398        response['X-FRAME-OPTIONS'] = 'DENY'
     399        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     400                                                       response)
     401        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     402
     403    def test_response_exempt(self):
     404        """
     405        Tests that if the response has a xframe_options_exempt attribute set
     406        to False then it still sets the header, but if it's set to True then
     407        it does not.
     408        """
     409        settings.X_FRAME_OPTIONS = 'DENY'
     410        response = HttpResponse()
     411        response.xframe_options_exempt = False
     412        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     413                                                       response)
     414        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     415       
     416        response = HttpResponse()
     417        response.xframe_options_exempt = True
     418        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
     419                                                       response)
     420        self.assertEqual(r.get('X-FRAME-OPTIONS', None), None)
     421       
     422    def test_is_extendable(self):
     423        """
     424        Tests that the XFrameOptionsMiddleware method that determines the
     425        X-FRAME-OPTIONS header value can be overridden based on something in
     426        the request or response.
     427        """
     428        class OtherXFrameOptionsMiddleware(XFrameOptionsMiddleware):
     429            # This is just an example for testing purposes...
     430            def get_xframe_options_value(self, request, response):
     431                if getattr(request, 'sameorigin', False):
     432                    return 'SAMEORIGIN'
     433                if getattr(response, 'sameorigin', False):
     434                    return 'SAMEORIGIN'
     435                return 'DENY'
     436
     437        settings.X_FRAME_OPTIONS = 'DENY'
     438        response = HttpResponse()
     439        response.sameorigin = True
     440        r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(),
     441                                                            response)
     442        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     443       
     444        request = HttpRequest()
     445        request.sameorigin = True
     446        r = OtherXFrameOptionsMiddleware().process_response(request,
     447                                                            HttpResponse())
     448        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     449       
     450        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
     451        r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(),
     452                                                       HttpResponse())
     453        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
  • tests/regressiontests/decorators/tests.py

     
    1313from django.views.decorators.http import require_http_methods, require_GET, require_POST
    1414from django.views.decorators.vary import vary_on_headers, vary_on_cookie
    1515from django.views.decorators.cache import cache_page, never_cache, cache_control
     16from django.views.decorators.clickjacking import xframe_options_deny, xframe_options_sameorigin, xframe_options_exempt
     17from django.middleware.clickjacking import XFrameOptionsMiddleware
    1618
    1719
    1820def fully_decorated(request):
     
    183185
    184186        self.assertEqual(Test.method.__doc__, 'A method')
    185187        self.assertEqual(Test.method.im_func.__name__, 'method')
     188
     189class XFrameOptionsDecoratorsTests(TestCase):
     190    """
     191    Tests for the X-FRAME-OPTIONS decorators.
     192    """
     193    def test_deny_decorator(self):
     194        """
     195        Ensures @xframe_options_deny properly sets the X-FRAME-OPTIONS header.
     196        """
     197        @xframe_options_deny
     198        def a_view(request):
     199            return HttpResponse()
     200        r = a_view(HttpRequest())
     201        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
     202   
     203    def test_sameorigin_decorator(self):
     204        """
     205        Ensures @xframe_options_sameorigin properly sets the X-FRAME-OPTIONS
     206        header.
     207        """
     208        @xframe_options_sameorigin
     209        def a_view(request):
     210            return HttpResponse()
     211        r = a_view(HttpRequest())
     212        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
     213
     214    def test_exempt_decorator(self):
     215        """
     216        Ensures @xframe_options_exempt properly instructs the
     217        XFrameOptionsMiddleware to NOT set the header.
     218        """
     219        @xframe_options_exempt
     220        def a_view(request):
     221            return HttpResponse()
     222        req = HttpRequest()
     223        resp = a_view(req)
     224        self.assertEqual(resp.get('X-FRAME-OPTIONS', None), None)
     225        self.assertTrue(resp.xframe_options_exempt)
     226       
     227        r = XFrameOptionsMiddleware().process_response(req, resp)
     228        self.assertEqual(r.get('X-FRAME-OPTIONS', None), None)
  • django/views/decorators/clickjacking.py

     
     1from django.utils.decorators import available_attrs
     2
     3try:
     4    from functools import wraps
     5except ImportError:
     6    from django.utils.functional import wraps  # Python 2.4 fallback.
     7
     8def xframe_options_deny(view_func):
     9    """
     10    Modifies a view function so its response has the X-FRAME-OPTIONS HTTP
     11    header set to 'DENY' as long as the response doesn't already have that
     12    header set.
     13   
     14    e.g.
     15   
     16    @xframe_options_deny
     17    def some_view(request):
     18        ...
     19
     20    """
     21    def wrapped_view(*args, **kwargs):
     22        resp = view_func(*args, **kwargs)
     23        if resp.get('X-FRAME-OPTIONS', None) is None:
     24            resp['X-FRAME-OPTIONS'] = 'DENY'
     25        return resp
     26    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
     27
     28def xframe_options_sameorigin(view_func):
     29    """
     30    Modifies a view function so its response has the X-FRAME-OPTIONS HTTP
     31    header set to 'SAMEORIGIN' as long as the response doesn't already have
     32    that header set.
     33   
     34    e.g.
     35   
     36    @xframe_options_sameorigin
     37    def some_view(request):
     38        ...
     39
     40    """
     41    def wrapped_view(*args, **kwargs):
     42        resp = view_func(*args, **kwargs)
     43        if resp.get('X-FRAME-OPTIONS', None) is None:
     44            resp['X-FRAME-OPTIONS'] = 'SAMEORIGIN'
     45        return resp
     46    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
     47
     48def xframe_options_exempt(view_func):
     49    """
     50    Modifies a view function by setting a response variable that instructs
     51    XFrameOptionsMiddleware to NOT set the X-FRAME-OPTIONS HTTP header.
     52   
     53    e.g.
     54   
     55    @xframe_options_exempt
     56    def some_view(request):
     57        ...
     58
     59    """
     60    def wrapped_view(*args, **kwargs):
     61        resp = view_func(*args, **kwargs)
     62        resp.xframe_options_exempt = True
     63        return resp
     64    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
  • django/middleware/clickjacking.py

     
     1"""
     2Clickjacking Protection Middleware.
     3
     4This module provides a middleware that implements protection against a
     5malicious site loading your site in a hidden iframe.
     6"""
     7
     8from django.conf import settings
     9
     10class XFrameOptionsMiddleware(object):
     11    """
     12    Middleware that sets the X-Frame-Options HTTP header in HTTP responses.
     13
     14    Does not set the header if it's already set or if the response contains
     15    a xframe_options_exempt value set to True.
     16
     17    By default, sets the X-Frame-Options header to 'DENY'. To have this
     18    middleware set it to 'SAMEORIGIN' instead, set X_FRAME_OPTIONS in
     19    Django settings to 'SAMEORIGIN'.
     20
     21    Note: older browsers will quietly ignore this header, thus other
     22    clickjacking protection techniques should be used if protection in those
     23    browsers is required.
     24   
     25    http://en.wikipedia.org/wiki/Clickjacking#Server_and_client
     26    """
     27    def process_response(self, request, response):
     28        # Don't set it if it's already in the response
     29        if response.get('X-FRAME-OPTIONS', None) is not None:
     30            return response
     31       
     32        # Don't set it if they used @xframe_options_exempt
     33        if getattr(response, 'xframe_options_exempt', False):
     34            return response
     35
     36        response['X-FRAME-OPTIONS'] = self.get_xframe_options_value(request,
     37                                                                    response)
     38        return response
     39
     40    def get_xframe_options_value(self, request, response):
     41        """
     42        Gets the value to set for the X_FRAME_OPTIONS header.
     43       
     44        By default uses the value from the X_FRAME_OPTIONS Django settings. If
     45        not found in settings, defaults to 'DENY'.
     46       
     47        This method can be overridden if needing to flex based on the request
     48        or response.
     49        """
     50        return getattr(settings, 'X_FRAME_OPTIONS', 'DENY').upper()
Back to Top