Skip to content

Commit

Permalink
Adding integrity for css and js files and optional nonce for js (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ash-Crow authored Oct 25, 2022
1 parent 0f9409e commit 39af736
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 20 deletions.
17 changes: 17 additions & 0 deletions dsfr/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Integrity checks for the css/js files
# Generated with the following command:
# openssl dgst -sha384 -binary path/to/file.min.css | openssl base64 -A


INTEGRITY_JS_MODULE = (
"sha384-S0rArPSbImK+pbCuenUI94sCELE0j2TcUqSauDNeTLZxL3HBr6ilBWCzU6605Lhc"
)
INTEGRITY_JS_NOMODULE = (
"sha384-/Ki29nOlnayQ7/etxVpEaxmfMye/syzFZVrgkd93WGAi4/WlVvVLhLp9GMvtPP+y"
)
INTEGRITY_CSS = (
"sha384-C/tHGtxXFiwg9vEKg7jcH+8kQhlkUfq22JBeptJ8AqGHcArh9k/LdedUi42QQRRi"
)
INTEGRITY_CSS_ICONS = (
"sha384-/chTXCOZpCTu7roNmemf+/rzzYwswg3UdqDqTYmx1sxYnJjDjvUWSfu+6lk02dHh"
)
4 changes: 2 additions & 2 deletions dsfr/templates/dsfr/global_css.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load static %}
<link rel="stylesheet" href="{% static 'dsfr/dist/dsfr/dsfr.min.css' %}">
<link rel="stylesheet" href="{% static 'dsfr/dist/utility/icons/icons.min.css' %}">
<link rel="stylesheet" href="{% static 'dsfr/dist/dsfr/dsfr.min.css' %}" integrity="{{ self.INTEGRITY_CSS }}">
<link rel="stylesheet" href="{% static 'dsfr/dist/utility/icons/icons.min.css' %}" integrity="{{ self.INTEGRITY_CSS_ICONS }}">

<meta name="theme-color" content="#000091"><!-- Définit la couleur de thème du navigateur (Safari/Android) -->
4 changes: 2 additions & 2 deletions dsfr/templates/dsfr/global_js.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{% load static %}
<script type="module" src="{% static 'dsfr/dist/dsfr/dsfr.module.min.js' %}"></script>
<script nomodule src="{% static 'dsfr/dist/dsfr/dsfr.nomodule.min.js' %}"></script>
<script type="module" src="{% static 'dsfr/dist/dsfr/dsfr.module.min.js' %}" integrity="{{ self.INTEGRITY_JS_MODULE }}"{% if self.nonce %} nonce="{{self.nonce}}"{% endif %}></script>
<script nomodule src="{% static 'dsfr/dist/dsfr/dsfr.nomodule.min.js' %}" integrity="{{ self.INTEGRITY_JS_NOMODULE }}" {% if self.nonce %} nonce="{{self.nonce}}"{% endif %}></script>
36 changes: 28 additions & 8 deletions dsfr/templatetags/dsfr_tags.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from django import template
from django.core.paginator import Page
from django.template.context import Context

from dsfr.constants import (
INTEGRITY_CSS,
INTEGRITY_CSS_ICONS,
INTEGRITY_JS_MODULE,
INTEGRITY_JS_NOMODULE,
)
from dsfr.utils import (
find_active_menu_items,
generate_random_id,
parse_tag_args,
)
from django import template
from django.core.paginator import Page
from django.template.context import Context

register = template.Library()
"""
Expand All @@ -16,7 +23,7 @@


@register.inclusion_tag("dsfr/global_css.html")
def dsfr_css() -> None:
def dsfr_css() -> dict:
"""
Returns the HTML for the CSS header tags for DSFR
Expand All @@ -25,11 +32,15 @@ def dsfr_css() -> None:
**Usage**::
{% dsfr_css %}
"""
return None
tag_data = {}
tag_data["INTEGRITY_CSS"] = INTEGRITY_CSS
tag_data["INTEGRITY_CSS_ICONS"] = INTEGRITY_CSS_ICONS

return {"self": tag_data}

@register.inclusion_tag("dsfr/global_js.html")
def dsfr_js() -> None:

@register.inclusion_tag("dsfr/global_js.html", takes_context=True)
def dsfr_js(context, *args, **kwargs) -> dict:
"""
Returns the HTML for the JS body tags for DSFR
Expand All @@ -38,7 +49,16 @@ def dsfr_js() -> None:
**Usage**::
{% dsfr_js %}
"""
return None

allowed_keys = [
"nonce",
]
tag_data = parse_tag_args(args, kwargs, allowed_keys)

tag_data["INTEGRITY_JS_MODULE"] = INTEGRITY_JS_MODULE
tag_data["INTEGRITY_JS_NOMODULE"] = INTEGRITY_JS_NOMODULE

return {"self": tag_data}


@register.inclusion_tag("dsfr/favicon.html")
Expand Down
28 changes: 23 additions & 5 deletions dsfr/test/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from dsfr.templatetags.dsfr_tags import concatenate, hyphenate
from django.test import SimpleTestCase
from django.template import Context, Template
from unittest.mock import MagicMock

from dsfr.constants import INTEGRITY_CSS, INTEGRITY_JS_MODULE, INTEGRITY_JS_NOMODULE
from dsfr.templatetags.dsfr_tags import concatenate, hyphenate


class DsfrCssTagTest(SimpleTestCase):
def test_css_tag_rendered(self):
context = Context()
template_to_render = Template("{% load dsfr_tags %} {% dsfr_css %}")
rendered_template = template_to_render.render(context)
self.assertInHTML(
'<link rel="stylesheet" href="/django-dsfr/static/dsfr/dist/dsfr/dsfr.min.css">',
f'<link rel="stylesheet" href="/django-dsfr/static/dsfr/dist/dsfr/dsfr.min.css" integrity="{ INTEGRITY_CSS }">',
rendered_template,
)

Expand All @@ -21,9 +23,25 @@ def test_js_tag_rendered(self):
template_to_render = Template("{% load dsfr_tags %} {% dsfr_js %}")
rendered_template = template_to_render.render(context)
self.assertInHTML(
"""
<script type="module" src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.module.min.js"></script>
<script nomodule src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.nomodule.min.js"></script>
f"""
<script type="module" src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.module.min.js" integrity="{ INTEGRITY_JS_MODULE }"></script>
<script nomodule src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.nomodule.min.js" integrity="{ INTEGRITY_JS_NOMODULE }"></script>
""",
rendered_template,
)


class DsfrJsTagWithNonceTest(SimpleTestCase):
def test_js_tag_rendered(self):
context = Context()
template_to_render = Template(
"{% load dsfr_tags %} {% dsfr_js nonce='random-nonce' %}"
)
rendered_template = template_to_render.render(context)
self.assertInHTML(
f"""
<script type="module" src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.module.min.js" integrity="{ INTEGRITY_JS_MODULE }" nonce="random-nonce"></script>
<script nomodule src="/django-dsfr/static/dsfr/dist/dsfr/dsfr.nomodule.min.js" integrity="{ INTEGRITY_JS_NOMODULE }" nonce="random-nonce"></script>
""",
rendered_template,
)
Expand Down
11 changes: 11 additions & 0 deletions example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"django.forms",
"csp",
"widget_tweaks",
"dsfr",
"example_app",
Expand All @@ -46,6 +47,7 @@

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"csp.middleware.CSPMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
Expand Down Expand Up @@ -135,3 +137,12 @@
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# Content security policies
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-eval'")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_OBJECT_SRC = ("'none'",)
CSP_IMG_SRC = ("'self'", "data:")

CSP_INCLUDE_NONCE_IN = ["script-src"]
2 changes: 1 addition & 1 deletion example_app/templates/example_app/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

{% include "example_app/blocks/footer.html" %}

{% dsfr_js %}
{% dsfr_js nonce=request.csp_nonce %}
{% block extra_js %}{% endblock extra_js %}
</body>

Expand Down
21 changes: 20 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ authors = ["Sylvain Boissel <[email protected]>"]
description = "Integrate the French government Design System into a Django app"
license = "MIT"
name = "django-dsfr"
version = "0.10.1"
version = "0.11.0"
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
Expand Down Expand Up @@ -45,6 +45,7 @@ pytest = "^6.2.4"
twine = "^4.0.1"
djlint = "^1.12.0"
black = "^22.6.0"
django-csp = "^3.7"

[build-system]
build-backend = "poetry.core.masonry.api"
Expand Down

0 comments on commit 39af736

Please sign in to comment.