| 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>" |