├── corsheaders ├── __init__.py ├── models.py ├── defaults.py ├── middleware.py └── tests.py ├── .gitignore ├── .travis.yml ├── tests.py ├── tox.ini ├── LICENSE.txt ├── setup.py └── README.md /corsheaders/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /corsheaders/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build/ 4 | dist/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.6 4 | - 2.7 5 | 6 | env: 7 | - DJANGO_VERSION="Django==1.4.5" 8 | - DJANGO_VERSION="http://www.djangoproject.com/m/releases/1.5/Django-1.5c1.tar.gz" 9 | 10 | install: 11 | - pip install -q $DJANGO_VERSION 12 | - pip install . --use-mirrors 13 | 14 | script: python setup.py test 15 | 16 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | """ 4 | import sys 5 | 6 | 7 | def run_tests(): 8 | from django.conf import global_settings 9 | from django.conf import settings 10 | settings.configure( 11 | INSTALLED_APPS=[ 12 | 'corsheaders', 13 | ], 14 | DATABASES={ 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', 17 | 'TEST_NAME': ':memory:', 18 | }, 19 | }, 20 | MIDDLEWARE_CLASSES=global_settings.MIDDLEWARE_CLASSES + ( 21 | 'corsheaders.middleware.CorsMiddleware',), 22 | ) 23 | 24 | from django.test.simple import DjangoTestSuiteRunner 25 | 26 | test_runner = DjangoTestSuiteRunner(verbosity=1) 27 | return test_runner.run_tests(['corsheaders']) 28 | 29 | 30 | def main(): 31 | failures = run_tests() 32 | sys.exit(failures) 33 | 34 | if __name__ == '__main__': 35 | main() 36 | 37 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | downloadcache = {toxworkdir}/cache/ 3 | envlist = 4 | py26-django14, 5 | py26-django15, 6 | py27-django14, 7 | py27-django15, 8 | py27-django16, 9 | py33-django16, 10 | 11 | [testenv] 12 | commands = python setup.py test 13 | 14 | [django14] 15 | deps = django == 1.4.10 16 | 17 | [django15] 18 | deps = https://www.djangoproject.com/download/1.5.5/tarball/ 19 | 20 | [django16] 21 | deps = https://www.djangoproject.com/download/1.6.2/tarball/ 22 | 23 | [testenv:py26-django14] 24 | basepython = python2.6 25 | deps = {[django14]deps} 26 | 27 | [testenv:py26-django15] 28 | basepython = python2.6 29 | deps = {[django15]deps} 30 | 31 | 32 | [testenv:py27-django14] 33 | basepython = python2.7 34 | deps = {[django14]deps} 35 | 36 | [testenv:py27-django15] 37 | basepython = python2.7 38 | deps = {[django15]deps} 39 | 40 | [testenv:py27-django16] 41 | basepython = python2.7 42 | deps = {[django16]deps} 43 | 44 | 45 | [testenv:py33-django16] 46 | basepython = python3.3 47 | deps = {[django16]deps} 48 | 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Otto Yiu and other contributors http://ottoyiu.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /corsheaders/defaults.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | default_headers = ( 4 | 'x-requested-with', 5 | 'content-type', 6 | 'accept', 7 | 'origin', 8 | 'authorization', 9 | 'x-csrftoken', 10 | ) 11 | CORS_ALLOW_HEADERS = getattr(settings, 'CORS_ALLOW_HEADERS', default_headers) 12 | 13 | default_methods = ( 14 | 'GET', 15 | 'POST', 16 | 'PUT', 17 | 'PATCH', 18 | 'DELETE', 19 | 'OPTIONS', 20 | ) 21 | CORS_ALLOW_METHODS = getattr(settings, 'CORS_ALLOW_METHODS', default_methods) 22 | 23 | CORS_ALLOW_CREDENTIALS = getattr(settings, 'CORS_ALLOW_CREDENTIALS', False) 24 | 25 | CORS_PREFLIGHT_MAX_AGE = getattr(settings, 'CORS_PREFLIGHT_MAX_AGE', 86400) 26 | 27 | CORS_ORIGIN_ALLOW_ALL = getattr(settings, 'CORS_ORIGIN_ALLOW_ALL', False) 28 | 29 | CORS_ORIGIN_WHITELIST = getattr(settings, 'CORS_ORIGIN_WHITELIST', ()) 30 | 31 | CORS_ORIGIN_REGEX_WHITELIST = getattr(settings, 'CORS_ORIGIN_REGEX_WHITELIST', ()) 32 | 33 | CORS_EXPOSE_HEADERS = getattr(settings, 'CORS_EXPOSE_HEADERS', ()) 34 | 35 | CORS_URLS_REGEX = getattr(settings, 'CORS_URLS_REGEX', '^.*$') 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='django-cors-headers', 5 | version='0.12', 6 | description='django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS).', 7 | author='Otto Yiu', 8 | author_email='otto@live.ca', 9 | url='https://github.com/ottoyiu/django-cors-headers', 10 | packages=['corsheaders'], 11 | license='MIT License', 12 | keywords='django cors middleware rest api', 13 | platforms = ['any'], 14 | classifiers = [ 15 | 'Development Status :: 5 - Production/Stable', 16 | 'Environment :: Web Environment', 17 | 'Framework :: Django', 18 | 'Intended Audience :: Developers', 19 | 'License :: OSI Approved :: MIT License', 20 | 'Operating System :: OS Independent', 21 | 'Programming Language :: Python', 22 | 'Programming Language :: Python :: 2', 23 | 'Programming Language :: Python :: 2.6', 24 | 'Programming Language :: Python :: 2.7', 25 | 'Programming Language :: Python :: 3', 26 | 'Programming Language :: Python :: 3.3', 27 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 28 | 'Topic :: Software Development :: Libraries :: Python Modules', 29 | ], 30 | install_requires=['Django >= 1.4'], 31 | tests_require=['mock >= 1.0'], 32 | test_suite='tests.main', 33 | ) 34 | -------------------------------------------------------------------------------- /corsheaders/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | from django import http 3 | try: 4 | from urlparse import urlparse 5 | except ImportError: 6 | from urllib.parse import urlparse 7 | 8 | from corsheaders import defaults as settings 9 | 10 | 11 | ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin' 12 | ACCESS_CONTROL_EXPOSE_HEADERS = 'Access-Control-Expose-Headers' 13 | ACCESS_CONTROL_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials' 14 | ACCESS_CONTROL_ALLOW_HEADERS = 'Access-Control-Allow-Headers' 15 | ACCESS_CONTROL_ALLOW_METHODS = 'Access-Control-Allow-Methods' 16 | ACCESS_CONTROL_MAX_AGE = 'Access-Control-Max-Age' 17 | 18 | 19 | class CorsMiddleware(object): 20 | 21 | def process_request(self, request): 22 | ''' 23 | If CORS preflight header, then create an empty body response (200 OK) and return it 24 | 25 | Django won't bother calling any other request view/exception middleware along with 26 | the requested view; it will call any response middlewares 27 | ''' 28 | if (self.is_enabled(request) and 29 | request.method == 'OPTIONS' and 30 | 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META): 31 | response = http.HttpResponse() 32 | return response 33 | return None 34 | 35 | def process_response(self, request, response): 36 | ''' 37 | Add the respective CORS headers 38 | ''' 39 | origin = request.META.get('HTTP_ORIGIN') 40 | if self.is_enabled(request) and origin: 41 | # todo: check hostname from db instead 42 | url = urlparse(origin) 43 | 44 | if not settings.CORS_ORIGIN_ALLOW_ALL and self.origin_not_found_in_white_lists(origin, url): 45 | return response 46 | 47 | response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*" if settings.CORS_ORIGIN_ALLOW_ALL else origin 48 | 49 | if len(settings.CORS_EXPOSE_HEADERS): 50 | response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(settings.CORS_EXPOSE_HEADERS) 51 | 52 | if settings.CORS_ALLOW_CREDENTIALS: 53 | response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true' 54 | 55 | if request.method == 'OPTIONS': 56 | response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(settings.CORS_ALLOW_HEADERS) 57 | response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(settings.CORS_ALLOW_METHODS) 58 | if settings.CORS_PREFLIGHT_MAX_AGE: 59 | response[ACCESS_CONTROL_MAX_AGE] = settings.CORS_PREFLIGHT_MAX_AGE 60 | 61 | return response 62 | 63 | def origin_not_found_in_white_lists(self, origin, url): 64 | return url.netloc not in settings.CORS_ORIGIN_WHITELIST and not self.regex_domain_match(origin) 65 | 66 | def regex_domain_match(self, origin): 67 | for domain_pattern in settings.CORS_ORIGIN_REGEX_WHITELIST: 68 | if re.match(domain_pattern, origin): 69 | return origin 70 | 71 | def is_enabled(self, request): 72 | return re.match(settings.CORS_URLS_REGEX, request.path) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-cors-headers 2 | ================== 3 | 4 | A Django App that adds CORS (Cross-Origin Resource Sharing) headers to responses. 5 | 6 | Although JSON-P is useful, it is strictly limited to GET requests. CORS builds on top of XmlHttpRequest to allow developers to make cross-domain requests, similar to same-domain requests. Read more about it here: [http://www.html5rocks.com/en/tutorials/cors/ ](http://www.html5rocks.com/en/tutorials/cors/) 7 | 8 | [![Build Status](https://travis-ci.org/ottoyiu/django-cors-headers.png?branch=master)](https://travis-ci.org/ottoyiu/django-cors-headers) 9 | 10 | ## Setup ## 11 | Install by downloading the source and running: 12 | 13 | > python setup.py install 14 | 15 | or 16 | 17 | > pip install django-cors-headers 18 | 19 | and then add it to your installed apps: 20 | 21 | INSTALLED_APPS = ( 22 | ... 23 | 'corsheaders', 24 | ... 25 | ) 26 | 27 | You will also need to add a middleware class to listen in on responses: 28 | 29 | MIDDLEWARE_CLASSES = ( 30 | ... 31 | 'corsheaders.middleware.CorsMiddleware', 32 | 'django.middleware.common.CommonMiddleware', 33 | ... 34 | ) 35 | 36 | Note that `CorsMiddleware` needs to come before Django's `CommonMiddleware` if you are using Django's `USE_ETAGS = True` setting, otherwise the CORS headers will be lost from the 304 not-modified responses, causing errors in some browsers. 37 | 38 | ## Configuration ## 39 | 40 | Add hosts that are allowed to do cross-site requests to `CORS_ORIGIN_WHITELIST` or set `CORS_ORIGIN_ALLOW_ALL` to `True` to allow all hosts. 41 | 42 | 43 | >CORS\_ORIGIN\_ALLOW\_ALL: if True, the whitelist will not be used and all origins will be accepted 44 | 45 | Default: 46 | 47 | CORS_ORIGIN_ALLOW_ALL = False 48 | 49 | >CORS\_ORIGIN\_WHITELIST: specify a list of origin hostnames that are authorized to make a cross-site HTTP request; set to None to allow access to anyone 50 | 51 | Example: 52 | 53 | CORS_ORIGIN_WHITELIST = ( 54 | 'google.com', 55 | 'hostname.example.com' 56 | ) 57 | 58 | 59 | Default: 60 | 61 | CORS_ORIGIN_WHITELIST = () 62 | 63 | >CORS\_ORIGIN\_REGEX\_WHITELIST: specify a regex list of origin hostnames that are authorized to make a cross-site HTTP request; Useful when you have a large amount of subdomains for instance. 64 | 65 | Example: 66 | 67 | CORS_ORIGIN_REGEX_WHITELIST = ('^http?://(\w+\.)?google\.com$', ) 68 | 69 | 70 | Default: 71 | 72 | CORS_ORIGIN_REGEX_WHITELIST = () 73 | 74 | 75 | --- 76 | 77 | 78 | You may optionally specify these options in settings.py to override the defaults. Defaults are shown below: 79 | 80 | 81 | >CORS\_URLS\_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under ``/api/``. 82 | 83 | Example: 84 | 85 | CORS_URLS_REGEX = r'^/api/.*$' 86 | 87 | Default: 88 | 89 | CORS_URLS_REGEX = '^.*$' 90 | 91 | >CORS\_ALLOW\_METHODS: specify the allowed HTTP methods that can be used when making the actual request 92 | 93 | Default: 94 | 95 | CORS_ALLOW_METHODS = ( 96 | 'GET', 97 | 'POST', 98 | 'PUT', 99 | 'PATCH', 100 | 'DELETE', 101 | 'OPTIONS' 102 | ) 103 | 104 | >CORS\_ALLOW\_HEADERS: specify which non-standard HTTP headers can be used when making the actual request 105 | 106 | Default: 107 | 108 | CORS_ALLOW_HEADERS = ( 109 | 'x-requested-with', 110 | 'content-type', 111 | 'accept', 112 | 'origin', 113 | 'authorization', 114 | 'x-csrftoken' 115 | ) 116 | 117 | >CORS\_EXPOSE\_HEADERS: specify which HTTP headers are to be exposed to the browser 118 | 119 | Default: 120 | 121 | CORS_EXPOSE_HEADERS = () 122 | 123 | >CORS\_PREFLIGHT\_MAX\_AGE: specify the number of seconds a client/browser can cache the preflight response 124 | 125 | Note: A preflight request is an extra request that is made when making a "not-so-simple" request (eg. content-type is not application/x-www-form-urlencoded) to determine what requests the server actually accepts. Read more about it here: [http://www.html5rocks.com/en/tutorials/cors/](http://www.html5rocks.com/en/tutorials/cors/) 126 | 127 | Default: 128 | 129 | CORS_PREFLIGHT_MAX_AGE = 86400 130 | 131 | >CORS\_ALLOW\_CREDENTIALS: specify whether or not cookies are allowed to be included in cross-site HTTP requests (CORS). 132 | 133 | Default: 134 | 135 | CORS_ALLOW_CREDENTIALS = False 136 | 137 | ## Changelog ## 138 | v0.12 - Added an option to selectively enable CORS only for specific URLs 139 | 140 | v0.11 - Added the ability to specify a regex for whitelisting many origin hostnames at once 141 | 142 | v0.10 - Introduced port distinction for origin checking; use ``urlparse`` for Python 3 support; added testcases to project 143 | 144 | v0.06 - Add support for exposed response headers 145 | 146 | v0.05 - fixed middleware to ensure correct response for CORS preflight requests 147 | 148 | v0.04 - add Access-Control-Allow-Credentials control to simple requests 149 | 150 | v0.03 - bugfix (repair mismatched default variable names) 151 | 152 | v0.02 - refactor/pull defaults into separate file 153 | 154 | v0.01 - initial release 155 | 156 | ## Credits ## 157 | A shoutout to everyone who has contributed: 158 | 159 | - Otto Yiu - [@ottoyiu](https://github.com/ottoyiu) 160 | - Michael Tom-Wing - [@mtomwing](https://github.com/mtomwing) 161 | - Darrin Massena - [@darrinm](https://github.com/darrinm) 162 | - Paul Dufour - [@pdufour](https://github.com/pdufour) 163 | - Lukasz Balcerzak - [@lukaszb](https://github.com/lukaszb) 164 | - Keita Oouchi - [@keitaoouchi](https://github.com/keitaoouchi) 165 | - Orlando Pozo - [@opozo](https://github.com/opozo) 166 | - Toran Billups - [@toranb](https://github.com/toranb) 167 | - Raymond Penners - [@pennersr](https://github.com/pennersr) 168 | - Markus Kaiserswerth - [@mkai](https://github.com/mkai) 169 | -------------------------------------------------------------------------------- /corsheaders/tests.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.test import TestCase 3 | from corsheaders.middleware import CorsMiddleware 4 | from corsheaders.middleware import ACCESS_CONTROL_ALLOW_ORIGIN 5 | from corsheaders.middleware import ACCESS_CONTROL_EXPOSE_HEADERS 6 | from corsheaders.middleware import ACCESS_CONTROL_ALLOW_CREDENTIALS 7 | from corsheaders.middleware import ACCESS_CONTROL_ALLOW_HEADERS 8 | from corsheaders.middleware import ACCESS_CONTROL_ALLOW_METHODS 9 | from corsheaders.middleware import ACCESS_CONTROL_MAX_AGE 10 | from corsheaders import defaults as settings 11 | from mock import Mock 12 | from mock import patch 13 | 14 | 15 | class settings_override(object): 16 | def __init__(self, **kwargs): 17 | self.overrides = kwargs 18 | 19 | def __enter__(self): 20 | self.old = dict((key, getattr(settings, key)) for key in self.overrides) 21 | settings.__dict__.update(self.overrides) 22 | 23 | def __exit__(self, exc, value, tb): 24 | settings.__dict__.update(self.old) 25 | 26 | 27 | class TestCorsMiddlewareProcessRequest(TestCase): 28 | 29 | def setUp(self): 30 | self.middleware = CorsMiddleware() 31 | 32 | def test_process_request(self): 33 | request = Mock(path='/') 34 | request.method = 'OPTIONS' 35 | request.META = {'HTTP_ACCESS_CONTROL_REQUEST_METHOD': 'value'} 36 | with settings_override(CORS_URLS_REGEX='^.*$'): 37 | response = self.middleware.process_request(request) 38 | self.assertIsInstance(response, HttpResponse) 39 | 40 | def test_process_request_empty_header(self): 41 | request = Mock(path='/') 42 | request.method = 'OPTIONS' 43 | request.META = {'HTTP_ACCESS_CONTROL_REQUEST_METHOD': ''} 44 | with settings_override(CORS_URLS_REGEX='^.*$'): 45 | response = self.middleware.process_request(request) 46 | self.assertIsInstance(response, HttpResponse) 47 | 48 | def test_process_request_no_header(self): 49 | request = Mock(path='/') 50 | request.method = 'OPTIONS' 51 | request.META = {} 52 | response = self.middleware.process_request(request) 53 | self.assertIsNone(response) 54 | 55 | def test_process_request_not_options(self): 56 | request = Mock(path='/') 57 | request.method = 'GET' 58 | request.META = {'HTTP_ACCESS_CONTROL_REQUEST_METHOD': 'value'} 59 | response = self.middleware.process_request(request) 60 | self.assertIsNone(response) 61 | 62 | 63 | @patch('corsheaders.middleware.settings') 64 | class TestCorsMiddlewareProcessResponse(TestCase): 65 | 66 | def setUp(self): 67 | self.middleware = CorsMiddleware() 68 | 69 | def assertAccessControlAllowOriginEquals(self, response, header): 70 | self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, response, "Response %r does " 71 | "NOT have %r header" % (response, ACCESS_CONTROL_ALLOW_ORIGIN)) 72 | self.assertEqual(response[ACCESS_CONTROL_ALLOW_ORIGIN], header) 73 | 74 | def test_process_response_no_origin(self, settings): 75 | settings.CORS_URLS_REGEX = '^.*$' 76 | response = HttpResponse() 77 | request = Mock(path='/', META={}) 78 | processed = self.middleware.process_response(request, response) 79 | self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, processed) 80 | 81 | def test_process_response_not_in_whitelist(self, settings): 82 | settings.CORS_ORIGIN_ALLOW_ALL = False 83 | settings.CORS_ORIGIN_WHITELIST = ['example.com'] 84 | settings.CORS_URLS_REGEX = '^.*$' 85 | response = HttpResponse() 86 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://foobar.it'}) 87 | processed = self.middleware.process_response(request, response) 88 | self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, processed) 89 | 90 | def test_process_response_in_whitelist(self, settings): 91 | settings.CORS_ORIGIN_ALLOW_ALL = False 92 | settings.CORS_ORIGIN_WHITELIST = ['example.com', 'foobar.it'] 93 | settings.CORS_URLS_REGEX = '^.*$' 94 | response = HttpResponse() 95 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://foobar.it'}) 96 | processed = self.middleware.process_response(request, response) 97 | self.assertAccessControlAllowOriginEquals(processed, 'http://foobar.it') 98 | 99 | def test_process_response_expose_headers(self, settings): 100 | settings.CORS_ORIGIN_ALLOW_ALL = True 101 | settings.CORS_EXPOSE_HEADERS = ['accept', 'origin', 'content-type'] 102 | settings.CORS_URLS_REGEX = '^.*$' 103 | response = HttpResponse() 104 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://example.com'}) 105 | processed = self.middleware.process_response(request, response) 106 | self.assertEqual(processed[ACCESS_CONTROL_EXPOSE_HEADERS], 107 | 'accept, origin, content-type') 108 | 109 | def test_process_response_dont_expose_headers(self, settings): 110 | settings.CORS_ORIGIN_ALLOW_ALL = True 111 | settings.CORS_EXPOSE_HEADERS = [] 112 | settings.CORS_URLS_REGEX = '^.*$' 113 | response = HttpResponse() 114 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://example.com'}) 115 | processed = self.middleware.process_response(request, response) 116 | self.assertNotIn(ACCESS_CONTROL_EXPOSE_HEADERS, processed) 117 | 118 | def test_process_response_allow_credentials(self, settings): 119 | settings.CORS_ORIGIN_ALLOW_ALL = True 120 | settings.CORS_ALLOW_CREDENTIALS = True 121 | settings.CORS_URLS_REGEX = '^.*$' 122 | response = HttpResponse() 123 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://example.com'}) 124 | processed = self.middleware.process_response(request, response) 125 | self.assertEqual(processed[ACCESS_CONTROL_ALLOW_CREDENTIALS], 'true') 126 | 127 | def test_process_response_dont_allow_credentials(self, settings): 128 | settings.CORS_ORIGIN_ALLOW_ALL = True 129 | settings.CORS_ALLOW_CREDENTIALS = False 130 | settings.CORS_URLS_REGEX = '^.*$' 131 | response = HttpResponse() 132 | request = Mock(path='/', META={'HTTP_ORIGIN': 'http://example.com'}) 133 | processed = self.middleware.process_response(request, response) 134 | self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, processed) 135 | 136 | def test_process_response_options_method(self, settings): 137 | settings.CORS_ORIGIN_ALLOW_ALL = True 138 | settings.CORS_ALLOW_HEADERS = ['content-type', 'origin'] 139 | settings.CORS_ALLOW_METHODS = ['GET', 'OPTIONS'] 140 | settings.CORS_PREFLIGHT_MAX_AGE = 1002 141 | settings.CORS_URLS_REGEX = '^.*$' 142 | response = HttpResponse() 143 | request_headers = {'HTTP_ORIGIN': 'http://example.com'} 144 | request = Mock(path='/', META=request_headers, method='OPTIONS') 145 | processed = self.middleware.process_response(request, response) 146 | self.assertEqual(processed[ACCESS_CONTROL_ALLOW_HEADERS], 147 | 'content-type, origin') 148 | self.assertEqual(processed[ACCESS_CONTROL_ALLOW_METHODS], 'GET, OPTIONS') 149 | self.assertEqual(processed[ACCESS_CONTROL_MAX_AGE], '1002') 150 | 151 | def test_process_response_options_method_no_max_age(self, settings): 152 | settings.CORS_ORIGIN_ALLOW_ALL = True 153 | settings.CORS_ALLOW_HEADERS = ['content-type', 'origin'] 154 | settings.CORS_ALLOW_METHODS = ['GET', 'OPTIONS'] 155 | settings.CORS_PREFLIGHT_MAX_AGE = 0 156 | settings.CORS_URLS_REGEX = '^.*$' 157 | response = HttpResponse() 158 | request_headers = {'HTTP_ORIGIN': 'http://example.com'} 159 | request = Mock(path='/', META=request_headers, method='OPTIONS') 160 | processed = self.middleware.process_response(request, response) 161 | self.assertEqual(processed[ACCESS_CONTROL_ALLOW_HEADERS], 162 | 'content-type, origin') 163 | self.assertEqual(processed[ACCESS_CONTROL_ALLOW_METHODS], 'GET, OPTIONS') 164 | self.assertNotIn(ACCESS_CONTROL_MAX_AGE, processed) 165 | 166 | def test_process_response_whitelist_with_port(self, settings): 167 | settings.CORS_ORIGIN_ALLOW_ALL = False 168 | settings.CORS_ALLOW_METHODS = ['OPTIONS'] 169 | settings.CORS_ORIGIN_WHITELIST = ('localhost:9000',) 170 | settings.CORS_URLS_REGEX = '^.*$' 171 | response = HttpResponse() 172 | request_headers = {'HTTP_ORIGIN': 'http://localhost:9000'} 173 | request = Mock(path='/', META=request_headers, method='OPTIONS') 174 | processed = self.middleware.process_response(request, response) 175 | self.assertEqual(processed.get(ACCESS_CONTROL_ALLOW_CREDENTIALS), 'true') 176 | 177 | def test_process_response_adds_origin_when_domain_found_in_origin_regex_whitelist(self, settings): 178 | settings.CORS_ORIGIN_REGEX_WHITELIST = ('^http?://(\w+\.)?google\.com$', ) 179 | settings.CORS_ALLOW_CREDENTIALS = True 180 | settings.CORS_ORIGIN_ALLOW_ALL = False 181 | settings.CORS_ALLOW_METHODS = ['OPTIONS'] 182 | settings.CORS_URLS_REGEX = '^.*$' 183 | response = HttpResponse() 184 | request_headers = {'HTTP_ORIGIN': 'http://foo.google.com'} 185 | request = Mock(path='/', META=request_headers, method='OPTIONS') 186 | processed = self.middleware.process_response(request, response) 187 | self.assertEqual(processed.get(ACCESS_CONTROL_ALLOW_ORIGIN), 'http://foo.google.com') 188 | 189 | def test_process_response_will_not_add_origin_when_domain_not_found_in_origin_regex_whitelist(self, settings): 190 | settings.CORS_ORIGIN_REGEX_WHITELIST = ('^http?://(\w+\.)?yahoo\.com$', ) 191 | settings.CORS_ALLOW_CREDENTIALS = True 192 | settings.CORS_ORIGIN_ALLOW_ALL = False 193 | settings.CORS_ALLOW_METHODS = ['OPTIONS'] 194 | settings.CORS_URLS_REGEX = '^.*$' 195 | response = HttpResponse() 196 | request_headers = {'HTTP_ORIGIN': 'http://foo.google.com'} 197 | request = Mock(path='/', META=request_headers, method='OPTIONS') 198 | processed = self.middleware.process_response(request, response) 199 | self.assertEqual(processed.get(ACCESS_CONTROL_ALLOW_ORIGIN), None) 200 | --------------------------------------------------------------------------------