Skip to content

Commit

Permalink
Make django-leaflet compatible with CSP (#371)
Browse files Browse the repository at this point in the history
* Set style css in script

* leaflet-container class to external css

* Set i18n variables into external js

* Further remove inline scripts

* Move custom css into leaflet.extras.css

* Don't modify leaflet.css

* Optionally add nonce to inline style and script in widget

* Maybe fix the whole thing?

* Revert mistaken deletions

* Add nonce to leaflet_map tag

* Quote the nonce

* Fix remaining inlines

---------

Co-authored-by: Rodolfo Valentín Becerra García <[email protected]>
Co-authored-by: Rodolfo Becerra <[email protected]>
  • Loading branch information
3 people authored May 13, 2024
1 parent fce3e06 commit 115edc4
Show file tree
Hide file tree
Showing 15 changed files with 162 additions and 60 deletions.
3 changes: 0 additions & 3 deletions example/mushrooms/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
<head>
{% leaflet_js %}
{% leaflet_css %}
<style>
.leaflet-container { height: 100%; }
</style>
<script>
var dataurl = '{% url "data" %}';

Expand Down
4 changes: 4 additions & 0 deletions leaflet/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ def formfield_for_dbfield(self, db_field, request=None, **kwargs):
widget = kwargs['widget']

kwargs['widget'] = self._get_map_widget(db_field, widget)

if request is not None:
kwargs['widget'].csp_nonce = getattr(request, "csp_nonce", None)

return db_field.formfield(**kwargs)
else:
return super().formfield_for_dbfield(db_field, request, **kwargs)
Expand Down
3 changes: 2 additions & 1 deletion leaflet/forms/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class GeometryField(BaseGeometryField):
widget = LeafletWidget
geom_type = 'GEOMETRY'

def __init__(self, *args, **kwargs):
def __init__(self, *args, csp_nonce=None, **kwargs):
super().__init__(*args, **kwargs)
self.widget.geom_type = self.geom_type
self.widget.csp_nonce = csp_nonce


class GeometryCollectionField(GeometryField):
Expand Down
6 changes: 6 additions & 0 deletions leaflet/forms/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.contrib.gis.forms.widgets import BaseGeometryWidget
from django.core import validators
from django.template.defaultfilters import slugify
from django.templatetags.static import static

from leaflet import app_settings, PLUGINS, PLUGIN_FORMS

Expand All @@ -15,6 +16,7 @@ class LeafletWidget(BaseGeometryWidget):
supports_3d = False
include_media = False
settings_overrides = {}
csp_nonce = None

@property
def media(self):
Expand Down Expand Up @@ -87,4 +89,8 @@ def get_context(self, name, value, attrs):
value = None if value in validators.EMPTY_VALUES else value
context = super().get_context(name, value, attrs)
context.update(self.get_attrs(name, attrs))
context["csp_nonce"] = self.csp_nonce
context["FORCE_IMAGE_PATH"] = app_settings.get('FORCE_IMAGE_PATH')
context["reset_view_icon"] = static("leaflet/images/reset-view.png")
print(context)
return context
35 changes: 35 additions & 0 deletions leaflet/static/leaflet/draw/leaflet.draw.i18n.js

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

7 changes: 7 additions & 0 deletions leaflet/static/leaflet/leaflet.extras.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.leaflet-container-default {
min-height: 300px;
}

.leaflet-container {
height: 100%;
}
6 changes: 6 additions & 0 deletions leaflet/static/leaflet/leaflet.extras.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ L.Control.ResetView = L.Control.extend({
};

L.Util.setOptions(this, options);
L.Control.ResetView.TITLE = JSON.parse(document.getElementById("Control-ResetView-TITLE").textContent);
L.Control.ResetView.ICON = JSON.parse(document.getElementById("Control-ResetView-ICON").textContent);
},

onAdd: function (map) {
Expand All @@ -28,6 +30,7 @@ L.Control.ResetView = L.Control.extend({
var link = L.DomUtil.create('a', 'leaflet-control-zoom-out leaflet-bar-part', container);
link.href = '#';
link.title = L.Control.ResetView.TITLE;

link.style.backgroundImage = L.Control.ResetView.ICON;

L.DomEvent.addListener(link, 'click', L.DomEvent.stopPropagation)
Expand Down Expand Up @@ -254,3 +257,6 @@ L.Map.djangoMap = function (id, options) {
}
}
};


L.Icon.Default.imagePath = JSON.parse(document.getElementById("force-img-path").textContent);
33 changes: 0 additions & 33 deletions leaflet/templates/leaflet/_leaflet_draw_i18n.js

This file was deleted.

3 changes: 2 additions & 1 deletion leaflet/templates/leaflet/_leaflet_map.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{% load i18n %}
{% load static %}
{% if creatediv %}<div id="{{ name }}" class="leaflet-container-default"></div>{% endif %}
<script>

<script {% if csp_nonce %}nonce="{{ csp_nonce }}"{% endif %}>
(function () {

function loadmap() {
Expand Down
11 changes: 3 additions & 8 deletions leaflet/templates/leaflet/admin/widget.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
{% extends "leaflet/widget.html" %}
{% load i18n %}
{% load static %}

{% block vars %}
{{ block.super }}

{% include "leaflet/_leaflet_draw_i18n.js" %}
L.Control.ResetView.TITLE = "{% trans "Reset view" %}";
L.Control.ResetView.ICON = "url({% static "leaflet/images/reset-view.png" %})";
{% endblock vars %}
{% load leaflet_tags %}

{% block map %}
<div id="{{ id_css }}-div-map">
{{ block.super }}
</div>

{% leaflet_draw_i18n %}
{% endblock map %}
2 changes: 1 addition & 1 deletion leaflet/templates/leaflet/css.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load static %}
<link rel="stylesheet" href="{% static "leaflet/leaflet.css" %}" />
<style>.leaflet-container-default {min-height: 300px;}</style>
<link rel="stylesheet" href="{% static "leaflet/leaflet.extras.css" %}" />
{% if PLUGINS_CSS %}
{% for css in PLUGINS_CSS %}
<link rel="stylesheet" href="{{ css }}" />
Expand Down
15 changes: 7 additions & 8 deletions leaflet/templates/leaflet/js.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% load i18n %}
{% load static %}
{% load leaflet_tags %}
{% if DEBUG %}
<script src="{% static "leaflet/leaflet-src.js" %}"></script>
{% else %}
Expand All @@ -15,12 +16,10 @@
<script src="{{ js }}"></script>
{% endfor %}
{% endif %}

{{ FORCE_IMAGE_PATH|json_script:"force-img-path" }}
{{ reset_view_icon|json_script:"Control-ResetView-ICON" }}
<script src="{% static "leaflet/leaflet.extras.js" %}"></script>
<script>
{% if with_forms %}{% include "leaflet/_leaflet_draw_i18n.js" %}{% endif %}
L.Control.ResetView.TITLE = "{% trans "Reset view" %}";
L.Control.ResetView.ICON = "url({% static "leaflet/images/reset-view.png" %})";
{% if FORCE_IMAGE_PATH %}
L.Icon.Default.imagePath = "{% static "leaflet/images/" %}";
{% endif %}
</script>

{{ with_forms|json_script:"with-forms" }}
{% leaflet_draw_i18n %}
37 changes: 37 additions & 0 deletions leaflet/templates/leaflet/leaflet_draw_i18n.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% load static %}
{{ Control_ResetView_TITLE|json_script:"Control-ResetView-TITLE" }}
{% if with_forms %}
{{ draw_toolbar_actions_title|json_script:"draw-toolbar-actions-title" }}
{{ draw_toolbar_actions_text|json_script:"draw-toolbar-actions-text" }}
{{ draw_toolbar_undo_title|json_script:"draw-toolbar-undo-title" }}
{{ draw_toolbar_undo_text|json_script:"draw-toolbar-undo-text" }}
{{ draw_toolbar_buttons_polyline|json_script:"draw-toolbar-buttons-polyline" }}
{{ draw_toolbar_buttons_polygon|json_script:"draw-toolbar-buttons-polygon" }}
{{ draw_toolbar_buttons_rectangle|json_script:"draw-toolbar-buttons-rectangle" }}
{{ draw_toolbar_buttons_circle|json_script:"draw-toolbar-buttons-circle" }}
{{ draw_toolbar_buttons_marker|json_script:"draw-toolbar-buttons-marker" }}
{{ draw_handlers_circle_tooltip_start|json_script:"draw-handlers-circle-tooltip-start" }}
{{ draw_handlers_marker_tooltip_start|json_script:"draw-handlers-marker-tooltip-start" }}
{{ draw_handlers_polygon_tooltip_start|json_script:"draw-handlers-polygon-tooltip-start" }}
{{ draw_handlers_polygon_tooltip_cont|json_script:"draw-handlers-polygon-tooltip-cont" }}
{{ draw_handlers_polygon_tooltip_end|json_script:"draw-handlers-polygon-tooltip-end" }}
{{ draw_handlers_polyline_error|json_script:"draw-handlers-polyline-error" }}
{{ draw_handlers_polyline_tooltip_start|json_script:"draw-handlers-polyline-tooltip-start" }}
{{ draw_handlers_polyline_tooltip_cont|json_script:"draw-handlers-polyline-tooltip-cont" }}
{{ draw_handlers_polyline_tooltip_end|json_script:"draw-handlers-polyline-tooltip-end" }}
{{ draw_handlers_rectangle_tooltip_start|json_script:"draw-handlers-rectangle-tooltip-start" }}
{{ draw_handlers_simpleshape_tooltip_end|json_script:"draw-handlers-simpleshape-tooltip-end" }}

{{ edit_toolbar_actions_save_title|json_script:"edit-toolbar-actions-save-title" }}
{{ edit_toolbar_actions_save_text|json_script:"edit-toolbar-actions-save-text" }}
{{ edit_toolbar_actions_cancel_title|json_script:"edit-toolbar-actions-cancel-title" }}
{{ edit_toolbar_actions_cancel_text|json_script:"edit-toolbar-actions-cancel-text" }}
{{ edit_toolbar_buttons_edit|json_script:"edit-toolbar-buttons-edit" }}
{{ edit_toolbar_buttons_editDisabled|json_script:"edit-toolbar-buttons-editDisabled" }}
{{ edit_toolbar_buttons_remove|json_script:"edit-toolbar-buttons-remove" }}
{{ edit_toolbar_buttons_removeDisabled|json_script:"edit-toolbar-buttons-removeDisabled" }}
{{ edit_handlers_edit_tooltip_text|json_script:"edit-handlers-edit-tooltip-text" }}
{{ edit_handlers_edit_tooltip_subtext|json_script:"edit-handlers-edit-tooltip-subtext" }}
{{ edit_handlers_remove_tooltip_text|json_script:"edit-handlers-remove-tooltip-text" }}
{% endif %}
<script src="{% static 'leaflet/draw/leaflet.draw.i18n.js' %}"></script>
10 changes: 7 additions & 3 deletions leaflet/templates/leaflet/widget.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{% load leaflet_tags l10n %}
{% load static %}

<style>{% block map_css %}
{{ FORCE_IMAGE_PATH|json_script:"force-img-path" }}
{{ reset_view_icon|json_script:"Control-ResetView-ICON" }}

<style {% if csp_nonce %}nonce="{{ csp_nonce }}"{% endif %}>
{% block map_css %}
{% if map_width and map_height %}#{{ id_map }} { width: {{ map_width|unlocalize }}; height: {{ map_height|unlocalize }}; }{% endif %}
{% if not display_raw %}#{{ id_css }} { display: none; }{% endif %}
{% endblock map_css %}
</style>

<script>
<script {% if csp_nonce %}nonce="{{ csp_nonce }}"{% endif %}>
{% block vars %}var {{ module }} = {};
{{ module }}.fieldid = '{{ id_css }}';
{{ module }}.modifiable = {{ modifiable|yesno:"true,false" }};
Expand Down Expand Up @@ -38,7 +42,7 @@

{% if not target_map %}
{% block map %}
{% leaflet_map id_map callback=id_map_callback loadevent=loadevent settings_overrides=settings_overrides %}
{% leaflet_map id_map callback=id_map_callback loadevent=loadevent settings_overrides=settings_overrides csp_nonce=csp_nonce %}
{% endblock map %}
{% endif %}

Expand Down
47 changes: 45 additions & 2 deletions leaflet/templatetags/leaflet_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.encoding import force_str
from django.utils.translation import gettext as _
from django.templatetags.static import static

from leaflet import (app_settings, SPATIAL_EXTENT, SRID, PLUGINS, PLUGINS_DEFAULT,
PLUGIN_ALL, PLUGIN_FORMS)
Expand Down Expand Up @@ -49,14 +51,15 @@ def leaflet_js(plugins=None):
"SRID": str(SRID) if SRID else None,
"PLUGINS_JS": _get_all_resources_for_plugins(plugin_names, 'js'),
"with_forms": with_forms,
"FORCE_IMAGE_PATH": FORCE_IMAGE_PATH
"FORCE_IMAGE_PATH": FORCE_IMAGE_PATH,
"reset_view_icon": static("leaflet/images/reset-view.png")
}


@register.inclusion_tag('leaflet/_leaflet_map.html')
def leaflet_map(name, callback=None, fitextent=True, creatediv=True,
loadevent=app_settings.get('LOADEVENT'),
settings_overrides={}):
settings_overrides={}, csp_nonce=None):
"""
:param name:
Expand Down Expand Up @@ -109,6 +112,46 @@ def leaflet_map(name, callback=None, fitextent=True, creatediv=True,
'djoptions': json.dumps(djoptions, cls=DjangoJSONEncoder),
# settings
'NO_GLOBALS': instance_app_settings.get('NO_GLOBALS'),
'csp_nonce': csp_nonce
}


@register.inclusion_tag('leaflet/leaflet_draw_i18n.html')
def leaflet_draw_i18n():
return {
"Control_ResetView_TITLE": _("Reset view"),
# with_forms
"draw_toolbar_actions_title": _("Cancel drawing"),
"draw_toolbar_actions_text": _("Cancel"),
"draw_toolbar_undo_title": _("Delete last point drawn"),
"draw_toolbar_undo_text": _("Delete last point"),
"draw_toolbar_buttons_polyline": _("Draw a polyline"),
"draw_toolbar_buttons_polygon": _("Draw a polygon"),
"draw_toolbar_buttons_rectangle": _("Draw a rectangle"),
"draw_toolbar_buttons_circle": _("Draw a circle"),
"draw_toolbar_buttons_marker": _("Draw a marker"),
"draw_handlers_circle_tooltip_start": _("Click and drag to draw circle."),
"draw_handlers_marker_tooltip_start": _("Click map to place marker."),
"draw_handlers_polygon_tooltip_start": _("Click to start drawing shape."),
"draw_handlers_polygon_tooltip_cont": _("Click to continue drawing shape."),
"draw_handlers_polygon_tooltip_end": _("Click first point to close this shape."),
"draw_handlers_polyline_error": _("<strong>Error:</strong> shape edges cannot cross!"),
"draw_handlers_polyline_tooltip_start": _("Click to start drawing line."),
"draw_handlers_polyline_tooltip_cont": _("Click to continue drawing line."),
"draw_handlers_polyline_tooltip_end": _("Click last point to finish line."),
"draw_handlers_rectangle_tooltip_start": _("Click and drag to draw rectangle."),
"draw_handlers_simpleshape_tooltip_end": _("Release mouse to finish drawing."),
"edit_toolbar_actions_save_title": _("Save changes."),
"edit_toolbar_actions_save_text": _("Save"),
"edit_toolbar_actions_cancel_title": _("Cancel editing, discards all changes."),
"edit_toolbar_actions_cancel_text": _("Cancel"),
"edit_toolbar_buttons_edit": _("Edit layers"),
"edit_toolbar_buttons_editDisabled": _("No layers to edit."),
"edit_toolbar_buttons_remove": _("Delete layers"),
"edit_toolbar_buttons_removeDisabled": _("No layers to delete."),
"edit_handlers_edit_tooltip_text": _("Drag handles, or marker to edit feature."),
"edit_handlers_edit_tooltip_subtext": _("Click cancel to undo changes."),
"edit_handlers_remove_tooltip_text": _("Click on a feature to remove"),
}


Expand Down

0 comments on commit 115edc4

Please sign in to comment.