Skip to content

➕(back) install and configure django csp #1085

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ These are the environment variables you can set for the `impress-backend` contai
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
| THEME_CUSTOMIZATION_FILE_PATH | full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |

| CONTENT_SECURITY_POLICY_EXCLUDE_URL_PREFIXES | Url with this prefix will not have the header Content-Security-Policy included | |
| CONTENT_SECURITY_POLICY_DIRECTIVES | A dict of directives set in the Content-Security-Policy header | All directives are set to 'none' |

## impress-frontend image

Expand Down
4 changes: 3 additions & 1 deletion src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import requests
import rest_framework as drf
from botocore.exceptions import ClientError
from csp.constants import NONE
from csp.decorators import csp_update
from lasuite.malware_detection import malware_detection
from rest_framework import filters, status, viewsets
from rest_framework import response as drf_response
Expand Down Expand Up @@ -1412,6 +1414,7 @@ def ai_translate(self, request, *args, **kwargs):
name="",
url_path="cors-proxy",
)
@csp_update({"img-src": [NONE, "data:"]})
def cors_proxy(self, request, *args, **kwargs):
"""
GET /api/v1.0/documents/<resource_id>/cors-proxy
Expand Down Expand Up @@ -1452,7 +1455,6 @@ def cors_proxy(self, request, *args, **kwargs):
content_type=content_type,
headers={
"Content-Disposition": "attachment;",
"Content-Security-Policy": "default-src 'none'; img-src 'none' data:;",
},
status=response.status_code,
)
Expand Down
46 changes: 38 additions & 8 deletions src/backend/core/tests/documents/test_api_documents_cors_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,25 @@ def test_api_docs_cors_proxy_valid_url():
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/png"
assert response.headers["Content-Disposition"] == "attachment;"
assert (
response.headers["Content-Security-Policy"]
== "default-src 'none'; img-src 'none' data:;"
)
policy_list = sorted(response.headers["Content-Security-Policy"].split("; "))
assert policy_list == [
"base-uri 'none'",
"child-src 'none'",
"connect-src 'none'",
"default-src 'none'",
"font-src 'none'",
"form-action 'none'",
"frame-ancestors 'none'",
"frame-src 'none'",
"img-src 'none' data:",
"manifest-src 'none'",
"media-src 'none'",
"object-src 'none'",
"prefetch-src 'none'",
"script-src 'none'",
"style-src 'none'",
"worker-src 'none'",
]
assert response.streaming_content


Expand Down Expand Up @@ -77,10 +92,25 @@ def test_api_docs_cors_proxy_authenticated_user_accessing_protected_doc():
assert response.status_code == 200
assert response.headers["Content-Type"] == "image/png"
assert response.headers["Content-Disposition"] == "attachment;"
assert (
response.headers["Content-Security-Policy"]
== "default-src 'none'; img-src 'none' data:;"
)
policy_list = sorted(response.headers["Content-Security-Policy"].split("; "))
assert policy_list == [
"base-uri 'none'",
"child-src 'none'",
"connect-src 'none'",
"default-src 'none'",
"font-src 'none'",
"form-action 'none'",
"frame-ancestors 'none'",
"frame-src 'none'",
"img-src 'none' data:",
"manifest-src 'none'",
"media-src 'none'",
"object-src 'none'",
"prefetch-src 'none'",
"script-src 'none'",
"style-src 'none'",
"worker-src 'none'",
]
assert response.streaming_content


Expand Down
19 changes: 19 additions & 0 deletions src/backend/core/tests/test_api_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,25 @@ def test_api_config(is_authenticated):
"AI_FEATURE_ENABLED": False,
"theme_customization": {},
}
policy_list = sorted(response.headers["Content-Security-Policy"].split("; "))
assert policy_list == [
"base-uri 'none'",
"child-src 'none'",
"connect-src 'none'",
"default-src 'none'",
"font-src 'none'",
"form-action 'none'",
"frame-ancestors 'none'",
"frame-src 'none'",
"img-src 'none'",
"manifest-src 'none'",
"media-src 'none'",
"object-src 'none'",
"prefetch-src 'none'",
"script-src 'none'",
"style-src 'none'",
"worker-src 'none'",
]


@override_settings(
Expand Down
37 changes: 37 additions & 0 deletions src/backend/impress/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@

import sentry_sdk
from configurations import Configuration, values
from csp.constants import NONE
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger

# pylint: disable=too-many-lines

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = os.getenv("DATA_DIR", os.path.join("/", "data"))
Expand Down Expand Up @@ -285,6 +288,7 @@ class Base(Configuration):
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"dockerflow.django.middleware.DockerflowMiddleware",
"csp.middleware.CSPMiddleware",
]

AUTHENTICATION_BACKENDS = [
Expand Down Expand Up @@ -318,6 +322,7 @@ class Base(Configuration):
# OIDC third party
"mozilla_django_oidc",
"lasuite.malware_detection",
"csp",
]

# Cache
Expand Down Expand Up @@ -717,6 +722,38 @@ class Base(Configuration):
environ_prefix=None,
)

# Content Security Policy
# See https://content-security-policy.com/ for more information.
CONTENT_SECURITY_POLICY = {
"EXCLUDE_URL_PREFIXES": values.ListValue(
[],
environ_name="CONTENT_SECURITY_POLICY_EXCLUDE_URL_PREFIXES",
environ_prefix=None,
),
"DIRECTIVES": values.DictValue(
default={
"default-src": [NONE],
"script-src": [NONE],
"style-src": [NONE],
"img-src": [NONE],
"connect-src": [NONE],
"font-src": [NONE],
"object-src": [NONE],
"media-src": [NONE],
"frame-src": [NONE],
"child-src": [NONE],
"form-action": [NONE],
"frame-ancestors": [NONE],
"base-uri": [NONE],
"worker-src": [NONE],
"manifest-src": [NONE],
"prefetch-src": [NONE],
},
environ_name="CONTENT_SECURITY_POLICY_DIRECTIVES",
environ_prefix=None,
),
}

# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
Expand Down
1 change: 1 addition & 0 deletions src/backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies = [
"django-configurations==2.5.1",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
"django-csp==4.0",
"django-filter==25.1",
"django-lasuite[all]==0.0.9",
"django-parler==2.3",
Expand Down
Loading