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

Add templatetags to clean HTML within django templates #16

Merged
merged 8 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
32 changes: 32 additions & 0 deletions src/django_nh3/templatetags/nh3_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import nh3
from django import template
from django.utils.safestring import SafeText, mark_safe

from django_nh3.utils import get_nh3_default_options

register = template.Library()


@register.filter(name="nh3")
def nh3_value(value: str | None, tags: str | None = None) -> SafeText:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a docstring on this please? Nothing complex, just something basic that descibes the fact it takes a value and will run clean on it & return a safe value to include in the template.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added! Not sure if there's a specific format you prefer so I can add to it if you have a preference. Cheers!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect - thank you!

"""
Takes an input HTML value and sanitizes it utilizing nh3,
returning a SafeText object that can be rendered by Django.

Accepts an optional argument of allowed tags. Should be a comma delimited
string (ie. "img,span" or "img")
"""
if value is None:
return None

args = {}

nh3_args = get_nh3_default_options()
if tags is not None:
args = nh3_args.copy()
args["tags"] = set(tags.split(","))
else:
args = nh3_args

nh3_value = nh3.clean(value, **args)
return mark_safe(nh3_value)
50 changes: 50 additions & 0 deletions src/django_nh3/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
from typing import Any

from django.conf import settings

logger = logging.getLogger(__name__)


def get_nh3_default_options() -> dict[str, Any]:
"""
Pull the django-nh3 settings similarly to how django-bleach handled them.

Some django-bleach settings can be mapped to django-nh3 settings without
any changes:

BLEACH_ALLOWED_TAGS -> NH3_ALLOWED_TAGS
BLEACH_ALLOWED_ATTRIBUTES -> NH3_ALLOWED_ATTRIBUTES
BLEACH_STRIP_COMMENTS -> NH3_STRIP_COMMENTS

While other settings are have no current support in nh3:

BLEACH_ALLOWED_STYLES -> There is no support for styling
BLEACH_ALLOWED_PROTOCOLS -> There is no suport for protocols
BLEACH_STRIP_TAGS -> This is the default behavior of nh3

"""
nh3_args: dict[str, Any] = {}

nh3_settings = {
"NH3_ALLOWED_TAGS": "tags",
"NH3_ALLOWED_ATTRIBUTES": "attributes",
"NH3_STRIP_COMMENTS": "strip_comments",
}

for setting, kwarg in nh3_settings.items():
if hasattr(settings, setting):
attr = getattr(settings, setting)

# Convert from general iterables to sets
if setting == "NH3_ALLOWED_TAGS":
attr = set(attr)
elif setting == "NH3_ALLOWED_ATTRIBUTES":
copy_dict = attr.copy()
for tag, attributes in attr.items():
copy_dict[tag] = set(attributes)
attr = copy_dict

nh3_args[kwarg] = attr

return nh3_args
5 changes: 5 additions & 0 deletions tests/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALLOWED_ATTRIBUTES = {"*": {"class", "style"}, "a": {"href", "title"}}

ALLOWED_TAGS = {"a", "li", "ul"}

STRIP_COMMENTS = True
32 changes: 32 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from unittest.mock import patch

from django.test import TestCase
from django_nh3.utils import get_nh3_default_options

from .constants import ALLOWED_ATTRIBUTES, ALLOWED_TAGS, STRIP_COMMENTS


class TestBleachOptions(TestCase):
@patch(
"django_nh3.utils.settings",
NH3_ALLOWED_ATTRIBUTES=ALLOWED_ATTRIBUTES,
)
def test_custom_attrs(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["attributes"], ALLOWED_ATTRIBUTES)

@patch(
"django_nh3.utils.settings",
NH3_ALLOWED_TAGS=ALLOWED_TAGS,
)
def test_custom_tags(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["tags"], ALLOWED_TAGS)

@patch(
"django_nh3.utils.settings",
NH3_STRIP_COMMENTS=STRIP_COMMENTS,
)
def test_strip_comments(self, settings):
nh3_args = get_nh3_default_options()
self.assertEqual(nh3_args["strip_comments"], STRIP_COMMENTS)
45 changes: 45 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from django.template import Context, Template
from django.test import TestCase


class TestBleachTemplates(TestCase):
"""Test template tags"""

def test_bleaching(self):
"""Test that unsafe tags are sanitised"""
context = Context(
{"some_unsafe_content": '<script>alert("Hello World!")</script>'},
)
template_to_render = Template(
"{% load nh3_tags %}" "{{ some_unsafe_content|nh3 }}"
)
rendered_template = template_to_render.render(context)
self.assertEqual("", rendered_template)

def test_bleaching_none(self):
"""Test that None is handled properly as an input"""
context = Context({"none_value": None})
template_to_render = Template(
"{% load nh3_tags %}" "{{ none_value|nh3 }}"
)
rendered_template = template_to_render.render(context)
self.assertEqual("None", rendered_template)

def test_bleaching_tags(self):
"""Test provided tags are kept"""
context = Context(
{
"some_unsafe_content": (
"<b><img src='' "
"onerror='alert(\\'hax\\')'>"
"I'm not trying to XSS you</b>"
)
}
)
template_to_render = Template(
"{% load nh3_tags %}" '{{ some_unsafe_content|nh3:"img" }}'
)
rendered_template = template_to_render.render(context)
self.assertInHTML(
'<img src="">I\'m not trying to XSS you', rendered_template
)