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

Allow to integrate subresource-integrity attributes to javascript and… #815

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
19 changes: 19 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ A dictionary passed to compiler's ``compile_file`` method as kwargs. None of def

Defaults to ``{}``.

``crossorigin``
...............

**Optional**

Indicate if you want to add to the group this attribute that provides support for CORS, defining how the element handles cross-origin requests, thereby enabling the configuration of the CORS requests for the element's fetched data. .

Missing by default (the attribute is not added), the only valid values currently are ``anonymous`` and ``use-credentials``.

``integrity``
.............

**Optional**

Indicate if you want to add the sub-resource integrity (SRI) attribute to the group.
This attribute contains inline metadata that a user agent can use to verify that a fetched resource has been delivered free of unexpected manipulation

Missing by default, and only valid values are ``"sha256"``, ``"sha384"`` and ``"sha512"``.


Other settings
--------------
Expand Down
9 changes: 8 additions & 1 deletion pipeline/jinja2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ def render_css(self, package, path):
template_name = package.template_name or "pipeline/css.jinja"
context = package.extra_context
context.update(
{"type": guess_type(path, "text/css"), "url": staticfiles_storage.url(path)}
{
"type": guess_type(path, "text/css"),
"url": staticfiles_storage.url(path),
"crossorigin": package.config.get("crossorigin"),
"integrity": package.get_sri(path),
}
)
template = self.environment.get_template(template_name)
return template.render(context)
Expand All @@ -66,6 +71,8 @@ def render_js(self, package, path):
{
"type": guess_type(path, "text/javascript"),
"url": staticfiles_storage.url(path),
"crossorigin": package.config.get("crossorigin"),
"integrity": package.get_sri(path),
}
)
template = self.environment.get_template(template_name)
Expand Down
2 changes: 1 addition & 1 deletion pipeline/jinja2/pipeline/css.jinja
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %}{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %} />
2 changes: 1 addition & 1 deletion pipeline/jinja2/pipeline/js.jinja
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %}></script>
18 changes: 18 additions & 0 deletions pipeline/packager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import base64
import hashlib
from functools import lru_cache

from django.contrib.staticfiles.finders import find, get_finders
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.files.base import ContentFile
Expand Down Expand Up @@ -61,6 +65,20 @@
def compiler_options(self):
return self.config.get("compiler_options", {})

@lru_cache
def get_sri(self, path):
method = self.config.get("integrity")
if method not in {"sha256", "sha384", "sha512"}:
return None
if staticfiles_storage.exists(path):
with staticfiles_storage.open(path) as fd:
h = getattr(hashlib, method)()
for data in iter(lambda: fd.read(16384), b""):
h.update(data)
digest = base64.b64encode(h.digest()).decode()
return f"{method}-{digest}"
return None

Check warning on line 80 in pipeline/packager.py

View check run for this annotation

Codecov / codecov/patch

pipeline/packager.py#L80

Added line #L80 was not covered by tests


class Packager:
def __init__(
Expand Down
2 changes: 1 addition & 1 deletion pipeline/templates/pipeline/css.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<link href="{{ url }}" rel="stylesheet" type="{{ type }}" media="{{ media|default:"all" }}"{% if title %} title="{{ title }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
<link href="{{ url }}" rel="stylesheet" type="{{ type }}" media="{{ media|default:"all" }}"{% if title %} title="{{ title }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %}{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %} />
2 changes: 1 addition & 1 deletion pipeline/templates/pipeline/css.jinja
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />
<link href="{{ url }}" rel="stylesheet" type="{{ type }}"{% if media %} media="{{ media }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %}{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %} />
2 changes: 1 addition & 1 deletion pipeline/templates/pipeline/js.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %}></script>
2 changes: 1 addition & 1 deletion pipeline/templates/pipeline/js.jinja
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"></script>
<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type="{{ type }}" src="{{ url }}" charset="utf-8"{% if integrity %} integrity="{{ integrity }}"{% endif %}{% if crossorigin %} crossorigin="{{ crossorigin }}"{% endif %}></script>
4 changes: 4 additions & 0 deletions pipeline/templatetags/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ def render_css(self, package, path):
{
"type": guess_type(path, "text/css"),
"url": mark_safe(staticfiles_storage.url(path)),
"crossorigin": package.config.get("crossorigin"),
"integrity": package.get_sri(path),
}
)
return render_to_string(template_name, context)
Expand Down Expand Up @@ -188,6 +190,8 @@ def render_js(self, package, path):
{
"type": guess_type(path, "text/javascript"),
"url": mark_safe(staticfiles_storage.url(path)),
"crossorigin": package.config.get("crossorigin"),
"integrity": package.get_sri(path),
}
)
return render_to_string(template_name, context)
Expand Down
92 changes: 91 additions & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
import shutil

from django.utils import version


def local_path(path):
return os.path.join(os.path.dirname(__file__), path)
Expand Down Expand Up @@ -42,7 +44,19 @@ def local_path(path):

MEDIA_ROOT = local_path("media")

STATICFILES_STORAGE = "pipeline.storage.PipelineStorage"
django_version = version.get_complete_version()
if django_version >= (4, 2):

STORAGES = {
"default": {
"BACKEND": "pipeline.storage.PipelineStorage",
},
"staticfiles": {
"BACKEND": "pipeline.storage.PipelineStorage",
},
}
else:
STATICFILES_STORAGE = "pipeline.storage.PipelineStorage"
STATIC_ROOT = local_path("static/")
STATIC_URL = "/static/"
STATICFILES_DIRS = (("pipeline", local_path("assets/")),)
Expand Down Expand Up @@ -89,6 +103,42 @@ def local_path(path):
"title": "Default Style",
},
},
"screen_crossorigin": {
"source_filenames": (
"pipeline/css/first.css",
"pipeline/css/second.css",
"pipeline/css/urls.css",
),
"output_filename": "screen_crossorigin.css",
"crossorigin": "anonymous",
},
"screen_sri_sha256": {
"source_filenames": (
"pipeline/css/first.css",
"pipeline/css/second.css",
"pipeline/css/urls.css",
),
"output_filename": "screen_sri_sha256.css",
"integrity": "sha256",
},
"screen_sri_sha384": {
"source_filenames": (
"pipeline/css/first.css",
"pipeline/css/second.css",
"pipeline/css/urls.css",
),
"output_filename": "screen_sri_sha384.css",
"integrity": "sha384",
},
"screen_sri_sha512": {
"source_filenames": (
"pipeline/css/first.css",
"pipeline/css/second.css",
"pipeline/css/urls.css",
),
"output_filename": "screen_sri_sha512.css",
"integrity": "sha512",
},
},
"JAVASCRIPT": {
"scripts": {
Expand Down Expand Up @@ -137,6 +187,46 @@ def local_path(path):
"defer": True,
},
},
"scripts_crossorigin": {
"source_filenames": (
"pipeline/js/first.js",
"pipeline/js/second.js",
"pipeline/js/application.js",
"pipeline/templates/**/*.jst",
),
"output_filename": "scripts_crossorigin.js",
"crossorigin": "anonymous",
},
"scripts_sri_sha256": {
"source_filenames": (
"pipeline/js/first.js",
"pipeline/js/second.js",
"pipeline/js/application.js",
"pipeline/templates/**/*.jst",
),
"output_filename": "scripts_sha256.js",
"integrity": "sha256",
},
"scripts_sri_sha384": {
"source_filenames": (
"pipeline/js/first.js",
"pipeline/js/second.js",
"pipeline/js/application.js",
"pipeline/templates/**/*.jst",
),
"output_filename": "scripts_sha384.js",
"integrity": "sha384",
},
"scripts_sri_sha512": {
"source_filenames": (
"pipeline/js/first.js",
"pipeline/js/second.js",
"pipeline/js/application.js",
"pipeline/templates/**/*.jst",
),
"output_filename": "scripts_sha512.js",
"integrity": "sha512",
},
},
}

Expand Down
Loading
Loading