[BACKEND] Properly sanitize recursive variables

This commit is contained in:
Jesse Bannon 2023-02-25 00:58:17 -08:00
parent a4dc61ea5b
commit 0dfeb06c36
2 changed files with 41 additions and 35 deletions

View file

@ -134,12 +134,21 @@ class StringFormatterValidator(Validator):
""" """
return self._value return self._value
def __apply_formatter( def _apply_formatter(
self, formatter: "StringFormatterValidator", variable_dict: Dict[str, str] self, formatter: "StringFormatterValidator", variable_dict: Dict[str, str]
) -> "StringFormatterValidator": ) -> "StringFormatterValidator":
# Ensure the variable names exist within the entry and overrides # Ensure the variable names exist within the entry and overrides
for variable_name in formatter.format_variables: for variable_name in formatter.format_variables:
if variable_name not in variable_dict: # If the variable exists, but is sanitized...
if variable_name.endswith('_sanitized') and variable_name.removesuffix('_sanitized') in variable_dict:
# Resolve just the non-sanitized version, then sanitize it
variable_dict[variable_name] = sanitize_filename(
StringFormatterValidator(
name=self._name, value=f"{{{variable_name.removesuffix('_sanitized')}}}"
).apply_formatter(variable_dict)
)
# If the variable doesn't exist, error
elif variable_name not in variable_dict:
available_fields = ", ".join(sorted(variable_dict.keys())) available_fields = ", ".join(sorted(variable_dict.keys()))
raise self._validation_exception( raise self._validation_exception(
self._variable_not_found_error_msg_formatter.format( self._variable_not_found_error_msg_formatter.format(
@ -153,37 +162,6 @@ class StringFormatterValidator(Validator):
value=formatter.format_string.format(**OrderedDict(variable_dict)), value=formatter.format_string.format(**OrderedDict(variable_dict)),
) )
def _apply_formatter(self, variable_dict: Dict[str, str], resolve_sanitized: bool = False):
formatter = self
recursion_depth = 0
max_depth = self._max_format_recursion
if resolve_sanitized:
for format_variable in formatter.format_variables:
# Must resolve the sanitized variable completely
if format_variable.endswith("_sanitized"):
# pylint: disable=protected-access
variable_dict[format_variable] = sanitize_filename(
StringFormatterValidator(
name=self._name, value=f"{{{format_variable}}}"
)._apply_formatter(variable_dict, resolve_sanitized=False)
)
# pylint: enable=protected-access
while formatter.format_variables and recursion_depth < max_depth:
formatter = self.__apply_formatter(formatter=formatter, variable_dict=variable_dict)
recursion_depth += 1
if formatter.format_variables:
raise self._validation_exception(
f"Attempted to format but failed after reaching max recursion depth of "
f"{max_depth}. Try to keep variables dependent on only one other variable at max. "
f"Unresolved variables: {', '.join(sorted(formatter.format_variables))}",
exception_class=StringFormattingException,
)
return formatter.format_string
def apply_formatter(self, variable_dict: Dict[str, str]) -> str: def apply_formatter(self, variable_dict: Dict[str, str]) -> str:
""" """
Calls `format` on the format string using the variable_dict as input kwargs Calls `format` on the format string using the variable_dict as input kwargs
@ -197,7 +175,23 @@ class StringFormatterValidator(Validator):
------- -------
Format string formatted Format string formatted
""" """
return self._apply_formatter(variable_dict=variable_dict, resolve_sanitized=True) formatter = self
recursion_depth = 0
max_depth = self._max_format_recursion
while formatter.format_variables and recursion_depth < max_depth:
formatter = self._apply_formatter(formatter=formatter, variable_dict=variable_dict)
recursion_depth += 1
if formatter.format_variables:
raise self._validation_exception(
f"Attempted to format but failed after reaching max recursion depth of "
f"{max_depth}. Try to keep variables dependent on only one other variable at max. "
f"Unresolved variables: {', '.join(sorted(formatter.format_variables))}",
exception_class=StringFormattingException,
)
return formatter.format_string
# pylint: disable=line-too-long # pylint: disable=line-too-long

View file

@ -153,7 +153,7 @@ class TestStringFormatterValidator(object):
variable_dict = { variable_dict = {
"level_a": "level a", "level_a": "level a",
"level_b": "level b ? {level_a}", "level_b": "level b ? {level_a}",
"level_c_sanitized": "level c and {level_b}", "level_c": "level c and {level_b}",
} }
format_string = string_formatter_class(name="test", value="level d and {level_c_sanitized}") format_string = string_formatter_class(name="test", value="level d and {level_c_sanitized}")
@ -161,6 +161,18 @@ class TestStringFormatterValidator(object):
assert format_string.apply_formatter(variable_dict=variable_dict) == expected_string assert format_string.apply_formatter(variable_dict=variable_dict) == expected_string
def test_entry_formatter_override_sanitized_recursive_inner(self, string_formatter_class):
variable_dict = {
"level_a": "level a ?",
"level_b": "level b ? {level_a_sanitized}",
"level_c": "level c and {level_b_sanitized}",
}
format_string = string_formatter_class(name="test", value="level d and {level_c}")
expected_string = "level d and level c and " + sanitize_filename("level b ? level a ?")
assert format_string.apply_formatter(variable_dict=variable_dict) == expected_string
def test_entry_formatter_override_recursive_fail_cycle(self, string_formatter_class): def test_entry_formatter_override_recursive_fail_cycle(self, string_formatter_class):
variable_dict = { variable_dict = {
"level_a": "{level_b}", "level_a": "{level_b}",