| import pytest | |
| from jinja2 import Environment | |
| from jinja2 import escape | |
| from jinja2.exceptions import SecurityError | |
| from jinja2.exceptions import TemplateRuntimeError | |
| from jinja2.exceptions import TemplateSyntaxError | |
| from jinja2.nodes import EvalContext | |
| from jinja2.sandbox import ImmutableSandboxedEnvironment | |
| from jinja2.sandbox import SandboxedEnvironment | |
| from jinja2.sandbox import unsafe | |
| class PrivateStuff: | |
| def bar(self): | |
| return 23 | |
| @unsafe | |
| def foo(self): | |
| return 42 | |
| def __repr__(self): | |
| return "PrivateStuff" | |
| class PublicStuff: | |
| def bar(self): | |
| return 23 | |
| def _foo(self): | |
| return 42 | |
| def __repr__(self): | |
| return "PublicStuff" | |
| class TestSandbox: | |
| def test_unsafe(self, env): | |
| env = SandboxedEnvironment() | |
| pytest.raises( | |
| SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff() | |
| ) | |
| assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23" | |
| pytest.raises( | |
| SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff() | |
| ) | |
| assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23" | |
| assert env.from_string("{{ foo.__class__ }}").render(foo=42) == "" | |
| assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == "" | |
| # security error comes from __class__ already. | |
| pytest.raises( | |
| SecurityError, | |
| env.from_string("{{ foo.__class__.__subclasses__() }}").render, | |
| foo=42, | |
| ) | |
| def test_immutable_environment(self, env): | |
| env = ImmutableSandboxedEnvironment() | |
| pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render) | |
| pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render) | |
| def test_restricted(self, env): | |
| env = SandboxedEnvironment() | |
| pytest.raises( | |
| TemplateSyntaxError, | |
| env.from_string, | |
| "{% for item.attribute in seq %}...{% endfor %}", | |
| ) | |
| pytest.raises( | |
| TemplateSyntaxError, | |
| env.from_string, | |
| "{% for foo, bar.baz in seq %}...{% endfor %}", | |
| ) | |
| def test_template_data(self, env): | |
| env = Environment(autoescape=True) | |
| t = env.from_string( | |
| "{% macro say_hello(name) %}" | |
| "<p>Hello {{ name }}!</p>{% endmacro %}" | |
| '{{ say_hello("<blink>foo</blink>") }}' | |
| ) | |
| escaped_out = "<p>Hello <blink>foo</blink>!</p>" | |
| assert t.render() == escaped_out | |
| assert str(t.module) == escaped_out | |
| assert escape(t.module) == escaped_out | |
| assert t.module.say_hello("<blink>foo</blink>") == escaped_out | |
| assert ( | |
| escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>")) | |
| == escaped_out | |
| ) | |
| assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out | |
| def test_attr_filter(self, env): | |
| env = SandboxedEnvironment() | |
| tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}') | |
| pytest.raises(SecurityError, tmpl.render, cls=int) | |
| def test_binary_operator_intercepting(self, env): | |
| def disable_op(left, right): | |
| raise TemplateRuntimeError("that operator so does not work") | |
| for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"): | |
| env = SandboxedEnvironment() | |
| env.binop_table["+"] = disable_op | |
| t = env.from_string(f"{{{{ {expr} }}}}") | |
| assert t.render(ctx) == rv | |
| env.intercepted_binops = frozenset(["+"]) | |
| t = env.from_string(f"{{{{ {expr} }}}}") | |
| with pytest.raises(TemplateRuntimeError): | |
| t.render(ctx) | |
| def test_unary_operator_intercepting(self, env): | |
| def disable_op(arg): | |
| raise TemplateRuntimeError("that operator so does not work") | |
| for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"): | |
| env = SandboxedEnvironment() | |
| env.unop_table["-"] = disable_op | |
| t = env.from_string(f"{{{{ {expr} }}}}") | |
| assert t.render(ctx) == rv | |
| env.intercepted_unops = frozenset(["-"]) | |
| t = env.from_string(f"{{{{ {expr} }}}}") | |
| with pytest.raises(TemplateRuntimeError): | |
| t.render(ctx) | |
| class TestStringFormat: | |
| def test_basic_format_safety(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ "a{0.__class__}b".format(42) }}') | |
| assert t.render() == "ab" | |
| def test_basic_format_all_okay(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}') | |
| assert t.render() == "a42b" | |
| def test_safe_format_safety(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}') | |
| assert t.render() == "ab<foo>" | |
| def test_safe_format_all_okay(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}') | |
| assert t.render() == "a42b<foo>" | |
| def test_empty_braces_format(self): | |
| env = SandboxedEnvironment() | |
| t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}') | |
| t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}') | |
| assert t1.render() == "afoob42" | |
| assert t2.render() == "a42b<foo>" | |
| class TestStringFormatMap: | |
| def test_basic_format_safety(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}') | |
| assert t.render() == "ab" | |
| def test_basic_format_all_okay(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}') | |
| assert t.render() == "a42b" | |
| def test_safe_format_all_okay(self): | |
| env = SandboxedEnvironment() | |
| t = env.from_string( | |
| '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}' | |
| ) | |
| assert t.render() == "a42b<foo>" |