Skip to content

Commit

Permalink
[CHANGE] Use django-sri provided by AA to get the integrity hash
Browse files Browse the repository at this point in the history
  • Loading branch information
ppfeufer committed Jan 10, 2025
1 parent fc5d817 commit a2910ed
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 102 deletions.
28 changes: 0 additions & 28 deletions timezones/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

# AA Time Zones
from timezones import __title__
from timezones.helper.static_files import calculate_integrity_hash

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

Expand All @@ -26,30 +25,3 @@ def allianceauth_discordbot_active():
"""

return apps.is_installed("aadiscordbot")


class IntegrityHash:
"""
Integrity hashes for static files
"""

logger.debug("Calculating integrity hashes for static files")

# CSS
CSS = {"timezones.min.css": calculate_integrity_hash("css/timezones.min.css")}

# JavaScript
JS = {"timezones.min.js": calculate_integrity_hash("js/timezones.min.js")}

# External Libraries
EXTERNAL_LIBS = {
"jquery.timeago.min.js": calculate_integrity_hash(
"libs/jquery-timeago/1.6.7/jquery.timeago.min.js"
),
"moment-timezone-with-data-1970-2030.min.js": calculate_integrity_hash(
"libs/moment-timezone/0.5.36/moment-timezone-with-data-1970-2030.min.js"
),
"weather-icons.min.css": calculate_integrity_hash(
"libs/weather-icons/2.0.10/css/weather-icons.min.css"
),
}
18 changes: 7 additions & 11 deletions timezones/helper/static_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""

# Standard Library
import base64
import hashlib
import os
from pathlib import Path

# Third Party
from sri import Algorithm, calculate_integrity

# Alliance Auth
from allianceauth.services.hooks import get_extension_logger
Expand All @@ -24,21 +26,15 @@ 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-timezones/timezones/static/timezones` folder
:type relative_file_path: str
:return: The integrity hash
:rtype: str
"""

file_path = os.path.join(AA_TIMEZONES_STATIC_DIR, relative_file_path)

logger.debug(f"Calculating integrity hash for file: {file_path}")

with open(file=file_path, encoding="utf-8") as static_file:
file_hash = hashlib.sha512()

content = static_file.read()
file_hash.update(content.encode("utf-8"))
integrity_hash = f"sha512-{base64.b64encode(file_hash.digest()).decode()}"
integrity_hash = calculate_integrity(Path(file_path), Algorithm.SHA512)

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

<link
rel="stylesheet"
href="{% timezones_static 'timezones/css/timezones.min.css' %}"
integrity="{% timezones_static_integrity_hash 'css' 'timezones.min.css' %}"
crossorigin="anonymous"
>
{% timezones_static 'css/timezones.min.css' %}
6 changes: 1 addition & 5 deletions timezones/templates/timezones/bundles/aa-timezones-js.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{% load timezones %}

<script
src="{% timezones_static 'timezones/js/timezones.min.js' %}"
integrity="{% timezones_static_integrity_hash 'js' 'timezones.min.js' %}"
crossorigin="anonymous"
></script>
{% timezones_static 'js/timezones.min.js' %}
7 changes: 1 addition & 6 deletions timezones/templates/timezones/bundles/jquery-timeago-js.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{% load static %}
{% load timezones %}

<script
src="{% static 'timezones/libs/jquery-timeago/1.6.7/jquery.timeago.min.js' %}"
integrity="{% timezones_static_integrity_hash 'lib' 'jquery.timeago.min.js' %}"
crossorigin="anonymous"
></script>
{% timezones_static 'libs/jquery-timeago/1.6.7/jquery.timeago.min.js' %}
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
{% load static %}
{% load timezones %}

<script
src="{% static 'timezones/libs/moment-timezone/0.5.36/moment-timezone-with-data-1970-2030.min.js' %}"
integrity="{% timezones_static_integrity_hash 'lib' 'moment-timezone-with-data-1970-2030.min.js' %}"
crossorigin="anonymous"
></script>
{% timezones_static 'libs/moment-timezone/0.5.36/moment-timezone-with-data-1970-2030.min.js' %}
8 changes: 1 addition & 7 deletions timezones/templates/timezones/bundles/weather-icons-css.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
{% load static %}
{% load timezones %}

<link
rel="stylesheet"
href="{% static 'timezones/libs/weather-icons/2.0.10/css/weather-icons.min.css' %}"
integrity="{% timezones_static_integrity_hash 'lib' 'weather-icons.min.css' %}"
crossorigin="anonymous"
>
{% timezones_static 'libs/weather-icons/2.0.10/css/weather-icons.min.css' %}
75 changes: 47 additions & 28 deletions timezones/templatetags/timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,73 @@
Versioned static URLs to break browser caches when changing the app version
"""

# Standard Library
import os

# Django
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

# AA Time Zones
from timezones import __version__
from timezones.app_settings import IntegrityHash
from timezones import __title__, __version__
from timezones.helper.static_files import calculate_integrity_hash

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


@register.simple_tag
def timezones_static(path: str) -> str:
def timezones_static(relative_file_path: str) -> str | None:
"""
Versioned static URL
:param path: Path to the static file relative to the static folder
:type path: str
:param relative_file_path: The file path relative to the `aa-timezones/timezones/static/timezones` folder
:type relative_file_path: str
:return: Versioned static URL
:rtype: str
"""

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

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

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

@register.simple_tag
def timezones_static_integrity_hash(
static_file_type: str, relative_file_path: str
) -> str:
"""
Returns the integrity hash for a file
# Only support CSS and JS files
if file_type not in ["css", "js"]:
raise ValueError(f"Unsupported file type: {file_type}")

:param static_file_type: The type of static file
:type static_file_type: str
:param relative_file_path: The file path relative to the `aa-timezones/timezones/static/timezones` folder
:type relative_file_path: str
:return: Integrity hash
:rtype: str
"""
integrity_hash = calculate_integrity_hash(relative_file_path)
static_file_path = os.path.join("timezones", relative_file_path)
static_url = static(static_file_path)

# Versioned URL for CSS and JS files
# Add version query parameter to break browser caches
# 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__
)

if static_file_type == "css":
return IntegrityHash.CSS.get(relative_file_path)
# Return the versioned URL with integrity hash for CSS
if file_type == "css":
logger.debug(f"Integrity hash for {relative_file_path}: {integrity_hash}")

if static_file_type == "js":
return IntegrityHash.JS.get(relative_file_path)
return mark_safe(
f'<link rel="stylesheet" href="{versioned_url}" integrity="{integrity_hash}" crossorigin="anonymous">'
)

if static_file_type == "lib":
return IntegrityHash.EXTERNAL_LIBS.get(relative_file_path)
# Return the versioned URL with integrity hash for JS files
if file_type == "js":
return mark_safe(
f'<script src="{versioned_url}" integrity="{integrity_hash}" crossorigin="anonymous"></script>'
)

raise ValueError(f"Unsupported static file type: {static_file_type}")
return None
9 changes: 4 additions & 5 deletions timezones/tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ def test_versioned_static(self):

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

rendered_template = template_to_render.render(context)

self.assertInHTML(
needle=f'/static/timezones/css/timezones.min.css?v={context["version"]}',
haystack=rendered_template,
self.assertIn(
member=f'/static/timezones/css/timezones.min.css?v={context["version"]}',
container=rendered_template,
)

0 comments on commit a2910ed

Please sign in to comment.