pax_global_header00006660000000000000000000000064131125465420014515gustar00rootroot0000000000000052 comment=49ffdac56221ed74beaa95cf5afa457b917d87db django-cors-headers-2.1.0/000077500000000000000000000000001311254654200153345ustar00rootroot00000000000000django-cors-headers-2.1.0/.gitignore000066400000000000000000000002541311254654200173250ustar00rootroot00000000000000*.pyc *.egg-info .cache/ build/ dist/ .eggs/ .idea/ .tox/ # Packages *.egg *.egg-info .coverage dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 django-cors-headers-2.1.0/.travis.yml000066400000000000000000000002051311254654200174420ustar00rootroot00000000000000sudo: false notifications: email: false language: python python: '3.6' cache: pip install: - pip install tox script: - tox django-cors-headers-2.1.0/HISTORY.rst000066400000000000000000000110131311254654200172230ustar00rootroot00000000000000History ======= Pending ------- * New release notes go here. 2.1.0 (2017-05-28) ------------------ * Django 1.11 compatibility. There were no changes to the actual library code, so previous versions probably work, though they weren't properly tested on 1.11. 2.0.2 (2017-02-06) ------------------ * Fix when the check for ``CORS_MODEL`` is done to allow it to properly add the headers and respond to ``OPTIONS`` requests. 2.0.1 (2017-01-29) ------------------ * Add support for specifying 'null' in ``CORS_ORIGIN_WHITELIST``. 2.0.0 (2017-01-07) ------------------ * Remove previously undocumented ``CorsModel`` as it was causing migration issues. For backwards compatibility, any users previously using ``CorsModel`` should create a model in their own app that inherits from the new ``AbstractCorsModel``, and to keep using the same data, set the model's ``db_table`` to 'corsheaders_corsmodel'. Users not using ``CorsModel`` will find they have an unused table that they can drop. * Make sure that ``Access-Control-Allow-Credentials`` is in the response if the client asks for it. 1.3.1 (2016-11-09) ------------------ * Fix a bug with the single check if CORS enabled added in 1.3.0: on Django < 1.10 shortcut responses could be generated by middleware above ``CorsMiddleware``, before it processed the request, failing with an ``AttributeError`` for ``request._cors_enabled``. Also clarified the docs that ``CorsMiddleware`` should be kept as high as possible in your middleware stack, above any middleware that can generate such responses. 1.3.0 (2016-11-06) ------------------ * Add checks to validate the types of the settings. * Add the 'Do Not Track' header ``'DNT'`` to the default for ``CORS_ALLOW_HEADERS``. * Add 'Origin' to the 'Vary' header of outgoing requests when not allowing all origins, as per the CORS spec. Note this changes the way HTTP caching works with your CORS-enabled responses. * Check whether CORS should be enabled on a request only once. This has had a minor change on the conditions where any custom signals will be called - signals will now always be called *before* ``HTTP_REFERER`` gets replaced, whereas before they could be called before and after. Also this attaches the attribute ``_cors_enabled`` to ``request`` - please take care that other code you're running does not remove it. 1.2.2 (2016-10-05) ------------------ * Add ``CorsModel.__str__`` for human-readable text * Add a signal that allows you to add code for more intricate control over when CORS headers are added. 1.2.1 (2016-09-30) ------------------ * Made settings dynamically respond to changes, and which allows you to import the defaults for headers and methods in order to extend them. 1.2.0 (2016-09-28) ------------------ * Drop Python 2.6 support. * Drop Django 1.3-1.7 support, as they are no longer supported. * Confirmed Django 1.9 support (no changes outside of tests were necessary). * Added Django 1.10 support. * Package as a universal wheel. 1.1.0 (2014-12-15) ------------------ * django-cors-header now supports Django 1.8 with its new application loading system! Thanks @jpadilla for making this possible and sorry for the delay in making a release. 1.0.0 (2014-12-13) ------------------ django-cors-headers is all grown-up :) Since it's been used in production for many many deployments, I think it's time we mark this as a stable release. * Switching this middleware versioning over to semantic versioning * #46 add user-agent and accept-encoding default headers * #45 pep-8 this big boy up 0.13 (2014-08-14) ----------------- * Add support for Python 3 * Updated tests * Improved docuemntation * Small bugfixes 0.12 (2013-09-24) ----------------- * Added an option to selectively enable CORS only for specific URLs 0.11 (2013-09-24) * Added the ability to specify a regex for whitelisting many origin hostnames at once 0.10 (2013-09-05) ----------------- * Introduced port distinction for origin checking * Use ``urlparse`` for Python 3 support * Added testcases to project 0.06 (2013-02-18) ----------------- * Add support for exposed response headers 0.05 (2013-01-26) ----------------- * Fixed middleware to ensure correct response for CORS preflight requests 0.04 (2013-01-25) ----------------- * Add ``Access-Control-Allow-Credentials`` control to simple requests 0.03 (2013-01-22) ----------------- * Bugfix to repair mismatched default variable names 0.02 (2013-01-19) ----------------- * Refactor/pull defaults into separate file 0.01 (2013-01-19) ----------------- * Initial release django-cors-headers-2.1.0/LICENSE.txt000077500000000000000000000021021311254654200171550ustar00rootroot00000000000000Copyright 2017 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. django-cors-headers-2.1.0/MANIFEST.in000066400000000000000000000000631311254654200170710ustar00rootroot00000000000000include HISTORY.rst README.rst include LICENSE.txt django-cors-headers-2.1.0/README.rst000066400000000000000000000240551311254654200170310ustar00rootroot00000000000000django-cors-headers =================== A Django App that adds CORS (Cross-Origin Resource Sharing) headers to responses. 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/ .. image:: https://travis-ci.org/ottoyiu/django-cors-headers.svg?branch=master :target: https://travis-ci.org/ottoyiu/django-cors-headers Requirements ------------ Tested with all combinations of: * Python: 2.7, 3.6 * Django: 1.8, 1.9, 1.10, 1.11 Setup ----- Install from **pip**: .. code-block:: sh pip install django-cors-headers and then add it to your installed apps: .. code-block:: python INSTALLED_APPS = ( ... 'corsheaders', ... ) You will also need to add a middleware class to listen in on responses: .. code-block:: python MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10 ... 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', ... ] ``CorsMiddleware`` should be placed as high as possible, especially before any middleware that can generate responses such as Django's ``CommonMiddleware`` or Whitenoise's ``WhiteNoiseMiddleware``. If it is not before, it will not be able to add the CORS headers to these responses. Also if you are using ``CORS_REPLACE_HTTPS_REFERER`` it should be placed before Django's ``CsrfViewMiddleware`` (see more below). Configuration ------------- Configure the middleware's behaviour in your Django settings. You must add the 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. ``CORS_ORIGIN_ALLOW_ALL`` ~~~~~~~~~~~~~~~~~~~~~~~~~ If ``True``, the whitelist will not be used and all origins will be accepted. Defaults to ``False``. ``CORS_ORIGIN_WHITELIST`` ~~~~~~~~~~~~~~~~~~~~~~~~~ A list of origin hostnames that are authorized to make cross-site HTTP requests. The value ``'null'`` can also appear in this list, and will match the ``Origin: null`` header that is used in `"privacy-sensitive contexts" `_, such as when the client is running from a ``file://`` domain. Defaults to ``[]``. Example: .. code-block:: python CORS_ORIGIN_WHITELIST = ( 'google.com', 'hostname.example.com', 'localhost:8000', '127.0.0.1:9000' ) ``CORS_ORIGIN_REGEX_WHITELIST`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A list of regexes that match origin regex list of origin hostnames that are authorized to make cross-site HTTP requests. Defaults to ``[]``. Useful when ``CORS_ORIGIN_WHITELIST`` is impractical, such as when you have a large number of subdomains. Example: .. code-block:: python CORS_ORIGIN_REGEX_WHITELIST = (r'^(https?://)?(\w+\.)?google\.com$', ) -------------- The following are optional settings, for which the defaults probably suffice. ``CORS_URLS_REGEX`` ~~~~~~~~~~~~~~~~~~~ A regex which restricts the URL's for which the CORS headers will be sent. Defaults to ``r'^.*$'``, i.e. match all URL's. Useful when you only need CORS on a part of your site, e.g. an API at ``/api/``. Example: .. code-block:: python CORS_URLS_REGEX = r'^/api/.*$' ``CORS_ALLOW_METHODS`` ~~~~~~~~~~~~~~~~~~~~~~ A list of HTTP verbs that are allowed for the actual request. Defaults to: .. code-block:: python CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', ) The default can be imported as ``corsheaders.defaults.default_methods`` so you can just extend it with your custom methods. This allows you to keep up to date with any future changes. For example: .. code-block:: python from corsheaders.defaults import default_methods CORS_ALLOW_METHODS = default_methods + ( 'POKE', ) ``CORS_ALLOW_HEADERS`` ~~~~~~~~~~~~~~~~~~~~~~ The list of non-standard HTTP headers that can be used when making the actual request. Defaults to: .. code-block:: python CORS_ALLOW_HEADERS = ( 'accept', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', ) The default can be imported as ``corsheaders.defaults.default_headers`` so you can extend it with your custom headers. This allows you to keep up to date with any future changes. For example: .. code-block:: python from corsheaders.defaults import default_headers CORS_ALLOW_HEADERS = default_headers + ( 'my-custom-header', ) ``CORS_EXPOSE_HEADERS`` ~~~~~~~~~~~~~~~~~~~~~~~ The list of HTTP headers that are to be exposed to the browser. Defaults to ``[]``. ``CORS_PREFLIGHT_MAX_AGE`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ The number of seconds a client/browser can cache the preflight response. If this is 0 (or any falsey value), no max age header will be sent. Defaults to ``86400`` (one day). **Note:** A preflight request is an extra request that is made when making a "not-so-simple" request (e.g. ``Content-Type`` is not ``application/x-www-form-urlencoded``) to determine what requests the server actually accepts. Read more about it in the `HTML 5 Rocks CORS tutorial `_. ``CORS_ALLOW_CREDENTIALS`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ If ``True``, cookies will be allowed to be included in cross-site HTTP requests. Defaults to ``False``. ``CORS_MODEL`` ~~~~~~~~~~~~~~ If set, this should be the path to a model to look up allowed origins, in the form ``app.modelname``. Defaults to ``None``. The model should inherit from ``corsheaders.models.AbstractCorsModel`` and specify the allowed origin in the ``CharField`` called ``cors``. CSRF Integration ---------------- Most sites will need to take advantage of the `Cross-Site Request Forgery protection `_ that Django offers. CORS and CSRF are separate, and Django has no way of using your CORS configuration to exempt sites from the ``Referer`` checking that it does on secure requests. The way to do that is with its `CSRF_TRUSTED_ORIGINS setting `_. For example: .. code-block:: python CORS_ORIGIN_WHITELIST = ( 'read.only.com', 'change.allowed.com', ) CSRF_TRUSTED_ORIGINS = ( 'change.allowed.com', ) ``CORS_REPLACE_HTTPS_REFERER`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``CSRF_TRUSTED_ORIGINS`` was introduced in Django 1.9, so users of earlier versions will need an alternate solution. If ``CORS_REPLACE_HTTPS_REFERER`` is ``True``, ``CorsMiddleware`` will change the ``Referer`` header to something that will pass Django's CSRF checks whenever the CORS checks pass. Defaults to ``False``. Note that unlike ``CSRF_TRUSTED_ORIGINS``, this setting does not allow you to distinguish between domains that are trusted to *read* resources by CORS and domains that are trusted to *change* resources by avoiding CSRF protection. With this feature enabled you should also add ``corsheaders.middleware.CorsPostCsrfMiddleware`` after ``django.middleware.csrf.CsrfViewMiddleware`` in your ``MIDDLEWARE_CLASSES`` to undo the ``Referer`` replacement: .. code-block:: python MIDDLEWARE_CLASSES = [ ... 'corsheaders.middleware.CorsMiddleware', ... 'django.middleware.csrf.CsrfViewMiddleware', 'corsheaders.middleware.CorsPostCsrfMiddleware', ... ] Signals ------- If you have a use case that requires more than just the above configuration, you can attach code to check if a given request should be allowed. For example, this can be used to read the list of origins you allow from a model. Attach any number of handlers to the ``check_request_enabled`` `Django signal `_, which provides the ``request`` argument (use ``**kwargs`` in your handler to protect against any future arguments being added). If any handler attached to the signal returns a truthy value, the request will be allowed. For example you might define a handler like this: .. code-block:: python # myapp/handlers.py from corsheaders.signals import check_request_enabled from .models import MySite def cors_allow_mysites(sender, request, **kwargs): return MySite.objects.filter(host=request.host).exists() check_request_enabled.connect(cors_allow_mysites) Then connect it at app ready time using a `Django AppConfig `_: .. code-block:: python # myapp/__init__.py default_app_config = 'myapp.apps.MyAppConfig' .. code-block:: python # myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): name = 'myapp' def ready(self): # Makes sure all signal handlers are connected from . import handlers # noqa A common use case for the signal is to allow *all* origins to access a subset of URL's, whilst allowing a normal set of origins to access *all* URL's. This isn't possible using just the normal configuration, but it can be achieved with a signal handler. First set ``CORS_ORIGIN_WHITELIST`` to the list of trusted origins that are allowed to access every URL, and then add a handler to ``check_request_enabled`` to allow CORS regardless of the origin for the unrestricted URL's. For example: .. code-block:: python # myapp/handlers.py from corsheaders.signals import check_request_enabled def cors_allow_api_to_everyone(sender, request, **kwargs): return request.path.startswith('/api/') check_request_enabled.connect(cors_allow_api_to_everyone) Credits ------- ``django-cors-headers`` was created by Otto Yiu (`@ottoyiu `_) and has been worked on by `25+ contributors `_. Thanks to every contributor, and if you want to get involved please don't hesitate to make a pull request. django-cors-headers-2.1.0/corsheaders/000077500000000000000000000000001311254654200176365ustar00rootroot00000000000000django-cors-headers-2.1.0/corsheaders/__init__.py000077500000000000000000000001101311254654200217420ustar00rootroot00000000000000from .checks import check_settings # noqa: F401 __version__ = '2.1.0' django-cors-headers-2.1.0/corsheaders/checks.py000066400000000000000000000062511311254654200214540ustar00rootroot00000000000000import re from collections import Sequence from numbers import Integral from django.core import checks from django.utils import six from .conf import conf re_type = type(re.compile('')) @checks.register def check_settings(app_configs, **kwargs): errors = [] if not is_sequence(conf.CORS_ALLOW_HEADERS, six.string_types): errors.append( checks.Error( "CORS_ALLOW_HEADERS should be a sequence of strings.", id="corsheaders.E001" ) ) if not is_sequence(conf.CORS_ALLOW_METHODS, six.string_types): errors.append( checks.Error( "CORS_ALLOW_METHODS should be a sequence of strings.", id="corsheaders.E002" ) ) if not isinstance(conf.CORS_ALLOW_CREDENTIALS, bool): errors.append( checks.Error( "CORS_ALLOW_CREDENTIALS should be a bool.", id="corsheaders.E003" ) ) if not isinstance(conf.CORS_PREFLIGHT_MAX_AGE, Integral) or conf.CORS_PREFLIGHT_MAX_AGE < 0: errors.append( checks.Error( "CORS_PREFLIGHT_MAX_AGE should be an integer greater than or equal to zero.", id="corsheaders.E004" ) ) if not isinstance(conf.CORS_ORIGIN_ALLOW_ALL, bool): errors.append( checks.Error( "CORS_ORIGIN_ALLOW_ALL should be a bool.", id="corsheaders.E005" ) ) if not is_sequence(conf.CORS_ORIGIN_WHITELIST, six.string_types): errors.append( checks.Error( "CORS_ORIGIN_WHITELIST should be a sequence of strings.", id="corsheaders.E006" ) ) if not is_sequence(conf.CORS_ORIGIN_REGEX_WHITELIST, six.string_types + (re_type,)): errors.append( checks.Error( "CORS_ORIGIN_REGEX_WHITELIST should be a sequence of strings and/or compiled regexes.", id="corsheaders.E007" ) ) if not is_sequence(conf.CORS_EXPOSE_HEADERS, six.string_types): errors.append( checks.Error( "CORS_EXPOSE_HEADERS should be a sequence.", id="corsheaders.E008" ) ) if not isinstance(conf.CORS_URLS_REGEX, six.string_types + (re_type,)): errors.append( checks.Error( "CORS_URLS_REGEX should be a string or regex.", id="corsheaders.E009" ) ) if conf.CORS_MODEL is not None and not isinstance(conf.CORS_MODEL, six.string_types): errors.append( checks.Error( "CORS_MODEL should be a string or None.", id="corsheaders.E010" ) ) if not isinstance(conf.CORS_REPLACE_HTTPS_REFERER, bool): errors.append( checks.Error( "CORS_REPLACE_HTTPS_REFERER should be a bool.", id="corsheaders.E011" ) ) return errors def is_sequence(thing, types): return ( isinstance(thing, Sequence) and all(isinstance(x, types) for x in thing) ) django-cors-headers-2.1.0/corsheaders/compat.py000066400000000000000000000004621311254654200214750ustar00rootroot00000000000000try: from django.utils.deprecation import MiddlewareMixin except ImportError: # pragma: no cover # Not required for Django <= 1.9, see: # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware MiddlewareMixin = object # pragma: no cover django-cors-headers-2.1.0/corsheaders/conf.py000066400000000000000000000027541311254654200211450ustar00rootroot00000000000000from django.conf import settings from .defaults import default_headers, default_methods # Kept here for backwards compatibility class Settings(object): """ Shadow Django's settings with a little logic """ @property def CORS_ALLOW_HEADERS(self): return getattr(settings, 'CORS_ALLOW_HEADERS', default_headers) @property def CORS_ALLOW_METHODS(self): return getattr(settings, 'CORS_ALLOW_METHODS', default_methods) @property def CORS_ALLOW_CREDENTIALS(self): return getattr(settings, 'CORS_ALLOW_CREDENTIALS', False) @property def CORS_PREFLIGHT_MAX_AGE(self): return getattr(settings, 'CORS_PREFLIGHT_MAX_AGE', 86400) @property def CORS_ORIGIN_ALLOW_ALL(self): return getattr(settings, 'CORS_ORIGIN_ALLOW_ALL', False) @property def CORS_ORIGIN_WHITELIST(self): return getattr(settings, 'CORS_ORIGIN_WHITELIST', ()) @property def CORS_ORIGIN_REGEX_WHITELIST(self): return getattr(settings, 'CORS_ORIGIN_REGEX_WHITELIST', ()) @property def CORS_EXPOSE_HEADERS(self): return getattr(settings, 'CORS_EXPOSE_HEADERS', ()) @property def CORS_URLS_REGEX(self): return getattr(settings, 'CORS_URLS_REGEX', r'^.*$') @property def CORS_MODEL(self): return getattr(settings, 'CORS_MODEL', None) @property def CORS_REPLACE_HTTPS_REFERER(self): return getattr(settings, 'CORS_REPLACE_HTTPS_REFERER', False) conf = Settings() django-cors-headers-2.1.0/corsheaders/defaults.py000066400000000000000000000004351311254654200220210ustar00rootroot00000000000000default_headers = ( 'accept', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', ) default_methods = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', ) django-cors-headers-2.1.0/corsheaders/middleware.py000077500000000000000000000140121311254654200223260ustar00rootroot00000000000000import re from django import http from django.apps import apps from django.utils.cache import patch_vary_headers from django.utils.six.moves.urllib.parse import urlparse from .compat import MiddlewareMixin from .conf import conf from .signals import check_request_enabled ACCESS_CONTROL_ALLOW_ORIGIN = 'Access-Control-Allow-Origin' ACCESS_CONTROL_EXPOSE_HEADERS = 'Access-Control-Expose-Headers' ACCESS_CONTROL_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials' ACCESS_CONTROL_ALLOW_HEADERS = 'Access-Control-Allow-Headers' ACCESS_CONTROL_ALLOW_METHODS = 'Access-Control-Allow-Methods' ACCESS_CONTROL_MAX_AGE = 'Access-Control-Max-Age' class CorsPostCsrfMiddleware(MiddlewareMixin): def _https_referer_replace_reverse(self, request): """ Put the HTTP_REFERER back to its original value and delete the temporary storage """ if conf.CORS_REPLACE_HTTPS_REFERER and 'ORIGINAL_HTTP_REFERER' in request.META: http_referer = request.META['ORIGINAL_HTTP_REFERER'] request.META['HTTP_REFERER'] = http_referer del request.META['ORIGINAL_HTTP_REFERER'] def process_request(self, request): self._https_referer_replace_reverse(request) return None def process_view(self, request, callback, callback_args, callback_kwargs): self._https_referer_replace_reverse(request) return None class CorsMiddleware(MiddlewareMixin): def _https_referer_replace(self, request): """ When https is enabled, django CSRF checking includes referer checking which breaks when using CORS. This function updates the HTTP_REFERER header to make sure it matches HTTP_HOST, provided that our cors logic succeeds """ origin = request.META.get('HTTP_ORIGIN') if request.is_secure() and origin and 'ORIGINAL_HTTP_REFERER' not in request.META: url = urlparse(origin) if not conf.CORS_ORIGIN_ALLOW_ALL and not self.origin_found_in_white_lists(origin, url): return try: http_referer = request.META['HTTP_REFERER'] http_host = "https://%s/" % request.META['HTTP_HOST'] request.META = request.META.copy() request.META['ORIGINAL_HTTP_REFERER'] = http_referer request.META['HTTP_REFERER'] = http_host except KeyError: pass def process_request(self, request): """ If CORS preflight header, then create an empty body response (200 OK) and return it Django won't bother calling any other request view/exception middleware along with the requested view; it will call any response middlewares """ request._cors_enabled = self.is_enabled(request) if request._cors_enabled: if conf.CORS_REPLACE_HTTPS_REFERER: self._https_referer_replace(request) if ( request.method == 'OPTIONS' and 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META ): return http.HttpResponse() def process_view(self, request, callback, callback_args, callback_kwargs): """ Do the referer replacement here as well """ if request._cors_enabled and conf.CORS_REPLACE_HTTPS_REFERER: self._https_referer_replace(request) return None def process_response(self, request, response): """ Add the respective CORS headers """ origin = request.META.get('HTTP_ORIGIN') if not origin: return response enabled = getattr(request, '_cors_enabled', None) if enabled is None: enabled = self.is_enabled(request) if not enabled: return response # todo: check hostname from db instead url = urlparse(origin) if conf.CORS_ALLOW_CREDENTIALS: response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true' if ( not conf.CORS_ORIGIN_ALLOW_ALL and not self.origin_found_in_white_lists(origin, url) and not self.origin_found_in_model(url) and not self.check_signal(request) ): return response if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS: response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*" else: response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin patch_vary_headers(response, ['Origin']) if len(conf.CORS_EXPOSE_HEADERS): response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(conf.CORS_EXPOSE_HEADERS) if request.method == 'OPTIONS': response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS) response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS) if conf.CORS_PREFLIGHT_MAX_AGE: response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE return response def origin_found_in_white_lists(self, origin, url): return ( url.netloc in conf.CORS_ORIGIN_WHITELIST or (origin == 'null' and origin in conf.CORS_ORIGIN_WHITELIST) or self.regex_domain_match(origin) ) def regex_domain_match(self, origin): for domain_pattern in conf.CORS_ORIGIN_REGEX_WHITELIST: if re.match(domain_pattern, origin): return origin def origin_found_in_model(self, url): if conf.CORS_MODEL is None: return False model = apps.get_model(*conf.CORS_MODEL.split('.')) return model.objects.filter(cors=url.netloc).exists() def is_enabled(self, request): return ( re.match(conf.CORS_URLS_REGEX, request.path) or self.check_signal(request) ) def check_signal(self, request): signal_responses = check_request_enabled.send( sender=None, request=request, ) return any( return_value for function, return_value in signal_responses ) django-cors-headers-2.1.0/corsheaders/models.py000066400000000000000000000003061311254654200214720ustar00rootroot00000000000000from django.db import models class AbstractCorsModel(models.Model): class Meta: abstract = True db_table = 'corsheaders_corsmodel' cors = models.CharField(max_length=255) django-cors-headers-2.1.0/corsheaders/signals.py000066400000000000000000000004311311254654200216460ustar00rootroot00000000000000from django.dispatch import Signal # If any attached handler returns Truthy, CORS will be allowed for the request. # This can be used to build custom logic into the request handling when the # configuration doesn't work. check_request_enabled = Signal(providing_args=['request']) django-cors-headers-2.1.0/requirements.in000066400000000000000000000001161311254654200204050ustar00rootroot00000000000000docutils flake8 isort multilint pytest pytest-cov pytest-django pytz Pygments django-cors-headers-2.1.0/requirements.txt000066400000000000000000000015251311254654200206230ustar00rootroot00000000000000# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --output-file requirements.txt requirements.in # appdirs==1.4.3 # via setuptools configparser==3.5.0 # via flake8 coverage==4.4.1 # via pytest-cov docutils==0.13.1 enum34==1.1.6 # via flake8 flake8==3.3.0 isort==4.2.5 mccabe==0.6.1 # via flake8 multilint==2.0.2 packaging==16.8 # via setuptools py==1.4.33 # via pytest pycodestyle==2.3.1 # via flake8 pyflakes==1.5.0 # via flake8 pygments==2.2.0 pyparsing==2.2.0 # via packaging pytest-cov==2.5.1 pytest-django==3.1.2 pytest==3.1.0 pytz==2017.2 six==1.10.0 # via packaging, setuptools # The following packages are considered to be unsafe in a requirements file: # setuptools # via pytest django-cors-headers-2.1.0/runtests.py000077500000000000000000000004631311254654200176030ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import os import sys import pytest def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') return pytest.main() if __name__ == '__main__': sys.exit(main()) django-cors-headers-2.1.0/setup.cfg000066400000000000000000000006661311254654200171650ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] ignore = X99999 max-complexity = 12 max-line-length = 120 [isort] known_first_party = corsheaders known_third_party = django line_length = 120 multi_line_output = 5 not_skip = __init__.py [tool:multilint] paths = corsheaders runtests.py tests setup.py [tool:pytest] addopts = -p no:doctest --cov=corsheaders --cov-report term-missing --cov-fail-under 100 django-cors-headers-2.1.0/setup.py000077500000000000000000000037531311254654200170610ustar00rootroot00000000000000import codecs import os import re from setuptools import setup def get_version(package): """ Return package version as listed in `__version__` in `__init__.py`. """ with codecs.open(os.path.join(package, '__init__.py'), 'r', 'utf-8') as fp: init_py = fp.read() return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) version = get_version('corsheaders') with codecs.open('README.rst', 'r', 'utf-8') as readme_file: readme = readme_file.read() with codecs.open('HISTORY.rst', 'r', 'utf-8') as history_file: history = history_file.read() setup( name='django-cors-headers', version=version, description=( 'django-cors-headers is a Django application for handling the server ' 'headers required for Cross-Origin Resource Sharing (CORS).' ), long_description=readme + '\n\n' + history, author='Otto Yiu', author_email='otto@live.ca', url='https://github.com/ottoyiu/django-cors-headers', packages=['corsheaders'], license='MIT License', keywords='django cors middleware rest api', platforms=['any'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 1.8', 'Framework :: Django :: 1.9', 'Framework :: Django :: 1.10', 'Framework :: Django :: 1.11', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], install_requires=[], ) django-cors-headers-2.1.0/tests/000077500000000000000000000000001311254654200164765ustar00rootroot00000000000000django-cors-headers-2.1.0/tests/__init__.py000066400000000000000000000000001311254654200205750ustar00rootroot00000000000000django-cors-headers-2.1.0/tests/settings.py000066400000000000000000000011211311254654200207030ustar00rootroot00000000000000import django from django.conf import global_settings SECRET_KEY = 'NOTASECRET' ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ 'corsheaders', 'tests.testapp', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'TEST_NAME': ':memory:', }, } ROOT_URLCONF = 'tests.urls' middlewares = list(global_settings.MIDDLEWARE_CLASSES) middlewares.append('corsheaders.middleware.CorsMiddleware') if django.VERSION >= (1, 10): MIDDLEWARE = middlewares else: MIDDLEWARE_CLASSES = middlewares SECURE_PROXY_SSL_HEADER = ('HTTP_FAKE_SECURE', 'true') django-cors-headers-2.1.0/tests/test_checks.py000077500000000000000000000066771311254654200213720ustar00rootroot00000000000000import re import pytest from django.core.checks import Error from django.core.management import base, call_command from django.test import SimpleTestCase from django.test.utils import override_settings from corsheaders.checks import check_settings class ChecksTests(SimpleTestCase): def check_error_codes(self, expected): errors = check_settings([]) assert len(errors) == len(expected) assert all(isinstance(e, Error) for e in errors) assert [e.id for e in errors] == expected def test_defaults_pass(self): self.check_error_codes([]) def test_defaults_pass_check(self): call_command('check') @override_settings(CORS_ORIGIN_ALLOW_ALL=object) def test_checks_are_bound(self): with pytest.raises(base.SystemCheckError): call_command('check') @override_settings(CORS_ALLOW_HEADERS=object) def test_cors_allow_headers_non_sequence(self): self.check_error_codes(['corsheaders.E001']) @override_settings(CORS_ALLOW_HEADERS=[object]) def test_cors_allow_headers_non_string(self): self.check_error_codes(['corsheaders.E001']) @override_settings(CORS_ALLOW_METHODS=object) def test_cors_allow_methods_non_sequence(self): self.check_error_codes(['corsheaders.E002']) @override_settings(CORS_ALLOW_METHODS=[object]) def test_cors_allow_methods_non_string(self): self.check_error_codes(['corsheaders.E002']) @override_settings(CORS_ALLOW_CREDENTIALS=object) def test_cors_allow_credentials_non_bool(self): self.check_error_codes(['corsheaders.E003']) @override_settings(CORS_PREFLIGHT_MAX_AGE='10') def test_cors_preflight_max_age_non_integer(self): self.check_error_codes(['corsheaders.E004']) @override_settings(CORS_PREFLIGHT_MAX_AGE=-1) def test_cors_preflight_max_age_negative(self): self.check_error_codes(['corsheaders.E004']) @override_settings(CORS_ORIGIN_ALLOW_ALL=object) def test_cors_origin_allow_all_non_bool(self): self.check_error_codes(['corsheaders.E005']) @override_settings(CORS_ORIGIN_WHITELIST=object) def test_cors_origin_whitelist_non_sequence(self): self.check_error_codes(['corsheaders.E006']) @override_settings(CORS_ORIGIN_WHITELIST=[object]) def test_cors_origin_whitelist_non_string(self): self.check_error_codes(['corsheaders.E006']) @override_settings(CORS_ORIGIN_REGEX_WHITELIST=object) def test_cors_origin_regex_whitelist_non_sequence(self): self.check_error_codes(['corsheaders.E007']) @override_settings(CORS_ORIGIN_REGEX_WHITELIST=[re.compile(r'a')]) def test_cors_origin_regex_whitelist_regex(self): self.check_error_codes([]) @override_settings(CORS_EXPOSE_HEADERS=object) def test_cors_expose_headers_non_sequence(self): self.check_error_codes(['corsheaders.E008']) @override_settings(CORS_EXPOSE_HEADERS=[object]) def test_cors_expose_headers_non_string(self): self.check_error_codes(['corsheaders.E008']) @override_settings(CORS_URLS_REGEX=object) def test_cors_urls_regex_non_string(self): self.check_error_codes(['corsheaders.E009']) @override_settings(CORS_MODEL=object) def test_cors_model_failure(self): self.check_error_codes(['corsheaders.E010']) @override_settings(CORS_REPLACE_HTTPS_REFERER=object) def test_cors_replace_https_referer_failure(self): self.check_error_codes(['corsheaders.E011']) django-cors-headers-2.1.0/tests/test_conf.py000066400000000000000000000004441311254654200210360ustar00rootroot00000000000000from django.test import SimpleTestCase from django.test.utils import override_settings from corsheaders.conf import conf class ConfTests(SimpleTestCase): @override_settings(CORS_ALLOW_HEADERS=['foo']) def test_can_override(self): assert conf.CORS_ALLOW_HEADERS == ['foo'] django-cors-headers-2.1.0/tests/test_middleware.py000066400000000000000000000413751311254654200222360ustar00rootroot00000000000000import django from django.http import HttpResponse from django.test import TestCase from django.test.utils import override_settings from corsheaders.compat import MiddlewareMixin from corsheaders.middleware import ( ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE ) from tests.testapp.models import CorsModel from .utils import append_middleware, prepend_middleware, temporary_check_request_hander class ShortCircuitMiddleware(MiddlewareMixin): def process_request(self, request): return HttpResponse('short-circuit-middleware-response') class CorsMiddlewareTests(TestCase): def test_get_no_origin(self): resp = self.client.get('/') assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp @override_settings(CORS_ORIGIN_WHITELIST=['example.com']) def test_get_not_in_whitelist(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.org') assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp @override_settings(CORS_ORIGIN_WHITELIST=['example.com', 'example.org']) def test_get_in_whitelist(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.org') assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.org' @override_settings(CORS_ORIGIN_WHITELIST=['example.com', 'null']) def test_null_in_whitelist(self): resp = self.client.get('/', HTTP_ORIGIN='null') assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'null' @override_settings( CORS_ORIGIN_ALLOW_ALL=True, CORS_EXPOSE_HEADERS=['accept', 'origin', 'content-type'], ) def test_get_expose_headers(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert resp[ACCESS_CONTROL_EXPOSE_HEADERS] == 'accept, origin, content-type' @override_settings(CORS_ORIGIN_ALLOW_ALL=True) def test_get_dont_expose_headers(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert ACCESS_CONTROL_EXPOSE_HEADERS not in resp @override_settings( CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_ALLOW_ALL=True, ) def test_get_allow_credentials(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert resp[ACCESS_CONTROL_ALLOW_CREDENTIALS] == 'true' @override_settings(CORS_ORIGIN_ALLOW_ALL=True) def test_get_dont_allow_credentials(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert ACCESS_CONTROL_ALLOW_CREDENTIALS not in resp @override_settings( CORS_ALLOW_HEADERS=['content-type', 'origin'], CORS_ALLOW_METHODS=['GET', 'OPTIONS'], CORS_PREFLIGHT_MAX_AGE=1002, CORS_ORIGIN_ALLOW_ALL=True, ) def test_options_allowed_origin(self): resp = self.client.options( '/', HTTP_ORIGIN='http://example.com', ) assert resp[ACCESS_CONTROL_ALLOW_HEADERS] == 'content-type, origin' assert resp[ACCESS_CONTROL_ALLOW_METHODS] == 'GET, OPTIONS' assert resp[ACCESS_CONTROL_MAX_AGE] == '1002' @override_settings( CORS_ALLOW_HEADERS=['content-type', 'origin'], CORS_ALLOW_METHODS=['GET', 'OPTIONS'], CORS_PREFLIGHT_MAX_AGE=0, CORS_ORIGIN_ALLOW_ALL=True, ) def test_options_no_max_age(self): resp = self.client.options('/', HTTP_ORIGIN='http://example.com') assert resp[ACCESS_CONTROL_ALLOW_HEADERS] == 'content-type, origin' assert resp[ACCESS_CONTROL_ALLOW_METHODS] == 'GET, OPTIONS' assert ACCESS_CONTROL_MAX_AGE not in resp @override_settings( CORS_ALLOW_METHODS=['OPTIONS'], CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_WHITELIST=('localhost:9000',), ) def test_options_whitelist_with_port(self): resp = self.client.options('/', HTTP_ORIGIN='http://localhost:9000') assert resp[ACCESS_CONTROL_ALLOW_CREDENTIALS] == 'true' @override_settings( CORS_ALLOW_METHODS=['OPTIONS'], CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_REGEX_WHITELIST=[r'^http?://(\w+\.)?example\.com$'], ) def test_options_adds_origin_when_domain_found_in_origin_regex_whitelist(self): resp = self.client.options('/', HTTP_ORIGIN='http://foo.example.com') assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://foo.example.com' @override_settings( CORS_ALLOW_METHODS=['OPTIONS'], CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_REGEX_WHITELIST=(r'^http?://(\w+\.)?example\.org$',), ) def test_options_will_not_add_origin_when_domain_not_found_in_origin_regex_whitelist(self): resp = self.client.options('/', HTTP_ORIGIN='http://foo.example.com') assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp @override_settings(CORS_MODEL='testapp.CorsModel') def test_get_when_custom_model_enabled(self): CorsModel.objects.create(cors='example.com') resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' assert ACCESS_CONTROL_ALLOW_CREDENTIALS not in resp @override_settings(CORS_MODEL='testapp.CorsModel', CORS_ALLOW_CREDENTIALS=True) def test_get_when_custom_model_enabled_and_allow_credentials(self): CorsModel.objects.create(cors='example.com') resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' assert resp[ACCESS_CONTROL_ALLOW_CREDENTIALS] == 'true' def test_options(self): resp = self.client.options( '/', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 def test_options_empty_request_method(self): resp = self.client.options( '/', HTTP_ACCESS_CONTROL_REQUEST_METHOD='', ) assert resp.status_code == 200 def test_options_no_header(self): resp = self.client.options('/') assert resp.status_code == 404 @override_settings(CORS_MODEL='testapp.CorsModel') def test_options_when_custom_model_enabled(self): CorsModel.objects.create(cors='example.com') resp = self.client.options( '/', HTTP_ORIGIN='http://example.com', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert ACCESS_CONTROL_ALLOW_HEADERS in resp @override_settings(CORS_MODEL='testapp.CorsModel') def test_process_response_when_custom_model_enabled(self): CorsModel.objects.create(cors='foo.google.com') response = self.client.get('/', HTTP_ORIGIN='http://foo.google.com') assert response.get(ACCESS_CONTROL_ALLOW_ORIGIN, None) == 'http://foo.google.com' @override_settings( CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_ALLOW_ALL=True, ) def test_allow_all_origins_get(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert resp.status_code == 200 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' assert resp['Vary'] == 'Origin' @override_settings( CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_ALLOW_ALL=True, ) def test_allow_all_origins_options(self): resp = self.client.options( '/', HTTP_ORIGIN='http://example.com', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' assert resp['Vary'] == 'Origin' @override_settings( CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_ALLOW_ALL=True, ) def test_non_200_headers_still_set(self): """ It's not clear whether the header should still be set for non-HTTP200 when not a preflight request. However this is the existing behaviour for django-cors-middleware, so at least this test makes that explicit, especially since for the switch to Django 1.10, special-handling will need to be put in place to preserve this behaviour. See `ExceptionMiddleware` mention here: https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware """ resp = self.client.get('/test-401/', HTTP_ORIGIN='http://example.com') assert resp.status_code == 401 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' @override_settings( CORS_ALLOW_CREDENTIALS=True, CORS_ORIGIN_ALLOW_ALL=True, ) def test_auth_view_options(self): """ Ensure HTTP200 and header still set, for preflight requests to views requiring authentication. See: https://github.com/ottoyiu/django-cors-headers/issues/3 """ resp = self.client.options( '/test-401/', HTTP_ORIGIN='http://example.com', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' def test_signal_handler_that_returns_false(self): def handler(*args, **kwargs): return False with temporary_check_request_hander(handler): resp = self.client.options( '/', HTTP_ORIGIN='http://example.com', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp def test_signal_handler_that_returns_true(self): def handler(*args, **kwargs): return True with temporary_check_request_hander(handler): resp = self.client.options( '/', HTTP_ORIGIN='http://example.com', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.com' @override_settings(CORS_ORIGIN_WHITELIST=['example.com']) def test_signal_handler_allow_some_urls_to_everyone(self): def allow_api_to_all(sender, request, **kwargs): return request.path.startswith('/api/') with temporary_check_request_hander(allow_api_to_all): resp = self.client.options( '/', HTTP_ORIGIN='http://example.org', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp resp = self.client.options( '/api/something/', HTTP_ORIGIN='http://example.org', HTTP_ACCESS_CONTROL_REQUEST_METHOD='value', ) assert resp.status_code == 200 assert resp[ACCESS_CONTROL_ALLOW_ORIGIN] == 'http://example.org' @override_settings(CORS_ORIGIN_WHITELIST=['example.com']) def test_signal_called_once_during_normal_flow(self): def allow_all(sender, request, **kwargs): allow_all.calls += 1 return True allow_all.calls = 0 with temporary_check_request_hander(allow_all): self.client.get('/', HTTP_ORIGIN='http://example.org') assert allow_all.calls == 1 @override_settings(CORS_ORIGIN_WHITELIST=['example.com']) @prepend_middleware('tests.test_middleware.ShortCircuitMiddleware') def test_get_short_circuit(self): """ Test a scenario when a middleware that returns a response is run before the ``CorsMiddleware``. In this case ``CorsMiddleware.process_response()`` should ignore the request if MIDDLEWARE setting is used (new mechanism in Django 1.10+) and process the request and add CORS response headers if MIDDLEWARE_CLASSES is used in a backward compatible fashion with django-cors-headers pre 1.3.0. """ resp = self.client.get('/', HTTP_ORIGIN='http://example.com') if django.VERSION[:2] >= (1, 10): assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp else: assert ACCESS_CONTROL_ALLOW_ORIGIN in resp @override_settings( CORS_URLS_REGEX=r'^/foo$', CORS_ORIGIN_WHITELIST=['example.com'], ) @prepend_middleware('tests.test_middleware.ShortCircuitMiddleware') def test_get_short_circuit_no_origin(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp @override_settings( CORS_URLS_REGEX=r'^/foo$', CORS_ORIGIN_WHITELIST=['example.com'], ) def test_get_no_origin_not_enabled(self): resp = self.client.get('/', HTTP_ORIGIN='http://example.com') assert ACCESS_CONTROL_ALLOW_ORIGIN not in resp @override_settings(CORS_ORIGIN_WHITELIST=['example.com']) def test_works_if_view_deletes_is_enabled(self): """ Just in case something crazy happens in the view or other middleware, check that get_response doesn't fall over if `is_enabled` is removed """ resp = self.client.get( '/delete-is-enabled/', HTTP_ORIGIN='http://example.com', ) assert ACCESS_CONTROL_ALLOW_ORIGIN in resp @override_settings( CORS_REPLACE_HTTPS_REFERER=True, CORS_ORIGIN_REGEX_WHITELIST=(r'.*example.*',), ) class RefererReplacementCorsMiddlewareTests(TestCase): def test_get_replaces_referer_when_secure(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo' ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.com/' assert resp.wsgi_request.META['ORIGINAL_HTTP_REFERER'] == 'https://example.org/foo' @append_middleware('corsheaders.middleware.CorsPostCsrfMiddleware') def test_get_post_middleware_rereplaces_referer_when_secure(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo' ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.org/foo' assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META def test_get_does_not_replace_referer_when_insecure(self): resp = self.client.get( '/', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo' ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.org/foo' assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META @override_settings(CORS_REPLACE_HTTPS_REFERER=False) def test_get_does_not_replace_referer_when_disabled(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo', ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.org/foo' assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META def test_get_does_not_fail_in_referer_replacement_when_referer_missing(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', ) assert resp.status_code == 200 assert 'HTTP_REFERER' not in resp.wsgi_request.META assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META def test_get_does_not_fail_in_referer_replacement_when_host_missing(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo', ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.org/foo' assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META @override_settings(CORS_ORIGIN_REGEX_WHITELIST=()) def test_get_does_not_replace_referer_when_not_valid_request(self): resp = self.client.get( '/', HTTP_FAKE_SECURE='true', HTTP_HOST='example.com', HTTP_ORIGIN='https://example.org', HTTP_REFERER='https://example.org/foo', ) assert resp.status_code == 200 assert resp.wsgi_request.META['HTTP_REFERER'] == 'https://example.org/foo' assert 'ORIGINAL_HTTP_REFERER' not in resp.wsgi_request.META django-cors-headers-2.1.0/tests/testapp/000077500000000000000000000000001311254654200201565ustar00rootroot00000000000000django-cors-headers-2.1.0/tests/testapp/__init__.py000066400000000000000000000000001311254654200222550ustar00rootroot00000000000000django-cors-headers-2.1.0/tests/testapp/models.py000066400000000000000000000001401311254654200220060ustar00rootroot00000000000000from corsheaders.models import AbstractCorsModel class CorsModel(AbstractCorsModel): pass django-cors-headers-2.1.0/tests/urls.py000066400000000000000000000011461311254654200200370ustar00rootroot00000000000000from django.conf.urls import url from django.http import Http404, HttpResponse def test_view(request): if request.method != 'GET': raise Http404() return HttpResponse("Test view") def test_view_http401(request): return HttpResponse('Unauthorized', status=401) def test_view_that_deletes_is_enabled(request): if hasattr(request, '_cors_enabled'): del request._cors_enabled return HttpResponse() urlpatterns = [ url(r'^$', test_view, name='test-view'), url(r'^test-401/$', test_view_http401), url(r'^delete-is-enabled/$', test_view_that_deletes_is_enabled), ] django-cors-headers-2.1.0/tests/utils.py000066400000000000000000000014071311254654200202120ustar00rootroot00000000000000from contextlib import contextmanager import django from django.test.utils import modify_settings from corsheaders.signals import check_request_enabled def add_middleware(action, path): if django.VERSION[:2] >= (1, 10): middleware_setting = 'MIDDLEWARE' else: middleware_setting = 'MIDDLEWARE_CLASSES' return modify_settings(**{ middleware_setting: { action: path, } }) def append_middleware(path): return add_middleware('append', path) def prepend_middleware(path): return add_middleware('prepend', path) @contextmanager def temporary_check_request_hander(handler): check_request_enabled.connect(handler) try: yield finally: check_request_enabled.disconnect(handler) django-cors-headers-2.1.0/tox.ini000066400000000000000000000007341311254654200166530ustar00rootroot00000000000000[tox] envlist = py{27,36}-codestyle, py{27,36}-django{18,19,110,111} [testenv] setenv = PYTHONDONTWRITEBYTECODE=1 install_command = pip install --no-deps {opts} {packages} deps = -rrequirements.txt django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 django111: Django>=1.11a1,<2.0 commands = ./runtests.py {posargs} [testenv:py27-codestyle] commands = multilint [testenv:py36-codestyle] commands = multilint