Skip to content

Commit

Permalink
[Fixes #11862] Sanitize HTML data (#11859)
Browse files Browse the repository at this point in the history
* Sanitize HTML data

* pin nh3, add to setup.cfg

* Update setup.cfg remove blank lines
  • Loading branch information
t-book committed Feb 13, 2024
1 parent 0d4f9bc commit 9d6a246
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 6 deletions.
36 changes: 36 additions & 0 deletions geonode/base/templatetags/sanitize_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django import template
from django.conf import settings
import nh3
from django.utils.encoding import force_str

register = template.Library()


@register.filter(name="sanitize_html")
def sanitize_html(value):
"""
This filter can be used in Django templates to ensure that HTML content is safe to render. It leverages the
nh3 library (https://github.com/messense/nh3) to remove dangerous tags and JavaScript patterns.
The allowed HTML tags and attributes can be
configured via the 'NH3_DEFAULT_CONFIG' setting in Django's settings.py. If this configuration is not set, a default
set of safe tags will be used.
Usage in templates: {{ some_variable|sanitize_html|safe }}
:param value: The HTML content to be sanitized within the template.
:return: Sanitized HTML content safe for rendering in templates.
"""

# Tests: Ensure the value is converted to a string if it's a lazy object
value = force_str(value)

nh3_config = getattr(
settings,
"NH3_DEFAULT_CONFIG",
{
"tags": {"b", "a", "img", "p", "ul", "li", "strong", "em", "span"},
},
)

return nh3.clean(value, **nh3_config)
9 changes: 9 additions & 0 deletions geonode/base/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from geonode.documents.models import Document
from geonode.tests.base import GeoNodeBaseTestSupport
from geonode.base.templatetags.base_tags import display_change_perms_button
from geonode.base.templatetags.sanitize_html import sanitize_html
from geonode.base.utils import OwnerRightsRequestViewUtils
from geonode.base.models import (
HierarchicalKeyword,
Expand Down Expand Up @@ -729,6 +730,14 @@ def test_display_change_perms_button_tag_standard(self):
self.assertTrue(user_perms)


class SanitizedHtmlTest(TestCase):
def test_sanitized_html(self):
test_html = "<script>alert('XSS');</script><b>Allowed Text</b>"
expected_output = "<b>Allowed Text</b>"
sanitized_output = sanitize_html(test_html)
self.assertEqual(sanitized_output, expected_output)


class TestGetVisibleResource(TestCase):
def setUp(self):
self.user = get_user_model().objects.create(username="mikel_arteta")
Expand Down
13 changes: 7 additions & 6 deletions geonode/templates/metadata_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{% load bootstrap_tags %}
{% load thesaurus %}
{% load client_lib_tags %}
{% load sanitize_html %}

{% block title %}{{ resource.title }} — {{ block.super }}{% endblock %}

Expand Down Expand Up @@ -45,7 +46,7 @@ <h2 class="page-title">{% trans "Metadata" %} : {{ resource.title }}</h2>
{% block abstract %}
{% if resource.abstract %}
<dt>{% trans "Abstract" %}</dt>
<dd>{{ resource.abstract|safe }}</dd>
<dd>{{ resource.abstract|sanitize_html|safe }}</dd>
{% endif %}
{% endblock abstract %}

Expand Down Expand Up @@ -91,7 +92,7 @@ <h2 class="page-title">{% trans "Metadata" %} : {{ resource.title }}</h2>
{% for keyword in resource.keywords.all %}
<li>
<span itemscope itemtype="http://schema.org/Text">
{{ keyword.name| safe }}
{{ keyword.name|sanitize_html|safe }}
</span>
</li>
{% endfor %}
Expand Down Expand Up @@ -299,7 +300,7 @@ <h2 class="page-title">{% trans "Metadata" %} : {{ resource.title }}</h2>
{% if resource.restriction_code_type or resource.constraints_other %}
<dt>{% trans "Restrictions" %}</dt>
<dd>{% if resource.constraints_other %}
{{ resource.constraints_other|safe }}
{{ resource.constraints_other|sanitize_html|safe }}
{% else %}
{{ resource.restriction_code_type }}
{% endif %}</dd>
Expand All @@ -316,7 +317,7 @@ <h2 class="page-title">{% trans "Metadata" %} : {{ resource.title }}</h2>
{% block purpose %}
{% if resource.purpose %}
<dt>{% trans "Purpose" %}</dt>
<dd>{{ resource.purpose|safe }}</dd>
<dd>{{ resource.purpose|sanitize_html|safe }}</dd>
{% endif %}
{% endblock purpose %}

Expand All @@ -337,14 +338,14 @@ <h2 class="page-title">{% trans "Metadata" %} : {{ resource.title }}</h2>
{% block data_quality_statement %}
{% if resource.data_quality_statement %}
<dt>{% trans "Data Quality" %}</dt>
<dd>{{ resource.data_quality_statement|safe }}</dd>
<dd>{{ resource.data_quality_statement|sanitize_html|safe }}</dd>
{% endif %}
{% endblock data_quality_statement %}

{% block supplemental_information %}
{% if resource.supplemental_information %}
<dt>{% trans "Supplemental Information" %}</dt>
<dd>{{ resource.supplemental_information|safe }}</dd>
<dd>{{ resource.supplemental_information|sanitize_html|safe }}</dd>
{% endif %}
{% endblock supplemental_information %}

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,4 @@ jwcrypto>=1.4 # not directly required, pinned by Snyk to avoid a vulnerability
cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
aiohttp>=3.9.0 # not directly required, pinned by Snyk to avoid a vulnerability
dnspython>=2.6.0rc1 # not directly required, pinned by Snyk to avoid a vulnerability
nh3==0.2.15
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ install_requires =
cryptography>=41.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
aiohttp>=3.9.0 # not directly required, pinned by Snyk to avoid a vulnerability
dnspython>=2.6.0rc1 # not directly required, pinned by Snyk to avoid a vulnerability
nh3==0.2.15

[options.packages.find]
exclude = tests
Expand Down

0 comments on commit 9d6a246

Please sign in to comment.