Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pontoon/base/tests/test_middleware.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue pointed out by LLMs: this test would fail locally for someone with SITE=https:// (couldn't verify on the device I'm currently in).

-def test_content_security_policy(rf):
+def test_content_security_policy(rf, settings):
     response = CSPMiddleware(lambda request: HttpResponse())(rf.get("/"))

+    # These directives get an http: fallback if the site isn't hosted on HTTPS
+    http = "" if settings.SITE_URL.startswith("https") else " http:"
+
     policy = response.headers["Content-Security-Policy"]
     assert "default-src 'self'" in policy
     assert "connect-src 'self'" in policy
     assert "font-src 'self'" in policy
-    assert "frame-src https: http:" in policy
+    assert f"frame-src https:{http}" in policy
     assert "img-src 'self'" in policy
     assert "script-src 'self' 'unsafe-eval'" in policy
     assert "style-src 'self' 'unsafe-inline'" in policy
-    assert "worker-src https: blob: http:" in policy
+    assert f"worker-src https: blob:{http}" in policy

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@

import pytest

from csp.middleware import CSPMiddleware

from django.http import HttpResponse
from django.urls import reverse
from django.utils import timezone


def test_content_security_policy(rf):
response = CSPMiddleware(lambda request: HttpResponse())(rf.get("/"))

policy = response.headers["Content-Security-Policy"]
assert "default-src 'self'" in policy
assert "connect-src 'self'" in policy
assert "font-src 'self'" in policy
assert "frame-src https: http:" in policy
assert "img-src 'self'" in policy
assert "script-src 'self' 'unsafe-eval'" in policy
assert "style-src 'self' 'unsafe-inline'" in policy
assert "worker-src https: blob: http:" in policy


@pytest.mark.django_db
def test_EmailConsentMiddleware(client, member, settings):
# By default, Email consent page is disabled
Expand Down
92 changes: 51 additions & 41 deletions pontoon/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import dj_database_url

from csp.constants import SELF, UNSAFE_EVAL, UNSAFE_INLINE

from django.utils.functional import lazy


Expand Down Expand Up @@ -280,6 +282,7 @@ def _default_from_email():
"pipeline",
"guardian",
"corsheaders",
"csp",
"allauth",
"allauth.account",
"allauth.socialaccount",
Expand Down Expand Up @@ -953,61 +956,68 @@ def _default_from_email():
os.environ.get("SECURE_SSL_REDIRECT", "True") != "False" and not DEV
)

# Content-Security-Policy headers
CSP_DEFAULT_SRC = (
# Needed for Docs
"'self'",
)
CSP_FRAME_SRC = ("https:",)
CSP_WORKER_SRC = (
# Content-Security-Policy header
csp_frame_src = ("https:",)
csp_worker_src = (
"https:",
# Needed for confetti.browser.js
"blob:",
)
CSP_CONNECT_SRC = (
"'self'",
"https://bugzilla.mozilla.org/rest/bug",
"https://region1.google-analytics.com/g/collect",
)
CSP_FONT_SRC = (
"'self'",
# Needed for Docs
"https://fonts.gstatic.com",
)
CSP_IMG_SRC = (
"'self'",
csp_img_src = (
SELF,
"https:",
# Needed for ACE editor images
"data:",
"https://*.wp.com/pontoon.mozilla.org/",
"https://www.google-analytics.com",
"https://www.gravatar.com/avatar/",
)
CSP_SCRIPT_SRC = (
"'self'",
"'unsafe-eval'",
"'sha256-fDsgbzHC0sNuBdM4W91nXVccgFLwIDkl197QEca/Cl4='",
# Needed for Google Analytics
"'sha256-MAn2iEyXLmB7sfv/20ImVRdQs8NCZ0A5SShdZsZdv20='",
"https://www.googletagmanager.com/gtag/js",
# Needed for Docs
"'sha256-DrEMJJ29sL7vIloQzly+VUGMxKcBTMII+OfW7Y8AkG4='",
"'sha256-/8wPdzX9q0NNJXyA5lzsLojXFpkeaXVxhbfkUOQaWy8='",
"'sha256-i0DgL2uLiE/Q2kHCFRPZIfz/mN3ZA/Sq08UynK9ZACY='",
"'sha256-9WmRqHphu0WtjGBriIQP5bBdmiqiG3tY04gCxNSST40='",
"'sha256-cgPnO/p6B0QlYcCUC4Ur5FXogQxKDNDgWWH3Q010y7A='",
)
CSP_STYLE_SRC = (
"'self'",
"'unsafe-inline'",
# Needed for Docs
"https://fonts.googleapis.com",
)

# Needed if site not hosted on HTTPS domains (like local setup)
if not SITE_URL.startswith("https"):
CSP_IMG_SRC = CSP_IMG_SRC + ("http://www.gravatar.com/avatar/",)
CSP_WORKER_SRC = CSP_FRAME_SRC = CSP_FRAME_SRC + ("http:",)
csp_img_src += ("http://www.gravatar.com/avatar/",)
csp_frame_src += ("http:",)
csp_worker_src += ("http:",)

CONTENT_SECURITY_POLICY = {
"DIRECTIVES": {
# Needed for Docs
"default-src": (SELF,),
"frame-src": csp_frame_src,
"worker-src": csp_worker_src,
"connect-src": (
SELF,
"https://bugzilla.mozilla.org/rest/bug",
"https://region1.google-analytics.com/g/collect",
),
"font-src": (
SELF,
# Needed for Docs
"https://fonts.gstatic.com",
),
"img-src": csp_img_src,
"script-src": (
SELF,
UNSAFE_EVAL,
"'sha256-fDsgbzHC0sNuBdM4W91nXVccgFLwIDkl197QEca/Cl4='",
# Needed for Google Analytics
"'sha256-MAn2iEyXLmB7sfv/20ImVRdQs8NCZ0A5SShdZsZdv20='",
"https://www.googletagmanager.com/gtag/js",
# Needed for Docs
"'sha256-DrEMJJ29sL7vIloQzly+VUGMxKcBTMII+OfW7Y8AkG4='",
"'sha256-/8wPdzX9q0NNJXyA5lzsLojXFpkeaXVxhbfkUOQaWy8='",
"'sha256-i0DgL2uLiE/Q2kHCFRPZIfz/mN3ZA/Sq08UynK9ZACY='",
"'sha256-9WmRqHphu0WtjGBriIQP5bBdmiqiG3tY04gCxNSST40='",
"'sha256-cgPnO/p6B0QlYcCUC4Ur5FXogQxKDNDgWWH3Q010y7A='",
),
"style-src": (
SELF,
UNSAFE_INLINE,
# Needed for Docs
"https://fonts.googleapis.com",
),
}
}

# For absolute urls
try:
Expand Down