123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import pytest
- from markupsafe import escape
- from jinja2 import Environment
- 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>"
|