You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
6.0 KiB
174 lines
6.0 KiB
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>"
|