From a413881799223e422fbefff9d7833005f60030f1 Mon Sep 17 00:00:00 2001 From: pgjones Date: Mon, 18 Jan 2021 12:45:07 +0000 Subject: [PATCH] Add Cross-Origin-Opener/Embedder-Policy response headers These header can only have a few distinct values, hence the use of an enum. They are more recent headers, and make it easier to control these policies (e.g. not having to write noopener everywhere). This code makes it a little easier to use the headers correctly - as the Enum fixes the values (preventing typos, mistakes etc). https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy --- CHANGES.rst | 4 +++- src/werkzeug/http.py | 16 ++++++++++++++++ src/werkzeug/wrappers/response.py | 21 +++++++++++++++++++++ tests/test_wrappers.py | 16 ++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7aa0479b4..9c3afc517 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -117,7 +117,9 @@ Unreleased and in some tests. MD5 is not available in some environments, such as FIPS 140. This may invalidate some caches since the ETag will be different. :issue:`1897` - +- Add ``Cross-Origin-Opener-Policy`` and + ``Cross-Origin-Embedder-Policy`` response header properties. + :pr:`2008` Version 1.0.2 ------------- diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py index 880e297e5..8db405a0e 100644 --- a/src/werkzeug/http.py +++ b/src/werkzeug/http.py @@ -6,6 +6,7 @@ from datetime import datetime from datetime import timedelta from email.utils import parsedate_tz +from enum import Enum from hashlib import sha1 from time import gmtime from time import struct_time @@ -172,6 +173,21 @@ } +class COEP(Enum): + """Cross Origin Embedder Policies""" + + UNSAFE_NONE = "unsafe-none" + REQUIRE_CORP = "require-corp" + + +class COOP(Enum): + """Cross Origin Opener Policies""" + + UNSAFE_NONE = "unsafe-none" + SAME_ORIGIN_ALLOW_POPUPS = "same-origin-allow-popups" + SAME_ORIGIN = "same-origin" + + def quote_header_value( value: t.Union[str, int], extra_chars: str = "", allow_token: bool = True ) -> str: diff --git a/src/werkzeug/wrappers/response.py b/src/werkzeug/wrappers/response.py index 94207f12e..c956abc00 100644 --- a/src/werkzeug/wrappers/response.py +++ b/src/werkzeug/wrappers/response.py @@ -22,6 +22,8 @@ from werkzeug.datastructures import ContentRange from werkzeug.datastructures import ResponseCacheControl from werkzeug.datastructures import WWWAuthenticate +from werkzeug.http import COEP +from werkzeug.http import COOP from werkzeug.http import dump_age from werkzeug.http import dump_csp_header from werkzeug.http import dump_header @@ -1376,6 +1378,25 @@ def access_control_allow_credentials(self, value: t.Optional[bool]) -> None: doc="The maximum age in seconds the access control settings can be cached for.", ) + cross_origin_opener_policy = header_property[COOP]( + "Cross-Origin-Opener-Policy", + load_func=lambda value: COOP(value), + dump_func=lambda value: value.value, + default=COOP.UNSAFE_NONE, + doc="""Allows control over sharing of browsing context group with cross-origin + documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""", + ) + + cross_origin_embedder_policy = header_property[COEP]( + "Cross-Origin-Embedder-Policy", + load_func=lambda value: COEP(value), + dump_func=lambda value: value.value, + default=COEP.UNSAFE_NONE, + doc="""Prevents a document from loading any cross-origin resources that do not + explicitly grant the document permission. Values must be a member of the + :class:`werkzeug.http.COEP` enum.""", + ) + class ResponseStream: """A file descriptor like object used by the :class:`ResponseStreamMixin` to diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index c3df604f9..34d756c93 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -22,6 +22,8 @@ from werkzeug.exceptions import BadRequest from werkzeug.exceptions import RequestedRangeNotSatisfiable from werkzeug.exceptions import SecurityError +from werkzeug.http import COEP +from werkzeug.http import COOP from werkzeug.http import generate_etag from werkzeug.test import Client from werkzeug.test import create_environ @@ -1576,3 +1578,17 @@ def test_check_base_deprecated(): def test_response_freeze_no_etag_deprecated(): with pytest.raises(DeprecationWarning, match="no_etag"): Response("Hello, World!").freeze(no_etag=True) + + +def test_response_coop(): + response = wrappers.Response("Hello World") + assert response.cross_origin_opener_policy is COOP.UNSAFE_NONE + response.cross_origin_opener_policy = COOP.SAME_ORIGIN + assert response.headers["Cross-Origin-Opener-Policy"] == "same-origin" + + +def test_response_coep(): + response = wrappers.Response("Hello World") + assert response.cross_origin_embedder_policy is COEP.UNSAFE_NONE + response.cross_origin_embedder_policy = COEP.REQUIRE_CORP + assert response.headers["Cross-Origin-Embedder-Policy"] == "require-corp"