Skip to content
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
67 changes: 67 additions & 0 deletions src/ansys/dynamicreporting/core/common_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import platform
import re

import bleach

from . import DEFAULT_ANSYS_VERSION as CURRENT_VERSION
from .constants import JSON_NECESSARY_KEYS, JSON_TEMPLATE_KEYS, REPORT_TYPES
from .exceptions import InvalidAnsysPath
Expand Down Expand Up @@ -189,3 +191,68 @@ def populate_template(id_str, attr, parent_template, create_template_func, logge
template.set_filter(filter_str=attr["item_filter"] if "item_filter" in attr else "")

return template


PROPERTIES_EXEMPT = {
"link_text",
"userdef_name",
"filter_x_title",
"filter_y_title",
"labels_column",
"labels_row",
"title",
"line_marker_text",
"plot_title",
"xtitle",
"ytitle",
"ztitle",
"nan_display",
"table_title",
"image_title",
"slider_title",
"TOCName",
}


def validate_html_dictionary(data):
for key, value in data.items():
# Do not validate HTML key
if key == "HTML":
continue

# Recursive case for nested dictionaries
if isinstance(value, dict):
# Specific checks for properties key
if key == "properties":
subdict = {k: v for k, v in value.items() if k not in PROPERTIES_EXEMPT}
validate_html_dictionary(subdict)
else:
validate_html_dictionary(value)

# Check for lists
elif isinstance(value, list):
validate_html_list(value, key)

# Main check for strings
elif isinstance(value, str):
cleaned_string = bleach.clean(value, strip=True)
if cleaned_string != value:
raise ValueError(f"{key} contains HTML content.")

# Ignore other types
else:
continue


def validate_html_list(value_list, key):
for item in value_list:
if isinstance(item, str):
cleaned_string = bleach.clean(item, strip=True)
if cleaned_string != item:
raise ValueError(f"{key} contains HTML content.")
elif isinstance(item, dict):
validate_html_dictionary(item)
elif isinstance(item, list):
validate_html_list(item, key)
else:
continue
3 changes: 3 additions & 0 deletions src/ansys/dynamicreporting/core/serverless/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.template.loader import render_to_string
from django.utils import timezone

from ..common_utils import validate_html_dictionary
from ..constants import JSON_ATTR_KEYS
from ..exceptions import ADRException, TemplateDoesNotExist, TemplateReorderOutOfBounds
from .base import BaseModel, StrEnum
Expand Down Expand Up @@ -251,6 +252,8 @@ def set_params(self, new_params: dict) -> None:
new_params = {}
if not isinstance(new_params, dict):
raise TypeError("input must be a dictionary")
if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1":
validate_html_dictionary(new_params)
self.params = json.dumps(new_params)

def add_params(self, new_params: dict):
Expand Down
5 changes: 5 additions & 0 deletions src/ansys/dynamicreporting/core/utils/report_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import pytz

from . import extremely_ugly_hacks, report_utils
from ..common_utils import validate_html_dictionary
from ..exceptions import TemplateDoesNotExist, TemplateReorderOutOfBounds
from .encoders import PayloaddataEncoder

Expand Down Expand Up @@ -413,6 +414,8 @@ def set_params(self, d: dict = None):
d = {}
if type(d) is not dict:
raise ValueError("Error: input must be a dictionary")
if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1":
validate_html_dictionary(d)
self.params = json.dumps(d)
return

Expand Down Expand Up @@ -1541,6 +1544,8 @@ def set_params(self, d: dict = None):
d = {}
if type(d) is not dict:
raise ValueError("Error: input must be a dictionary")
if os.getenv("ADR_VALIDATION_BETAFLAG_ANSYS") == "1":
validate_html_dictionary(d)
self.params = json.dumps(d)
return

Expand Down
57 changes: 57 additions & 0 deletions tests/test_report_objects.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import json
import os
import uuid

import pytest
Expand Down Expand Up @@ -1964,6 +1965,62 @@ def test_unit_template() -> None:
assert succ and succ_two and succ_three and succ_four


@pytest.mark.ado_test
def test_template_validation() -> None:
os.environ["ADR_VALIDATION_BETAFLAG_ANSYS"] = "1"
a = ro.Template()
try:
a.set_params(
{
"reduce_params": {
"reduce_type": "row<script>This is bad</script>",
"operations": [
"test 1",
["test 2", 1],
{"source_rows": "'Phase*'", "output_rows": "Maximum"},
],
},
"properties": {"plot": "line", "plot_title": "Reduced Table"},
"HTML": "<div>Test</div>",
}
)
except ValueError as e:
succ_one = "contains HTML content" in str(e)
try:
a.set_params(
{
"reduce_params": {
"reduce_type": "row",
"operations": [
["test 2", 1],
{"source_rows": "'Phase*'", "output_rows": "Maximum"},
"test 1<script>Bad</script>",
],
},
"properties": {"plot": "line", "plot_title": "Reduced Table"},
"HTML": "<div>Test</div>",
}
)
except ValueError as e:
succ_two = "contains HTML content" in str(e)
a.set_params(
{
"reduce_params": {
"reduce_type": "row",
"operations": [
"test 1",
["test 2", 1],
{"source_rows": "'Phase*'", "output_rows": "Maximum"},
],
},
"HTML": "<div>Test</div>",
"properties": {"plot": "line", "plot_title": "Reduced Table<script>Bad</script>"},
}
)
del os.environ["ADR_VALIDATION_BETAFLAG_ANSYS"]
assert succ_one and succ_two


@pytest.mark.ado_test
def test_unit_base() -> None:
a = ro.BaseRESTObject()
Expand Down
Loading