123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- # -*- coding: utf-8 -*-
- """
- extension
- ~~~~
- Flask-CORS is a simple extension to Flask allowing you to support cross
- origin resource sharing (CORS) using a simple decorator.
- :copyright: (c) 2016 by Cory Dolphin.
- :license: MIT, see LICENSE for more details.
- """
- import logging
- from urllib.parse import unquote_plus
- from flask import request
- from .core import (
- parse_resources,
- get_cors_options,
- get_regexp_pattern,
- ACL_ORIGIN,
- try_match,
- set_cors_headers
- )
- LOG = logging.getLogger(__name__)
- class CORS(object):
- """
- Initializes Cross Origin Resource sharing for the application. The
- arguments are identical to :py:func:`cross_origin`, with the addition of a
- `resources` parameter. The resources parameter defines a series of regular
- expressions for resource paths to match and optionally, the associated
- options to be applied to the particular resource. These options are
- identical to the arguments to :py:func:`cross_origin`.
- The settings for CORS are determined in the following order
- 1. Resource level settings (e.g when passed as a dictionary)
- 2. Keyword argument settings
- 3. App level configuration settings (e.g. CORS_*)
- 4. Default settings
- Note: as it is possible for multiple regular expressions to match a
- resource path, the regular expressions are first sorted by length,
- from longest to shortest, in order to attempt to match the most
- specific regular expression. This allows the definition of a
- number of specific resource options, with a wildcard fallback
- for all other resources.
- :param resources:
- The series of regular expression and (optionally) associated CORS
- options to be applied to the given resource path.
- If the argument is a dictionary, it's keys must be regular expressions,
- and the values must be a dictionary of kwargs, identical to the kwargs
- of this function.
- If the argument is a list, it is expected to be a list of regular
- expressions, for which the app-wide configured options are applied.
- If the argument is a string, it is expected to be a regular expression
- for which the app-wide configured options are applied.
- Default : Match all and apply app-level configuration
- :type resources: dict, iterable or string
- :param origins:
- The origin, or list of origins to allow requests from.
- The origin(s) may be regular expressions, case-sensitive strings,
- or else an asterisk.
- :note: origins must include the schema and the port (if not port 80),
- e.g.,
- `CORS(app, origins=["http://localhost:8000", "https://example.com"])`.
- Default : '*'
- :type origins: list, string or regex
- :param methods:
- The method or list of methods which the allowed origins are allowed to
- access for non-simple requests.
- Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]
- :type methods: list or string
- :param expose_headers:
- The header or list which are safe to expose to the API of a CORS API
- specification.
- Default : None
- :type expose_headers: list or string
- :param allow_headers:
- The header or list of header field names which can be used when this
- resource is accessed by allowed origins. The header(s) may be regular
- expressions, case-sensitive strings, or else an asterisk.
- Default : '*', allow all headers
- :type allow_headers: list, string or regex
- :param supports_credentials:
- Allows users to make authenticated requests. If true, injects the
- `Access-Control-Allow-Credentials` header in responses. This allows
- cookies and credentials to be submitted across domains.
- :note: This option cannot be used in conjunction with a '*' origin
- Default : False
- :type supports_credentials: bool
- :param max_age:
- The maximum time for which this CORS request maybe cached. This value
- is set as the `Access-Control-Max-Age` header.
- Default : None
- :type max_age: timedelta, integer, string or None
- :param send_wildcard: If True, and the origins parameter is `*`, a wildcard
- `Access-Control-Allow-Origin` header is sent, rather than the
- request's `Origin` header.
- Default : False
- :type send_wildcard: bool
- :param vary_header:
- If True, the header Vary: Origin will be returned as per the W3
- implementation guidelines.
- Setting this header when the `Access-Control-Allow-Origin` is
- dynamically generated (e.g. when there is more than one allowed
- origin, and an Origin than '*' is returned) informs CDNs and other
- caches that the CORS headers are dynamic, and cannot be cached.
- If False, the Vary header will never be injected or altered.
- Default : True
- :type vary_header: bool
- """
- def __init__(self, app=None, **kwargs):
- self._options = kwargs
- if app is not None:
- self.init_app(app, **kwargs)
- def init_app(self, app, **kwargs):
- # The resources and options may be specified in the App Config, the CORS constructor
- # or the kwargs to the call to init_app.
- options = get_cors_options(app, self._options, kwargs)
- # Flatten our resources into a list of the form
- # (pattern_or_regexp, dictionary_of_options)
- resources = parse_resources(options.get('resources'))
- # Compute the options for each resource by combining the options from
- # the app's configuration, the constructor, the kwargs to init_app, and
- # finally the options specified in the resources dictionary.
- resources = [
- (pattern, get_cors_options(app, options, opts))
- for (pattern, opts) in resources
- ]
- # Create a human-readable form of these resources by converting the compiled
- # regular expressions into strings.
- resources_human = {get_regexp_pattern(pattern): opts for (pattern,opts) in resources}
- LOG.debug("Configuring CORS with resources: %s", resources_human)
- cors_after_request = make_after_request_function(resources)
- app.after_request(cors_after_request)
- # Wrap exception handlers with cross_origin
- # These error handlers will still respect the behavior of the route
- if options.get('intercept_exceptions', True):
- def _after_request_decorator(f):
- def wrapped_function(*args, **kwargs):
- return cors_after_request(app.make_response(f(*args, **kwargs)))
- return wrapped_function
- if hasattr(app, 'handle_exception'):
- app.handle_exception = _after_request_decorator(
- app.handle_exception)
- app.handle_user_exception = _after_request_decorator(
- app.handle_user_exception)
- def make_after_request_function(resources):
- def cors_after_request(resp):
- # If CORS headers are set in a view decorator, pass
- if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
- LOG.debug('CORS have been already evaluated, skipping')
- return resp
- normalized_path = unquote_plus(request.path)
- for res_regex, res_options in resources:
- if try_match(normalized_path, res_regex):
- LOG.debug("Request to '%s' matches CORS resource '%s'. Using options: %s",
- request.path, get_regexp_pattern(res_regex), res_options)
- set_cors_headers(resp, res_options)
- break
- else:
- LOG.debug('No CORS rule matches')
- return resp
- return cors_after_request
|