Fixed #14261 - Added clickjacking protection (X-Frame-Options header)
Many thanks to rniemeyer for the patch! git-svn-id: http://code.djangoproject.com/svn/django/trunk@16298 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dc4c2f3add
commit
524c5fa07a
1
AUTHORS
1
AUTHORS
|
@ -534,6 +534,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Jarek Zgoda <jarek.zgoda@gmail.com>
|
Jarek Zgoda <jarek.zgoda@gmail.com>
|
||||||
Cheng Zhang
|
Cheng Zhang
|
||||||
Zlatko Mašek <zlatko.masek@gmail.com>
|
Zlatko Mašek <zlatko.masek@gmail.com>
|
||||||
|
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
|
||||||
|
|
||||||
A big THANK YOU goes to:
|
A big THANK YOU goes to:
|
||||||
|
|
||||||
|
|
|
@ -406,6 +406,9 @@ URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_vers
|
||||||
DEFAULT_TABLESPACE = ''
|
DEFAULT_TABLESPACE = ''
|
||||||
DEFAULT_INDEX_TABLESPACE = ''
|
DEFAULT_INDEX_TABLESPACE = ''
|
||||||
|
|
||||||
|
# Default X-Frame-Options header value
|
||||||
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# MIDDLEWARE #
|
# MIDDLEWARE #
|
||||||
##############
|
##############
|
||||||
|
|
|
@ -98,6 +98,8 @@ MIDDLEWARE_CLASSES = (
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
# Uncomment the next line for simple clickjacking protection:
|
||||||
|
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOT_URLCONF = '{{ project_name }}.urls'
|
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
"""
|
||||||
|
Clickjacking Protection Middleware.
|
||||||
|
|
||||||
|
This module provides a middleware that implements protection against a
|
||||||
|
malicious site loading resources from your site in a hidden frame.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 'SAMEORIGIN', meaning the
|
||||||
|
response can only be loaded on a frame within the same site. To prevent the
|
||||||
|
response from being loaded in a frame in any site, set X_FRAME_OPTIONS in
|
||||||
|
your project's Django settings to 'DENY'.
|
||||||
|
|
||||||
|
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 this uses the value from the X_FRAME_OPTIONS Django
|
||||||
|
settings. If not found in settings, defaults to 'SAMEORIGIN'.
|
||||||
|
|
||||||
|
This method can be overridden if needed, allowing it to vary based on
|
||||||
|
the request or response.
|
||||||
|
"""
|
||||||
|
return getattr(settings, 'X_FRAME_OPTIONS', 'SAMEORIGIN').upper()
|
|
@ -0,0 +1,64 @@
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from django.utils.decorators import available_attrs
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
|
@ -167,8 +167,9 @@ Other batteries included
|
||||||
* :doc:`Admin site <ref/contrib/admin/index>` | :doc:`Admin actions <ref/contrib/admin/actions>` | :doc:`Admin documentation generator<ref/contrib/admin/admindocs>`
|
* :doc:`Admin site <ref/contrib/admin/index>` | :doc:`Admin actions <ref/contrib/admin/actions>` | :doc:`Admin documentation generator<ref/contrib/admin/admindocs>`
|
||||||
* :doc:`Authentication <topics/auth>`
|
* :doc:`Authentication <topics/auth>`
|
||||||
* :doc:`Cache system <topics/cache>`
|
* :doc:`Cache system <topics/cache>`
|
||||||
* :doc:`Conditional content processing <topics/conditional-view-processing>`
|
* :doc:`Clickjacking protection <ref/clickjacking>`
|
||||||
* :doc:`Comments <ref/contrib/comments/index>` | :doc:`Moderation <ref/contrib/comments/moderation>` | :doc:`Custom comments <ref/contrib/comments/custom>`
|
* :doc:`Comments <ref/contrib/comments/index>` | :doc:`Moderation <ref/contrib/comments/moderation>` | :doc:`Custom comments <ref/contrib/comments/custom>`
|
||||||
|
* :doc:`Conditional content processing <topics/conditional-view-processing>`
|
||||||
* :doc:`Content types <ref/contrib/contenttypes>`
|
* :doc:`Content types <ref/contrib/contenttypes>`
|
||||||
* :doc:`Cross Site Request Forgery protection <ref/contrib/csrf>`
|
* :doc:`Cross Site Request Forgery protection <ref/contrib/csrf>`
|
||||||
* :doc:`Cryptographic signing <topics/signing>`
|
* :doc:`Cryptographic signing <topics/signing>`
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
========================
|
||||||
|
Clickjacking Protection
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. module:: django.middleware.clickjacking
|
||||||
|
:synopsis: Protects against Clickjacking
|
||||||
|
|
||||||
|
The clickjacking middleware and decorators provide easy-to-use protection
|
||||||
|
against `clickjacking`_. This type of attack occurs when a malicious site
|
||||||
|
tricks a user into clicking on a concealed element of another site which they
|
||||||
|
have loaded in a hidden frame or iframe.
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
The clickjacking middleware and decorators were added.
|
||||||
|
|
||||||
|
.. _clickjacking: http://en.wikipedia.org/wiki/Clickjacking
|
||||||
|
|
||||||
|
An example of clickjacking
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Suppose an online store has a page where a logged in user can click "Buy Now" to
|
||||||
|
purchase an item. A user has chosen to stay logged into the store all the time
|
||||||
|
for convenience. An attacker site might create an "I Like Ponies" button on one
|
||||||
|
of their own pages, and load the store's page in a transparent iframe such that
|
||||||
|
the "Buy Now" button is invisibly overlaid on the "I Like Ponies" button. If the
|
||||||
|
user visits the attacker site and clicks "I Like Ponies" he will inadvertently
|
||||||
|
click on the online store's "Buy Now" button and unknowningly purchase the item.
|
||||||
|
|
||||||
|
Preventing clickjacking
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Modern browsers honor the `X-Frame-Options`_ HTTP header that indicates whether
|
||||||
|
or not a resource is allowed to load within a frame or iframe. If the response
|
||||||
|
contains the header with a value of SAMEORIGIN then the browser will only load
|
||||||
|
the resource in a frame if the request originated from the same site. If the
|
||||||
|
header is set to DENY then the browser will block the resource from loading in a
|
||||||
|
frame no matter which site made the request.
|
||||||
|
|
||||||
|
.. _X-Frame-Options: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header
|
||||||
|
|
||||||
|
Django provides a few simple ways to include this header in responses from your
|
||||||
|
site:
|
||||||
|
|
||||||
|
1. A simple middleware that sets the header in all responses.
|
||||||
|
|
||||||
|
2. A set of view decorators that can be used to override the middleware or to
|
||||||
|
only set the header for certain views.
|
||||||
|
|
||||||
|
How to use it
|
||||||
|
=============
|
||||||
|
|
||||||
|
Setting X-Frame-Options for all responses
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To set the same X-Frame-Options value for all responses in your site, add
|
||||||
|
``'django.middleware.clickjacking.XFrameOptionsMiddleware'`` to
|
||||||
|
:setting:`MIDDLEWARE_CLASSES`::
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
...
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
By default, the middleware will set the X-Frame-Options header to SAMEORIGIN for
|
||||||
|
every outgoing ``HttpResponse``. If you want DENY instead, set the
|
||||||
|
:setting:`X_FRAME_OPTIONS` setting::
|
||||||
|
|
||||||
|
X_FRAME_OPTIONS = 'DENY'
|
||||||
|
|
||||||
|
When using the middleware there may be some views where you do **not** want the
|
||||||
|
X-Frame-Options header set. For those cases, you can use a view decorator that
|
||||||
|
tells the middleware to not set the header::
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
|
|
||||||
|
@xframe_options_exempt
|
||||||
|
def ok_to_load_in_a_frame(request):
|
||||||
|
return HttpResponse("This page is safe to load in a frame on any site.")
|
||||||
|
|
||||||
|
|
||||||
|
Setting X-Frame-Options per view
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To set the X-Frame-Options header on a per view basis, Django provides these
|
||||||
|
decorators::
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_deny
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
|
|
||||||
|
@xframe_options_deny
|
||||||
|
def view_one(request):
|
||||||
|
return HttpResponse("I won't display in any frame!")
|
||||||
|
|
||||||
|
@xframe_options_sameorigin
|
||||||
|
def view_two(request):
|
||||||
|
return HttpResponse("Display in a frame if it's from the same origin as me.")
|
||||||
|
|
||||||
|
Note that you can use the decorators in conjunction with the middleware. Use of
|
||||||
|
a decorator overrides the middleware.
|
||||||
|
|
||||||
|
Limitations
|
||||||
|
===========
|
||||||
|
|
||||||
|
The `X-Frame-Options` header will only protect against clickjacking in a modern
|
||||||
|
browser. Older browsers will quietly ignore the header and need `other
|
||||||
|
clickjacking prevention techniques`_.
|
||||||
|
|
||||||
|
Browsers that support X-Frame-Options
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Internet Explorer 8+
|
||||||
|
* Firefox 3.6.9+
|
||||||
|
* Opera 10.5+
|
||||||
|
* Safari 4+
|
||||||
|
* Chrome 4.1+
|
||||||
|
|
||||||
|
See also
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
A `complete list`_ of browsers supporting X-Frame-Options.
|
||||||
|
|
||||||
|
.. _complete list: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header#Browser_compatibility
|
||||||
|
.. _other clickjacking prevention techniques: http://en.wikipedia.org/wiki/Clickjacking#Prevention
|
|
@ -6,6 +6,7 @@ API Reference
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
authbackends
|
authbackends
|
||||||
|
clickjacking
|
||||||
contrib/index
|
contrib/index
|
||||||
databases
|
databases
|
||||||
django-admin
|
django-admin
|
||||||
|
|
|
@ -204,3 +204,16 @@ Middleware modules running inside it (coming later in the stack) will be under
|
||||||
the same transaction control as the view functions.
|
the same transaction control as the view functions.
|
||||||
|
|
||||||
See the :doc:`transaction management documentation </topics/db/transactions>`.
|
See the :doc:`transaction management documentation </topics/db/transactions>`.
|
||||||
|
|
||||||
|
X-Frame-Options middleware
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. module:: django.middleware.clickjacking
|
||||||
|
:synopsis: Clickjacking protection
|
||||||
|
|
||||||
|
.. class:: XFrameOptionsMiddleware
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
``XFrameOptionsMiddleware`` was added.
|
||||||
|
|
||||||
|
Simple :doc:`clickjacking protection via the X-Frame-Options header </ref/clickjacking/>`.
|
||||||
|
|
|
@ -2023,6 +2023,17 @@ See :tfilter:`allowed date format strings <date>`. See also
|
||||||
:setting:`DATE_FORMAT`, :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT`
|
:setting:`DATE_FORMAT`, :setting:`DATETIME_FORMAT`, :setting:`TIME_FORMAT`
|
||||||
and :setting:`MONTH_DAY_FORMAT`.
|
and :setting:`MONTH_DAY_FORMAT`.
|
||||||
|
|
||||||
|
.. setting:: X_FRAME_OPTIONS
|
||||||
|
|
||||||
|
X_FRAME_OPTIONS
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Default: ``'SAMEORIGIN'``
|
||||||
|
|
||||||
|
The default value for the X-Frame-Options header used by
|
||||||
|
:class:`~django.middleware.clickjacking.XFrameOptionsMiddleware`. See the
|
||||||
|
:doc:`clickjacking protection </ref/clickjacking/>` documentation.
|
||||||
|
|
||||||
Deprecated settings
|
Deprecated settings
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,15 @@ signing in Web applications.
|
||||||
|
|
||||||
See :doc:`cryptographic signing </topics/signing>` docs for more information.
|
See :doc:`cryptographic signing </topics/signing>` docs for more information.
|
||||||
|
|
||||||
|
Simple clickjacking protection
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
We've added a middleware to provide easy protection against `clickjacking
|
||||||
|
<http://en.wikipedia.org/wiki/Clickjacking>`_ using the X-Frame-Options
|
||||||
|
header. It's not enabled by default for backwards compatibility reasons, but
|
||||||
|
you'll almost certainly want to :doc:`enable it </ref/clickjacking/>` to help
|
||||||
|
plug that security hole for browsers that support the header.
|
||||||
|
|
||||||
``reverse_lazy``
|
``reverse_lazy``
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ from django.utils.unittest import TestCase
|
||||||
from django.views.decorators.http import require_http_methods, require_GET, require_POST, require_safe
|
from django.views.decorators.http import require_http_methods, require_GET, require_POST, require_safe
|
||||||
from django.views.decorators.vary import vary_on_headers, vary_on_cookie
|
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.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):
|
def fully_decorated(request):
|
||||||
|
@ -216,3 +218,47 @@ class MethodDecoratorTests(TestCase):
|
||||||
|
|
||||||
self.assertEqual(Test.method.__doc__, 'A method')
|
self.assertEqual(Test.method.__doc__, 'A method')
|
||||||
self.assertEqual(Test.method.im_func.__name__, '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)
|
||||||
|
|
||||||
|
# Since the real purpose of the exempt decorator is to suppress
|
||||||
|
# the middleware's functionality, let's make sure it actually works...
|
||||||
|
r = XFrameOptionsMiddleware().process_response(req, resp)
|
||||||
|
self.assertEqual(r.get('X-Frame-Options', None), None)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.http import HttpRequest
|
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.common import CommonMiddleware
|
||||||
from django.middleware.http import ConditionalGetMiddleware
|
from django.middleware.http import ConditionalGetMiddleware
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
@ -371,3 +373,125 @@ class ConditionalGetMiddlewareTest(TestCase):
|
||||||
self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT'
|
self.resp['Last-Modified'] = 'Sat, 12 Feb 2011 17:41:44 GMT'
|
||||||
self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
|
self.resp = ConditionalGetMiddleware().process_response(self.req, self.resp)
|
||||||
self.assertEqual(self.resp.status_code, 200)
|
self.assertEqual(self.resp.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class XFrameOptionsMiddlewareTest(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the X-Frame-Options clickjacking prevention middleware.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.x_frame_options = settings.X_FRAME_OPTIONS
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
settings.X_FRAME_OPTIONS = self.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_sameorigin(self):
|
||||||
|
"""
|
||||||
|
Tests that if the X_FRAME_OPTIONS setting is not set then it defaults
|
||||||
|
to SAMEORIGIN.
|
||||||
|
"""
|
||||||
|
del settings.X_FRAME_OPTIONS
|
||||||
|
r = XFrameOptionsMiddleware().process_response(HttpRequest(),
|
||||||
|
HttpResponse())
|
||||||
|
self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN')
|
||||||
|
|
||||||
|
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 = 'SAMEORIGIN'
|
||||||
|
response = HttpResponse()
|
||||||
|
response.xframe_options_exempt = False
|
||||||
|
r = XFrameOptionsMiddleware().process_response(HttpRequest(),
|
||||||
|
response)
|
||||||
|
self.assertEqual(r['X-Frame-Options'], 'SAMEORIGIN')
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
Loading…
Reference in New Issue