Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the CSP header #2237

Merged
merged 1 commit into from Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Expand Up @@ -36,6 +36,7 @@ Unreleased
greenlet versions. :pr:`2212`
- Fix type annotation in ``CallbackDict``, because it is not
utilizing a bound TypeVar. :issue:`2235`
- Fix setting CSP header options on the response. :pr:`2237`


Version 2.0.1
Expand Down
83 changes: 66 additions & 17 deletions src/werkzeug/sansio/response.py
Expand Up @@ -12,12 +12,12 @@
from ..utils import get_content_type
from werkzeug.datastructures import CallbackDict
from werkzeug.datastructures import ContentRange
from werkzeug.datastructures import ContentSecurityPolicy
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
from werkzeug.http import dump_options_header
from werkzeug.http import http_date
Expand Down Expand Up @@ -567,23 +567,72 @@ def on_update(www_auth: WWWAuthenticate) -> None:

# CSP

content_security_policy = header_property(
"Content-Security-Policy",
None,
parse_csp_header, # type: ignore
dump_csp_header,
doc="""The Content-Security-Policy header adds an additional layer of
security to help detect and mitigate certain types of attacks.""",
)
content_security_policy_report_only = header_property(
"Content-Security-Policy-Report-Only",
None,
parse_csp_header, # type: ignore
dump_csp_header,
doc="""The Content-Security-Policy-Report-Only header adds a csp policy
@property
def content_security_policy(self) -> ContentSecurityPolicy:
"""The ``Content-Security-Policy`` header as a
:class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
even if the header is not set.

The Content-Security-Policy header adds an additional layer of
security to help detect and mitigate certain types of attacks.
"""

def on_update(csp: ContentSecurityPolicy) -> None:
if not csp:
del self.headers["content-security-policy"]
else:
self.headers["Content-Security-Policy"] = csp.to_header()

rv = parse_csp_header(self.headers.get("content-security-policy"), on_update)
if rv is None:
rv = ContentSecurityPolicy(None, on_update=on_update)
return rv

@content_security_policy.setter
def content_security_policy(
self, value: t.Optional[t.Union[ContentSecurityPolicy, str]]
) -> None:
if not value:
del self.headers["content-security-policy"]
elif isinstance(value, str):
self.headers["Content-Security-Policy"] = value
else:
self.headers["Content-Security-Policy"] = value.to_header()

@property
def content_security_policy_report_only(self) -> ContentSecurityPolicy:
"""The ``Content-Security-policy-report-only`` header as a
:class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
even if the header is not set.

The Content-Security-Policy-Report-Only header adds a csp policy
that is not enforced but is reported thereby helping detect
certain types of attacks.""",
)
certain types of attacks.
"""

def on_update(csp: ContentSecurityPolicy) -> None:
if not csp:
del self.headers["content-security-policy-report-only"]
else:
self.headers["Content-Security-policy-report-only"] = csp.to_header()

rv = parse_csp_header(
self.headers.get("content-security-policy-report-only"), on_update
)
if rv is None:
rv = ContentSecurityPolicy(None, on_update=on_update)
return rv

@content_security_policy_report_only.setter
def content_security_policy_report_only(
self, value: t.Optional[t.Union[ContentSecurityPolicy, str]]
) -> None:
if not value:
del self.headers["content-security-policy-report-only"]
elif isinstance(value, str):
self.headers["Content-Security-policy-report-only"] = value
else:
self.headers["Content-Security-policy-report-only"] = value.to_header()

# CORS

Expand Down
14 changes: 14 additions & 0 deletions tests/test_wrappers.py
Expand Up @@ -1343,6 +1343,20 @@ def test_ranges():
assert resp.content_range.length == 1000


def test_csp():
resp = wrappers.Response()
resp.content_security_policy.default_src = "'self'"
assert resp.headers["Content-Security-Policy"] == "default-src 'self'"
resp.content_security_policy.script_src = "'self' palletsprojects.com"
assert (
resp.headers["Content-Security-Policy"]
== "default-src 'self'; script-src 'self' palletsprojects.com"
)

resp.content_security_policy = None
assert "Content-Security-Policy" not in resp.headers


def test_auto_content_length():
resp = wrappers.Response("Hello World!")
assert resp.content_length == 12
Expand Down