1import pytest 2 3from jinja2 import Environment 4from jinja2 import escape 5from jinja2.exceptions import SecurityError 6from jinja2.exceptions import TemplateRuntimeError 7from jinja2.exceptions import TemplateSyntaxError 8from jinja2.nodes import EvalContext 9from jinja2.sandbox import ImmutableSandboxedEnvironment 10from jinja2.sandbox import SandboxedEnvironment 11from jinja2.sandbox import unsafe 12 13 14class PrivateStuff: 15 def bar(self): 16 return 23 17 18 @unsafe 19 def foo(self): 20 return 42 21 22 def __repr__(self): 23 return "PrivateStuff" 24 25 26class PublicStuff: 27 def bar(self): 28 return 23 29 30 def _foo(self): 31 return 42 32 33 def __repr__(self): 34 return "PublicStuff" 35 36 37class TestSandbox: 38 def test_unsafe(self, env): 39 env = SandboxedEnvironment() 40 pytest.raises( 41 SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff() 42 ) 43 assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23" 44 45 pytest.raises( 46 SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff() 47 ) 48 assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23" 49 assert env.from_string("{{ foo.__class__ }}").render(foo=42) == "" 50 assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == "" 51 # security error comes from __class__ already. 52 pytest.raises( 53 SecurityError, 54 env.from_string("{{ foo.__class__.__subclasses__() }}").render, 55 foo=42, 56 ) 57 58 def test_immutable_environment(self, env): 59 env = ImmutableSandboxedEnvironment() 60 pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render) 61 pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render) 62 63 def test_restricted(self, env): 64 env = SandboxedEnvironment() 65 pytest.raises( 66 TemplateSyntaxError, 67 env.from_string, 68 "{% for item.attribute in seq %}...{% endfor %}", 69 ) 70 pytest.raises( 71 TemplateSyntaxError, 72 env.from_string, 73 "{% for foo, bar.baz in seq %}...{% endfor %}", 74 ) 75 76 def test_template_data(self, env): 77 env = Environment(autoescape=True) 78 t = env.from_string( 79 "{% macro say_hello(name) %}" 80 "<p>Hello {{ name }}!</p>{% endmacro %}" 81 '{{ say_hello("<blink>foo</blink>") }}' 82 ) 83 escaped_out = "<p>Hello <blink>foo</blink>!</p>" 84 assert t.render() == escaped_out 85 assert str(t.module) == escaped_out 86 assert escape(t.module) == escaped_out 87 assert t.module.say_hello("<blink>foo</blink>") == escaped_out 88 assert ( 89 escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>")) 90 == escaped_out 91 ) 92 assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out 93 94 def test_attr_filter(self, env): 95 env = SandboxedEnvironment() 96 tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}') 97 pytest.raises(SecurityError, tmpl.render, cls=int) 98 99 def test_binary_operator_intercepting(self, env): 100 def disable_op(left, right): 101 raise TemplateRuntimeError("that operator so does not work") 102 103 for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"): 104 env = SandboxedEnvironment() 105 env.binop_table["+"] = disable_op 106 t = env.from_string(f"{{{{ {expr} }}}}") 107 assert t.render(ctx) == rv 108 env.intercepted_binops = frozenset(["+"]) 109 t = env.from_string(f"{{{{ {expr} }}}}") 110 with pytest.raises(TemplateRuntimeError): 111 t.render(ctx) 112 113 def test_unary_operator_intercepting(self, env): 114 def disable_op(arg): 115 raise TemplateRuntimeError("that operator so does not work") 116 117 for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"): 118 env = SandboxedEnvironment() 119 env.unop_table["-"] = disable_op 120 t = env.from_string(f"{{{{ {expr} }}}}") 121 assert t.render(ctx) == rv 122 env.intercepted_unops = frozenset(["-"]) 123 t = env.from_string(f"{{{{ {expr} }}}}") 124 with pytest.raises(TemplateRuntimeError): 125 t.render(ctx) 126 127 128class TestStringFormat: 129 def test_basic_format_safety(self): 130 env = SandboxedEnvironment() 131 t = env.from_string('{{ "a{0.__class__}b".format(42) }}') 132 assert t.render() == "ab" 133 134 def test_basic_format_all_okay(self): 135 env = SandboxedEnvironment() 136 t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}') 137 assert t.render() == "a42b" 138 139 def test_safe_format_safety(self): 140 env = SandboxedEnvironment() 141 t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}') 142 assert t.render() == "ab<foo>" 143 144 def test_safe_format_all_okay(self): 145 env = SandboxedEnvironment() 146 t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}') 147 assert t.render() == "a42b<foo>" 148 149 def test_empty_braces_format(self): 150 env = SandboxedEnvironment() 151 t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}') 152 t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}') 153 assert t1.render() == "afoob42" 154 assert t2.render() == "a42b<foo>" 155 156 157class TestStringFormatMap: 158 def test_basic_format_safety(self): 159 env = SandboxedEnvironment() 160 t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}') 161 assert t.render() == "ab" 162 163 def test_basic_format_all_okay(self): 164 env = SandboxedEnvironment() 165 t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}') 166 assert t.render() == "a42b" 167 168 def test_safe_format_all_okay(self): 169 env = SandboxedEnvironment() 170 t = env.from_string( 171 '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}' 172 ) 173 assert t.render() == "a42b<foo>" 174