extension.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # -*- coding: utf-8 -*-
  2. """
  3. extension
  4. ~~~~
  5. Flask-CORS is a simple extension to Flask allowing you to support cross
  6. origin resource sharing (CORS) using a simple decorator.
  7. :copyright: (c) 2016 by Cory Dolphin.
  8. :license: MIT, see LICENSE for more details.
  9. """
  10. import logging
  11. from urllib.parse import unquote_plus
  12. from flask import request
  13. from .core import (
  14. parse_resources,
  15. get_cors_options,
  16. get_regexp_pattern,
  17. ACL_ORIGIN,
  18. try_match,
  19. set_cors_headers
  20. )
  21. LOG = logging.getLogger(__name__)
  22. class CORS(object):
  23. """
  24. Initializes Cross Origin Resource sharing for the application. The
  25. arguments are identical to :py:func:`cross_origin`, with the addition of a
  26. `resources` parameter. The resources parameter defines a series of regular
  27. expressions for resource paths to match and optionally, the associated
  28. options to be applied to the particular resource. These options are
  29. identical to the arguments to :py:func:`cross_origin`.
  30. The settings for CORS are determined in the following order
  31. 1. Resource level settings (e.g when passed as a dictionary)
  32. 2. Keyword argument settings
  33. 3. App level configuration settings (e.g. CORS_*)
  34. 4. Default settings
  35. Note: as it is possible for multiple regular expressions to match a
  36. resource path, the regular expressions are first sorted by length,
  37. from longest to shortest, in order to attempt to match the most
  38. specific regular expression. This allows the definition of a
  39. number of specific resource options, with a wildcard fallback
  40. for all other resources.
  41. :param resources:
  42. The series of regular expression and (optionally) associated CORS
  43. options to be applied to the given resource path.
  44. If the argument is a dictionary, it's keys must be regular expressions,
  45. and the values must be a dictionary of kwargs, identical to the kwargs
  46. of this function.
  47. If the argument is a list, it is expected to be a list of regular
  48. expressions, for which the app-wide configured options are applied.
  49. If the argument is a string, it is expected to be a regular expression
  50. for which the app-wide configured options are applied.
  51. Default : Match all and apply app-level configuration
  52. :type resources: dict, iterable or string
  53. :param origins:
  54. The origin, or list of origins to allow requests from.
  55. The origin(s) may be regular expressions, case-sensitive strings,
  56. or else an asterisk.
  57. :note: origins must include the schema and the port (if not port 80),
  58. e.g.,
  59. `CORS(app, origins=["http://localhost:8000", "https://example.com"])`.
  60. Default : '*'
  61. :type origins: list, string or regex
  62. :param methods:
  63. The method or list of methods which the allowed origins are allowed to
  64. access for non-simple requests.
  65. Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]
  66. :type methods: list or string
  67. :param expose_headers:
  68. The header or list which are safe to expose to the API of a CORS API
  69. specification.
  70. Default : None
  71. :type expose_headers: list or string
  72. :param allow_headers:
  73. The header or list of header field names which can be used when this
  74. resource is accessed by allowed origins. The header(s) may be regular
  75. expressions, case-sensitive strings, or else an asterisk.
  76. Default : '*', allow all headers
  77. :type allow_headers: list, string or regex
  78. :param supports_credentials:
  79. Allows users to make authenticated requests. If true, injects the
  80. `Access-Control-Allow-Credentials` header in responses. This allows
  81. cookies and credentials to be submitted across domains.
  82. :note: This option cannot be used in conjunction with a '*' origin
  83. Default : False
  84. :type supports_credentials: bool
  85. :param max_age:
  86. The maximum time for which this CORS request maybe cached. This value
  87. is set as the `Access-Control-Max-Age` header.
  88. Default : None
  89. :type max_age: timedelta, integer, string or None
  90. :param send_wildcard: If True, and the origins parameter is `*`, a wildcard
  91. `Access-Control-Allow-Origin` header is sent, rather than the
  92. request's `Origin` header.
  93. Default : False
  94. :type send_wildcard: bool
  95. :param vary_header:
  96. If True, the header Vary: Origin will be returned as per the W3
  97. implementation guidelines.
  98. Setting this header when the `Access-Control-Allow-Origin` is
  99. dynamically generated (e.g. when there is more than one allowed
  100. origin, and an Origin than '*' is returned) informs CDNs and other
  101. caches that the CORS headers are dynamic, and cannot be cached.
  102. If False, the Vary header will never be injected or altered.
  103. Default : True
  104. :type vary_header: bool
  105. """
  106. def __init__(self, app=None, **kwargs):
  107. self._options = kwargs
  108. if app is not None:
  109. self.init_app(app, **kwargs)
  110. def init_app(self, app, **kwargs):
  111. # The resources and options may be specified in the App Config, the CORS constructor
  112. # or the kwargs to the call to init_app.
  113. options = get_cors_options(app, self._options, kwargs)
  114. # Flatten our resources into a list of the form
  115. # (pattern_or_regexp, dictionary_of_options)
  116. resources = parse_resources(options.get('resources'))
  117. # Compute the options for each resource by combining the options from
  118. # the app's configuration, the constructor, the kwargs to init_app, and
  119. # finally the options specified in the resources dictionary.
  120. resources = [
  121. (pattern, get_cors_options(app, options, opts))
  122. for (pattern, opts) in resources
  123. ]
  124. # Create a human-readable form of these resources by converting the compiled
  125. # regular expressions into strings.
  126. resources_human = {get_regexp_pattern(pattern): opts for (pattern,opts) in resources}
  127. LOG.debug("Configuring CORS with resources: %s", resources_human)
  128. cors_after_request = make_after_request_function(resources)
  129. app.after_request(cors_after_request)
  130. # Wrap exception handlers with cross_origin
  131. # These error handlers will still respect the behavior of the route
  132. if options.get('intercept_exceptions', True):
  133. def _after_request_decorator(f):
  134. def wrapped_function(*args, **kwargs):
  135. return cors_after_request(app.make_response(f(*args, **kwargs)))
  136. return wrapped_function
  137. if hasattr(app, 'handle_exception'):
  138. app.handle_exception = _after_request_decorator(
  139. app.handle_exception)
  140. app.handle_user_exception = _after_request_decorator(
  141. app.handle_user_exception)
  142. def make_after_request_function(resources):
  143. def cors_after_request(resp):
  144. # If CORS headers are set in a view decorator, pass
  145. if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
  146. LOG.debug('CORS have been already evaluated, skipping')
  147. return resp
  148. normalized_path = unquote_plus(request.path)
  149. for res_regex, res_options in resources:
  150. if try_match(normalized_path, res_regex):
  151. LOG.debug("Request to '%s' matches CORS resource '%s'. Using options: %s",
  152. request.path, get_regexp_pattern(res_regex), res_options)
  153. set_cors_headers(resp, res_options)
  154. break
  155. else:
  156. LOG.debug('No CORS rule matches')
  157. return resp
  158. return cors_after_request