Skip to content

Commit

Permalink
[CHANGE] Use django-sri for sri hashes
Browse files Browse the repository at this point in the history
  • Loading branch information
ppfeufer committed Jan 27, 2025
1 parent 86975fc commit a54cc7c
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 60 deletions.
71 changes: 40 additions & 31 deletions .make/conf.d/django.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
pot:
@echo "Creating or updating .pot file …"
@django-admin makemessages \
-l en \
--locale en \
--keep-pot \
--ignore 'build/*'
--ignore 'build/*' \
--ignore 'node_modules/*' \
--ignore 'testauth/*' \
--ignore 'runtests.py'
@current_app_version=$$(pip show $(appname) | grep 'Version: ' | awk '{print $$NF}'); \
sed -i "/\"Project-Id-Version: /c\\\"Project-Id-Version: $(appname_verbose) $$current_app_version\\\n\"" $(translation_template); \
sed -i "/\"Report-Msgid-Bugs-To: /c\\\"Report-Msgid-Bugs-To: $(git_repository_issues)\\\n\"" $(translation_template);
Expand All @@ -18,9 +21,12 @@ add_translation:
@echo "Adding a new translation"
@read -p "Enter the language code (e.g. 'en_GB'): " language_code; \
django-admin makemessages \
-l $$language_code \
--locale $$language_code \
--keep-pot \
--ignore 'build/*'; \
--ignore 'build/*' \
--ignore 'node_modules/*' \
--ignore 'testauth/*' \
--ignore 'runtests.py'; \
current_app_version=$$(pip show $(appname) | grep 'Version: ' | awk '{print $$NF}'); \
sed -i "/\"Project-Id-Version: /c\\\"Project-Id-Version: $(appname_verbose) $$current_app_version\\\n\"" $(translation_template); \
sed -i "/\"Report-Msgid-Bugs-To: /c\\\"Report-Msgid-Bugs-To: $(git_repository_issues)\\\n\"" $(translation_template); \
Expand All @@ -34,21 +40,24 @@ add_translation:
translations:
@echo "Creating or updating translation files"
@django-admin makemessages \
-l cs_CZ \
-l de \
-l es \
-l fr_FR \
-l it_IT \
-l ja \
-l ko_KR \
-l nl_NL \
-l pl_PL \
-l ru \
-l sk \
-l uk \
-l zh_Hans \
--locale cs_CZ \
--locale de \
--locale es \
--locale fr_FR \
--locale it_IT \
--locale ja \
--locale ko_KR \
--locale nl_NL \
--locale pl_PL \
--locale ru \
--locale sk \
--locale uk \
--locale zh_Hans \
--keep-pot \
--ignore 'build/*'
--ignore 'build/*' \
--ignore 'node_modules/*' \
--ignore 'testauth/*' \
--ignore 'runtests.py'
@current_app_version=$$(pip show $(appname) | grep 'Version: ' | awk '{print $$NF}'); \
sed -i "/\"Project-Id-Version: /c\\\"Project-Id-Version: $(appname_verbose) $$current_app_version\\\n\"" $(translation_template); \
sed -i "/\"Report-Msgid-Bugs-To: /c\\\"Report-Msgid-Bugs-To: $(git_repository_issues)\\\n\"" $(translation_template); \
Expand All @@ -69,19 +78,19 @@ translations:
compile_translations:
@echo "Compiling translation files"
@django-admin compilemessages \
-l cs_CZ \
-l de \
-l es \
-l fr_FR \
-l it_IT \
-l ja \
-l ko_KR \
-l nl_NL \
-l pl_PL \
-l ru \
-l sk \
-l uk \
-l zh_Hans
--locale cs_CZ \
--locale de \
--locale es \
--locale fr_FR \
--locale it_IT \
--locale ja \
--locale ko_KR \
--locale nl_NL \
--locale pl_PL \
--locale ru \
--locale sk \
--locale uk \
--locale zh_Hans

# Migrate all database changes
.PHONY: migrate
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Section Order:

### Changed

- Use `django-sri` for sri hashes
- Set own user agent for dhooks-lite

## [3.4.3] - 2024-12-14
Expand Down
6 changes: 6 additions & 0 deletions fleetpings/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
Constants
"""

# Standard Library
import os

APP_NAME = "aa-fleetpings"
GITHUB_URL = f"https://github.com/ppfeufer/{APP_NAME}"

DISCORD_WEBHOOK_REGEX = r"https:\/\/discord\.com\/api\/webhooks\/[\d]+\/[a-zA-Z0-9_-]+$"

AA_FLEETPINGS_BASE_DIR = os.path.join(os.path.dirname(__file__))
AA_FLEETPINGS_STATIC_DIR = os.path.join(AA_FLEETPINGS_BASE_DIR, "static", "fleetpings")
Empty file added fleetpings/helper/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions fleetpings/helper/static_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Helper functions for static integrity calculations
"""

# Standard Library
import os
from pathlib import Path

# Third Party
from sri import Algorithm, calculate_integrity

# Alliance Auth
from allianceauth.services.hooks import get_extension_logger

# Alliance Auth (External Libs)
from app_utils.logging import LoggerAddTag

# AA Fleet Pings
from fleetpings import __title__
from fleetpings.constants import AA_FLEETPINGS_STATIC_DIR

logger = LoggerAddTag(my_logger=get_extension_logger(__name__), prefix=__title__)


def calculate_integrity_hash(relative_file_path: str) -> str:
"""
Calculates the integrity hash for a given static file
:param self:
:type self:
:param relative_file_path: The file path relative to the `aa-fleetpings/fleetpings/static/fleetpings` folder
:type relative_file_path: str
:return: The integrity hash
:rtype: str
"""

file_path = os.path.join(AA_FLEETPINGS_STATIC_DIR, relative_file_path)
integrity_hash = calculate_integrity(Path(file_path), Algorithm.SHA512)

return integrity_hash
7 changes: 1 addition & 6 deletions fleetpings/templates/fleetpings/bundles/fleetpings-css.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{% load fleetpings %}

<link
rel="stylesheet"
href="{% fleetpings_static 'fleetpings/css/fleetpings.min.css' %}"
integrity="sha512-+QMniThtoDDcrG5vAFSSKJ5ZyxY1m08u0RPoZnw9lZ0ZAWrc4jlZA+9MAS5pZWIzTEJevJChut4aW99JCSvMow=="
crossorigin="anonymous"
>
{% fleetpings_static 'css/fleetpings.min.css' %}
7 changes: 1 addition & 6 deletions fleetpings/templates/fleetpings/bundles/fleetpings-js.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{% load fleetpings %}

<script
type="module"
src="{% fleetpings_static 'fleetpings/js/fleetpings.min.js' %}"
integrity="sha512-+WukzJKmxTeDpw6vEQ1DDaPlrhpYwbT3UHYCS33W/gVNUCTkXFMH1bGsAYaqj5FKxioVvQKhD+wMhZdw837C0w=="
crossorigin="anonymous"
></script>
{% fleetpings_static 'js/fleetpings.min.js' 'module' %}
72 changes: 62 additions & 10 deletions fleetpings/templatetags/fleetpings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@
Template tags
"""

# Standard Library
import os

# Django
from django.conf import settings
from django.template.defaulttags import register
from django.templatetags.static import static
from django.utils.safestring import mark_safe

# Alliance Auth
from allianceauth.services.hooks import get_extension_logger

# Alliance Auth (External Libs)
from app_utils.logging import LoggerAddTag
from app_utils.urls import reverse_absolute

# AA Fleet Pings
from fleetpings import __version__
from fleetpings import __title__, __version__
from fleetpings.helper.static_files import calculate_integrity_hash

logger = LoggerAddTag(my_logger=get_extension_logger(__name__), prefix=__title__)


@register.simple_tag
Expand All @@ -30,17 +42,57 @@ def fleetpings_reverse_url(view: str, *args) -> str:


@register.simple_tag
def fleetpings_static(path: str) -> str:
def fleetpings_static(relative_file_path: str, script_type: str = None) -> str | None:
"""
Versioned Static URL
Versioned static URL
:param path:
:type path:
:return:
:rtype:
:param relative_file_path: The file path relative to the `aa-fleetpings/fleetpings/static/fleetpings` folder
:type relative_file_path: str
:param script_type: The script type
:type script_type: str
:return: Versioned static URL
:rtype: str
"""

static_url = static(path=path)
versioned_url = static_url + "?v=" + __version__
logger.debug(f"Getting versioned static URL for: {relative_file_path}")

file_type = os.path.splitext(relative_file_path)[1][1:]

logger.debug(f"File extension: {file_type}")

# Only support CSS and JS files
if file_type not in ["css", "js"]:
raise ValueError(f"Unsupported file type: {file_type}")

static_file_path = os.path.join("fleetpings", relative_file_path)
static_url = static(static_file_path)

# Integrity hash calculation only for non-debug mode
sri_string = (
f' integrity="{calculate_integrity_hash(relative_file_path)}" crossorigin="anonymous"'
if not settings.DEBUG
else ""
)

# Versioned URL for CSS and JS files
# Add version query parameter to break browser caches when changing the app version
# Do not add version query parameter for libs as they are already versioned through their file path
versioned_url = (
static_url
if relative_file_path.startswith("libs/")
else static_url + "?v=" + __version__
)

# Return the versioned URL with integrity hash for CSS
if file_type == "css":
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}"{sri_string}>')

# Return the versioned URL with integrity hash for JS files
if file_type == "js":
js_type = f' type="{script_type}"' if script_type else ""

return mark_safe(
f'<script{js_type} src="{versioned_url}"{sri_string}></script>'
)

return versioned_url
return None
60 changes: 53 additions & 7 deletions fleetpings/tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@

# Django
from django.template import Context, Template
from django.test import TestCase
from django.test import TestCase, override_settings

# Alliance Auth (External Libs)
from app_utils.urls import site_absolute_url

# AA Fleet Pings
from fleetpings import __version__
from fleetpings.helper.static_files import calculate_integrity_hash


class TestVersionedStatic(TestCase):
"""
Test fleetpings_versioned_static template tag
Test the `fleetpings_static` template tag
"""

@override_settings(DEBUG=False)
def test_versioned_static(self):
"""
Test should return a versioned static URL
Test should return the versioned static
:return:
:rtype:
Expand All @@ -30,16 +32,60 @@ def test_versioned_static(self):
template_to_render = Template(
template_string=(
"{% load fleetpings %}"
"{% fleetpings_static 'fleetpings/css/fleetpings.min.css' %}"
"{% fleetpings_static 'css/fleetpings.min.css' %}"
"{% fleetpings_static 'js/fleetpings.min.js' 'module' %}"
)
)

rendered_template = template_to_render.render(context=context)

self.assertInHTML(
needle=f'/static/fleetpings/css/fleetpings.min.css?v={context["version"]}',
haystack=rendered_template,
expected_static_css_src = (
f'/static/fleetpings/css/fleetpings.min.css?v={context["version"]}'
)
expected_static_css_src_integrity = calculate_integrity_hash(
"css/fleetpings.min.css"
)
expected_static_js_src = (
f'/static/fleetpings/js/fleetpings.min.js?v={context["version"]}'
)
expected_static_js_src_integrity = calculate_integrity_hash(
"js/fleetpings.min.js"
)

self.assertIn(member=expected_static_css_src, container=rendered_template)
self.assertIn(
member=expected_static_css_src_integrity, container=rendered_template
)
self.assertIn(member=expected_static_js_src, container=rendered_template)
self.assertIn(
member=expected_static_js_src_integrity, container=rendered_template
)

@override_settings(DEBUG=True)
def test_versioned_static_with_debug_enabled(self) -> None:
"""
Test versioned static template tag with DEBUG enabled
:return:
:rtype:
"""

context = Context({"version": __version__})
template_to_render = Template(
template_string=(
"{% load fleetpings %}"
"{% fleetpings_static 'css/fleetpings.min.css' %}"
)
)

rendered_template = template_to_render.render(context=context)

expected_static_css_src = (
f'/static/fleetpings/css/fleetpings.min.css?v={context["version"]}'
)

self.assertIn(member=expected_static_css_src, container=rendered_template)
self.assertNotIn(member="integrity=", container=rendered_template)


class TestReverseUrl(TestCase):
Expand Down

0 comments on commit a54cc7c

Please sign in to comment.