Skip to content

Commit

Permalink
Add Access Control, CORS (Cross Origin Request Sharing) header methods
Browse files Browse the repository at this point in the history
This should make it a little easier to get and set access control
headers as it ensures the types and naming is correct. It is also
intentionally very minimal like the other header accessors.
  • Loading branch information
pgjones committed Jan 14, 2020
1 parent 1c4837a commit 6cb8615
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -88,6 +88,8 @@ Unreleased
and kwargs. :pr:`1687, 1697`
- The development server accepts paths that start with two slashes,
rather than stripping off the first path segment. :issue:`491`
- Add access control (Cross Origin Request Sharing, CORS) header
methods to the Request and Response wrappers. :pr:`1699`


Version 0.16.1
Expand Down
106 changes: 106 additions & 0 deletions src/werkzeug/wrappers/cors.py
@@ -0,0 +1,106 @@
from ..http import dump_header
from ..http import parse_set_header
from ..utils import environ_property
from ..utils import header_property


class RequestCORSMixin(object):
"""A mixin for :class:`BaseRequest` subclasses. Request objects that
mix this class in will automatically get descriptors for Cross
Origin Resource Sharing headers.
.. versionadded:: 1.0
"""

origin = environ_property(
"HTTP_ORIGIN",
doc="""The origin header field indicates the host that the request
originated from.""",
)

access_control_request_headers = environ_property(
"HTTP_ACCESS_CONTROL_REQUEST_HEADERS",
load_func=parse_set_header,
doc="""The Access-Control-Request-Headers field is set on a preflight
request to indicate what headers will be sent on the cross
origin request. This allows the server to reply indicating
which headers are allowed.""",
)

access_control_request_method = environ_property(
"HTTP_ACCESS_CONTROL_REQUEST_METHOD",
doc="""The Access-Control-Request-Method field is set on a preflight
request to indicate which method will be used on the cross
origin request. This allows the server to reply indicating
which method is allowed.""",
)


class ResponseCORSMixin(object):
"""A mixin for :class:`BaseResponse` subclasses. Response objects that
mix this class in will automatically get descriptors for Cross
Origin Resource Sharing headers.
.. versionadded:: 1.0
"""

@property
def access_control_allow_credentials(self):
"""Indicate whether credentials can be shared by the browser to the
javascript code. As part of the preflight request it indicates
whether credentials can be used on the cross origin
request.
"""
if "Access-Control-Allow-Credentials" in self.headers:
return True
else:
return False

@access_control_allow_credentials.setter
def access_control_allow_credentials(self, value):
"""Indicate whether credentials can be shared by the browser to the
javascript code. As part of the preflight request it indicates
whether credentials can be used on the cross origin
request.
"""
if value is True:
self.headers["Access-Control-Allow-Credentials"] = "true"
else:
self.headers.pop("Access-Control-Allow-Credentials", None)

access_control_allow_headers = header_property(
"Access-Control-Allow-Headers",
load_func=parse_set_header,
dump_func=dump_header,
doc="""Indicate which headers can be used on the cross origin request.""",
)

access_control_allow_methods = header_property(
"Access-Control-Allow-Methods",
load_func=parse_set_header,
dump_func=dump_header,
doc="""Indicate which methods can be used on the cross origin request.""",
)

access_control_allow_origin = header_property(
"Access-Control-Allow-Origin",
load_func=parse_set_header,
dump_func=dump_header,
doc="""Indicate the origins that may make cross origin requests.""",
)

access_control_expose_headers = header_property(
"Access-Control-Expose-Headers",
load_func=parse_set_header,
dump_func=dump_header,
doc="""Indicate which headers can be shared by the browser to the
javascript code.""",
)

access_control_max_age = header_property(
"Access-Control-Max-Age",
load_func=int,
dump_func=str,
doc="""Indicate the maximum age in seconds the access control settings can
be cached for.""",
)
5 changes: 5 additions & 0 deletions src/werkzeug/wrappers/request.py
Expand Up @@ -2,6 +2,7 @@
from .auth import AuthorizationMixin
from .base_request import BaseRequest
from .common_descriptors import CommonRequestDescriptorsMixin
from .cors import RequestCORSMixin
from .etag import ETagRequestMixin
from .user_agent import UserAgentMixin

Expand All @@ -12,6 +13,7 @@ class Request(
ETagRequestMixin,
UserAgentMixin,
AuthorizationMixin,
RequestCORSMixin,
CommonRequestDescriptorsMixin,
):
"""Full featured request object implementing the following mixins:
Expand All @@ -21,6 +23,9 @@ class Request(
- :class:`UserAgentMixin` for user agent introspection
- :class:`AuthorizationMixin` for http auth handling
- :class:`CommonRequestDescriptorsMixin` for common headers
- :class:`RequestCORSMixin` for Cross Origin Resource Sharing
headers
"""


Expand Down
4 changes: 4 additions & 0 deletions src/werkzeug/wrappers/response.py
Expand Up @@ -2,6 +2,7 @@
from .auth import WWWAuthenticateMixin
from .base_response import BaseResponse
from .common_descriptors import CommonResponseDescriptorsMixin
from .cors import ResponseCORSMixin
from .etag import ETagResponseMixin


Expand Down Expand Up @@ -68,11 +69,14 @@ class Response(
ResponseStreamMixin,
CommonResponseDescriptorsMixin,
WWWAuthenticateMixin,
ResponseCORSMixin,
):
"""Full featured response object implementing the following mixins:
- :class:`ETagResponseMixin` for etag and cache control handling
- :class:`ResponseStreamMixin` to add support for the `stream` property
- :class:`CommonResponseDescriptorsMixin` for various HTTP descriptors
- :class:`WWWAuthenticateMixin` for HTTP authentication support
- :class:`ResponseCORSMixin` for Cross Origin Resource Sharing
headers
"""
25 changes: 25 additions & 0 deletions tests/test_wrappers.py
Expand Up @@ -270,6 +270,31 @@ def failing_application(request):
assert resp.status_code == 400


def test_request_access_control():
request = wrappers.Request.from_values(
headers={
"Origin": "https://palletsprojects.com",
"Access-Control-Request-Headers": "X-A, X-B",
"Access-Control-Request-Method": "PUT",
},
)
assert request.origin == "https://palletsprojects.com"
assert request.access_control_request_headers == {"X-A", "X-B"}
assert request.access_control_request_method == "PUT"


def test_response_access_control():
response = wrappers.Response("Hello World")
assert response.access_control_allow_credentials is False
response.access_control_allow_credentials = True
response.access_control_allow_headers = ["X-A", "X-B"]
assert response.headers["Access-Control-Allow-Credentials"] == "true"
assert set(response.headers["Access-Control-Allow-Headers"].split(", ")) == {
"X-A",
"X-B",
}


def test_base_response():
# unicode
response = wrappers.BaseResponse(u"öäü")
Expand Down

0 comments on commit 6cb8615

Please sign in to comment.