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

Escape component values #288

Merged
merged 1 commit into from Oct 7, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 22 additions & 1 deletion django_unicorn/components/unicorn_view.py
Expand Up @@ -10,6 +10,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Model
from django.http import HttpRequest
from django.utils.html import conditional_escape
from django.views.generic.base import TemplateView

from cachetools.lru import LRUCache
Expand Down Expand Up @@ -333,13 +334,21 @@ def get_frontend_context_variables(self) -> str:
attributes = self._attributes()
frontend_context_variables.update(attributes)

# Remove any field in `javascript_exclude` from the `frontend_context_variables`
# Remove any field in `javascript_exclude` from `frontend_context_variables`
if hasattr(self, "Meta") and hasattr(self.Meta, "javascript_exclude"):
if isinstance(self.Meta.javascript_exclude, Sequence):
for field_name in self.Meta.javascript_exclude:
if field_name in frontend_context_variables:
del frontend_context_variables[field_name]

safe_fields = []
# Keep a list of fields that are safe to not sanitize from `frontend_context_variables`
if hasattr(self, "Meta") and hasattr(self.Meta, "safe"):
if isinstance(self.Meta.safe, Sequence):
for field_name in self.Meta.safe:
if field_name in frontend_context_variables:
safe_fields.append(field_name)

# Add cleaned values to `frontend_content_variables` based on the widget in form's fields
form = self._get_form(attributes)

Expand All @@ -363,6 +372,18 @@ def get_frontend_context_variables(self) -> str:
):
frontend_context_variables[key] = value

for (
frontend_context_variable_key,
frontend_context_variable_value,
) in frontend_context_variables.items():
if (
isinstance(frontend_context_variable_value, str)
and frontend_context_variable_key not in safe_fields
):
frontend_context_variables[
frontend_context_variable_key
] = conditional_escape(frontend_context_variable_value)

encoded_frontend_context_variables = serializer.dumps(
frontend_context_variables
)
Expand Down
2 changes: 1 addition & 1 deletion django_unicorn/utils.py
Expand Up @@ -6,7 +6,7 @@
from typing import get_type_hints as typing_get_type_hints

from django.conf import settings
from django.utils.html import _json_script_escapes, format_html
from django.utils.html import _json_script_escapes
from django.utils.safestring import mark_safe

import shortuuid
Expand Down
35 changes: 34 additions & 1 deletion tests/components/test_component.py
Expand Up @@ -13,7 +13,7 @@ def get_name(self):
return "World"


@pytest.fixture(scope="module")
@pytest.fixture()
def component():
return ExampleComponent(component_id="asdf1234", component_name="example")

Expand Down Expand Up @@ -82,6 +82,39 @@ def test_get_frontend_context_variables(component):
assert frontend_context_variables_dict.get("name") == "World"


def test_get_frontend_context_variables_xss(component):
# Set component.name to a potential XSS attack
component.name = '<a><style>@keyframes x{}</style><a style="animation-name:x" onanimationend="alert(1)"></a>'

frontend_context_variables = component.get_frontend_context_variables()
frontend_context_variables_dict = orjson.loads(frontend_context_variables)
assert len(frontend_context_variables_dict) == 1
assert (
frontend_context_variables_dict.get("name")
== "&lt;a&gt;&lt;style&gt;@keyframes x{}&lt;/style&gt;&lt;a style=&quot;animation-name:x&quot; onanimationend=&quot;alert(1)&quot;&gt;&lt;/a&gt;"
)


def test_get_frontend_context_variables_safe(component):
# Set component.name to a potential XSS attack
component.name = '<a><style>@keyframes x{}</style><a style="animation-name:x" onanimationend="alert(1)"></a>'

class Meta:
safe = [
"name",
]

setattr(component, "Meta", Meta())

frontend_context_variables = component.get_frontend_context_variables()
frontend_context_variables_dict = orjson.loads(frontend_context_variables)
assert len(frontend_context_variables_dict) == 1
assert (
frontend_context_variables_dict.get("name")
== '<a><style>@keyframes x{}</style><a style="animation-name:x" onanimationend="alert(1)"></a>'
)


def test_get_context_data(component):
context_data = component.get_context_data()
assert (
Expand Down