### Eclipse Workspace Patch 1.0
#P django-the-trunk
Index: tests/regressiontests/middleware/tests.py
===================================================================
--- tests/regressiontests/middleware/tests.py	(revision 15796)
+++ tests/regressiontests/middleware/tests.py	(working copy)
@@ -2,6 +2,8 @@
 
 from django.conf import settings
 from django.http import HttpRequest
+from django.http import HttpResponse
+from django.middleware.clickjacking import XFrameOptionsMiddleware
 from django.middleware.common import CommonMiddleware
 from django.middleware.http import ConditionalGetMiddleware
 from django.test import TestCase
@@ -334,3 +336,118 @@
         self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT'
         self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
         self.assertEqual(self.resp.status_code, 200)
+
+class XFrameOptionsMiddlewareTest(TestCase):
+    def tearDown(self):
+        if hasattr(settings, 'X_FRAME_OPTIONS'):
+            delattr(settings, 'X_FRAME_OPTIONS')
+    
+    def test_same_origin(self):
+        """
+        Tests that the X_FRAME_OPTIONS setting can be set to SAMEORIGIN to
+        have the middleware use that value for the HTTP header.
+        """
+        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(), 
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+        
+        settings.X_FRAME_OPTIONS = 'sameorigin'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+    
+    def test_deny(self):
+        """
+        Tests that the X_FRAME_OPTIONS setting can be set to DENY to
+        have the middleware use that value for the HTTP header.
+        """
+        settings.X_FRAME_OPTIONS = 'DENY'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+        
+        settings.X_FRAME_OPTIONS = 'deny'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+    
+    def test_defaults_deny(self):
+        """
+        Tests that if the X_FRAME_OPTIONS setting is not set then it defaults
+        to DENY.
+        """
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+        
+    def test_dont_set_if_set(self):
+        """
+        Tests that if the X-FRAME-OPTIONS header is already set then the
+        middleware does not attempt to override it.
+        """
+        settings.X_FRAME_OPTIONS = 'DENY'
+        response = HttpResponse()
+        response['X-FRAME-OPTIONS'] = 'SAMEORIGIN'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       response)
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+        
+        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
+        response = HttpResponse()
+        response['X-FRAME-OPTIONS'] = 'DENY'
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       response)
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+
+    def test_response_exempt(self):
+        """
+        Tests that if the response has a xframe_options_exempt attribute set 
+        to False then it still sets the header, but if it's set to True then 
+        it does not.
+        """
+        settings.X_FRAME_OPTIONS = 'DENY'
+        response = HttpResponse()
+        response.xframe_options_exempt = False
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       response)
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+        
+        response = HttpResponse()
+        response.xframe_options_exempt = True
+        r = XFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       response)
+        self.assertEqual(r.get('X-FRAME-OPTIONS', None), None)
+        
+    def test_is_extendable(self):
+        """
+        Tests that the XFrameOptionsMiddleware method that determines the 
+        X-FRAME-OPTIONS header value can be overridden based on something in 
+        the request or response.
+        """
+        class OtherXFrameOptionsMiddleware(XFrameOptionsMiddleware):
+            # This is just an example for testing purposes...
+            def get_xframe_options_value(self, request, response):
+                if getattr(request, 'sameorigin', False):
+                    return 'SAMEORIGIN'
+                if getattr(response, 'sameorigin', False):
+                    return 'SAMEORIGIN'
+                return 'DENY'
+
+        settings.X_FRAME_OPTIONS = 'DENY'
+        response = HttpResponse()
+        response.sameorigin = True
+        r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                            response)
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+        
+        request = HttpRequest()
+        request.sameorigin = True
+        r = OtherXFrameOptionsMiddleware().process_response(request,
+                                                            HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+        
+        settings.X_FRAME_OPTIONS = 'SAMEORIGIN'
+        r = OtherXFrameOptionsMiddleware().process_response(HttpRequest(),
+                                                       HttpResponse())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
Index: tests/regressiontests/decorators/tests.py
===================================================================
--- tests/regressiontests/decorators/tests.py	(revision 15796)
+++ tests/regressiontests/decorators/tests.py	(working copy)
@@ -13,6 +13,8 @@
 from django.views.decorators.http import require_http_methods, require_GET, require_POST
 from django.views.decorators.vary import vary_on_headers, vary_on_cookie
 from django.views.decorators.cache import cache_page, never_cache, cache_control
+from django.views.decorators.clickjacking import xframe_options_deny, xframe_options_sameorigin, xframe_options_exempt
+from django.middleware.clickjacking import XFrameOptionsMiddleware
 
 
 def fully_decorated(request):
@@ -183,3 +185,44 @@
 
         self.assertEqual(Test.method.__doc__, 'A method')
         self.assertEqual(Test.method.im_func.__name__, 'method')
+
+class XFrameOptionsDecoratorsTests(TestCase):
+    """
+    Tests for the X-FRAME-OPTIONS decorators.
+    """
+    def test_deny_decorator(self):
+        """
+        Ensures @xframe_options_deny properly sets the X-FRAME-OPTIONS header.
+        """
+        @xframe_options_deny
+        def a_view(request):
+            return HttpResponse()
+        r = a_view(HttpRequest())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'DENY')
+    
+    def test_sameorigin_decorator(self):
+        """
+        Ensures @xframe_options_sameorigin properly sets the X-FRAME-OPTIONS
+        header.
+        """
+        @xframe_options_sameorigin
+        def a_view(request):
+            return HttpResponse()
+        r = a_view(HttpRequest())
+        self.assertEqual(r['X-FRAME-OPTIONS'], 'SAMEORIGIN')
+
+    def test_exempt_decorator(self):
+        """
+        Ensures @xframe_options_exempt properly instructs the 
+        XFrameOptionsMiddleware to NOT set the header.
+        """
+        @xframe_options_exempt
+        def a_view(request):
+            return HttpResponse()
+        req = HttpRequest()
+        resp = a_view(req)
+        self.assertEqual(resp.get('X-FRAME-OPTIONS', None), None)
+        self.assertTrue(resp.xframe_options_exempt)
+        
+        r = XFrameOptionsMiddleware().process_response(req, resp)
+        self.assertEqual(r.get('X-FRAME-OPTIONS', None), None)
Index: django/views/decorators/clickjacking.py
===================================================================
--- django/views/decorators/clickjacking.py	(revision 0)
+++ django/views/decorators/clickjacking.py	(revision 0)
@@ -0,0 +1,64 @@
+from django.utils.decorators import available_attrs
+
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.4 fallback.
+
+def xframe_options_deny(view_func):
+    """
+    Modifies a view function so its response has the X-FRAME-OPTIONS HTTP 
+    header set to 'DENY' as long as the response doesn't already have that
+    header set.
+    
+    e.g.
+    
+    @xframe_options_deny
+    def some_view(request):
+        ...
+
+    """
+    def wrapped_view(*args, **kwargs):
+        resp = view_func(*args, **kwargs)
+        if resp.get('X-FRAME-OPTIONS', None) is None:
+            resp['X-FRAME-OPTIONS'] = 'DENY'
+        return resp
+    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
+
+def xframe_options_sameorigin(view_func):
+    """
+    Modifies a view function so its response has the X-FRAME-OPTIONS HTTP 
+    header set to 'SAMEORIGIN' as long as the response doesn't already have 
+    that header set.
+    
+    e.g.
+    
+    @xframe_options_sameorigin
+    def some_view(request):
+        ...
+
+    """
+    def wrapped_view(*args, **kwargs):
+        resp = view_func(*args, **kwargs)
+        if resp.get('X-FRAME-OPTIONS', None) is None:
+            resp['X-FRAME-OPTIONS'] = 'SAMEORIGIN'
+        return resp
+    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
+
+def xframe_options_exempt(view_func):
+    """
+    Modifies a view function by setting a response variable that instructs
+    XFrameOptionsMiddleware to NOT set the X-FRAME-OPTIONS HTTP header.
+    
+    e.g.
+    
+    @xframe_options_exempt
+    def some_view(request):
+        ...
+
+    """
+    def wrapped_view(*args, **kwargs):
+        resp = view_func(*args, **kwargs)
+        resp.xframe_options_exempt = True
+        return resp
+    return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
Index: django/middleware/clickjacking.py
===================================================================
--- django/middleware/clickjacking.py	(revision 0)
+++ django/middleware/clickjacking.py	(revision 0)
@@ -0,0 +1,50 @@
+"""
+Clickjacking Protection Middleware.
+
+This module provides a middleware that implements protection against a
+malicious site loading your site in a hidden iframe.
+"""
+
+from django.conf import settings
+
+class XFrameOptionsMiddleware(object):
+    """
+    Middleware that sets the X-Frame-Options HTTP header in HTTP responses.
+
+    Does not set the header if it's already set or if the response contains
+    a xframe_options_exempt value set to True.
+
+    By default, sets the X-Frame-Options header to 'DENY'. To have this
+    middleware set it to 'SAMEORIGIN' instead, set X_FRAME_OPTIONS in
+    Django settings to 'SAMEORIGIN'.
+
+    Note: older browsers will quietly ignore this header, thus other
+    clickjacking protection techniques should be used if protection in those
+    browsers is required.
+    
+    http://en.wikipedia.org/wiki/Clickjacking#Server_and_client
+    """
+    def process_response(self, request, response):
+        # Don't set it if it's already in the response
+        if response.get('X-FRAME-OPTIONS', None) is not None:
+            return response
+        
+        # Don't set it if they used @xframe_options_exempt
+        if getattr(response, 'xframe_options_exempt', False):
+            return response
+
+        response['X-FRAME-OPTIONS'] = self.get_xframe_options_value(request,
+                                                                    response)
+        return response
+
+    def get_xframe_options_value(self, request, response):
+        """
+        Gets the value to set for the X_FRAME_OPTIONS header.
+        
+        By default uses the value from the X_FRAME_OPTIONS Django settings. If
+        not found in settings, defaults to 'DENY'.
+        
+        This method can be overridden if needing to flex based on the request
+        or response.
+        """
+        return getattr(settings, 'X_FRAME_OPTIONS', 'DENY').upper()
