blob: 4518eac659fc33b2284f16e76c4a67b47572fcae [file] [log] [blame]
Armin Ronacher644a2812010-02-09 18:06:32 +01001# -*- coding: utf-8 -*-
2"""
3 jinja2.testsuite.security
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Checks the sandbox and other security features.
7
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
10"""
Armin Ronacher644a2812010-02-09 18:06:32 +010011import unittest
12
13from jinja2.testsuite import JinjaTestCase
14
15from jinja2 import Environment
16from jinja2.sandbox import SandboxedEnvironment, \
17 ImmutableSandboxedEnvironment, unsafe
18from jinja2 import Markup, escape
Armin Ronachera9195382010-11-29 13:21:57 +010019from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
20 TemplateRuntimeError
Armin Ronacher644a2812010-02-09 18:06:32 +010021
22
23class PrivateStuff(object):
24
25 def bar(self):
26 return 23
27
28 @unsafe
29 def foo(self):
30 return 42
31
32 def __repr__(self):
33 return 'PrivateStuff'
34
35
36class PublicStuff(object):
37 bar = lambda self: 23
38 _foo = lambda self: 42
39
40 def __repr__(self):
41 return 'PublicStuff'
42
43
44class SandboxTestCase(JinjaTestCase):
45
46 def test_unsafe(self):
47 env = SandboxedEnvironment()
48 self.assert_raises(SecurityError, env.from_string("{{ foo.foo() }}").render,
49 foo=PrivateStuff())
50 self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()), '23')
51
52 self.assert_raises(SecurityError, env.from_string("{{ foo._foo() }}").render,
53 foo=PublicStuff())
54 self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()), '23')
55 self.assert_equal(env.from_string("{{ foo.__class__ }}").render(foo=42), '')
56 self.assert_equal(env.from_string("{{ foo.func_code }}").render(foo=lambda:None), '')
Armin Ronacher6a3e95d2010-11-19 13:51:38 +010057 # security error comes from __class__ already.
Armin Ronacher644a2812010-02-09 18:06:32 +010058 self.assert_raises(SecurityError, env.from_string(
59 "{{ foo.__class__.__subclasses__() }}").render, foo=42)
60
Armin Ronacher1fb42692010-02-09 21:14:16 +010061 def test_immutable_environment(self):
62 env = ImmutableSandboxedEnvironment()
63 self.assert_raises(SecurityError, env.from_string(
64 '{{ [].append(23) }}').render)
65 self.assert_raises(SecurityError, env.from_string(
66 '{{ {1:2}.clear() }}').render)
67
Armin Ronacher644a2812010-02-09 18:06:32 +010068 def test_restricted(self):
69 env = SandboxedEnvironment()
70 self.assert_raises(TemplateSyntaxError, env.from_string,
71 "{% for item.attribute in seq %}...{% endfor %}")
72 self.assert_raises(TemplateSyntaxError, env.from_string,
73 "{% for foo, bar.baz in seq %}...{% endfor %}")
74
75 def test_markup_operations(self):
76 # adding two strings should escape the unsafe one
77 unsafe = '<script type="application/x-some-script">alert("foo");</script>'
78 safe = Markup('<em>username</em>')
79 assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe)
80
81 # string interpolations are safe to use too
82 assert Markup('<em>%s</em>') % '<bad user>' == \
83 '<em>&lt;bad user&gt;</em>'
84 assert Markup('<em>%(username)s</em>') % {
85 'username': '<bad user>'
86 } == '<em>&lt;bad user&gt;</em>'
87
88 # an escaped object is markup too
89 assert type(Markup('foo') + 'bar') is Markup
90
91 # and it implements __html__ by returning itself
92 x = Markup("foo")
93 assert x.__html__() is x
94
95 # it also knows how to treat __html__ objects
96 class Foo(object):
97 def __html__(self):
98 return '<em>awesome</em>'
99 def __unicode__(self):
100 return 'awesome'
101 assert Markup(Foo()) == '<em>awesome</em>'
102 assert Markup('<strong>%s</strong>') % Foo() == \
103 '<strong><em>awesome</em></strong>'
104
105 # escaping and unescaping
106 assert escape('"<>&\'') == '&#34;&lt;&gt;&amp;&#39;'
107 assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
108 assert Markup("&lt;test&gt;").unescape() == "<test>"
109
Armin Ronacher644a2812010-02-09 18:06:32 +0100110 def test_template_data(self):
111 env = Environment(autoescape=True)
112 t = env.from_string('{% macro say_hello(name) %}'
113 '<p>Hello {{ name }}!</p>{% endmacro %}'
114 '{{ say_hello("<blink>foo</blink>") }}')
115 escaped_out = '<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>'
116 assert t.render() == escaped_out
117 assert unicode(t.module) == escaped_out
118 assert escape(t.module) == escaped_out
119 assert t.module.say_hello('<blink>foo</blink>') == escaped_out
120 assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
121
Armin Ronacher644a2812010-02-09 18:06:32 +0100122 def test_attr_filter(self):
123 env = SandboxedEnvironment()
Armin Ronacher6a3e95d2010-11-19 13:51:38 +0100124 tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
125 self.assert_raises(SecurityError, tmpl.render, cls=int)
Armin Ronacher644a2812010-02-09 18:06:32 +0100126
Armin Ronachera9195382010-11-29 13:21:57 +0100127 def test_binary_operator_intercepting(self):
128 def disable_op(left, right):
129 raise TemplateRuntimeError('that operator so does not work')
130 for expr, ctx, rv in ('1 + 2', {}, '3'), ('a + 2', {'a': 2}, '4'):
131 env = SandboxedEnvironment()
132 env.binop_table['+'] = disable_op
133 t = env.from_string('{{ %s }}' % expr)
134 assert t.render(ctx) == rv
135 env.intercepted_binops = frozenset(['+'])
136 t = env.from_string('{{ %s }}' % expr)
137 try:
138 t.render(ctx)
139 except TemplateRuntimeError, e:
140 pass
141 else:
142 self.fail('expected runtime error')
143
144 def test_unary_operator_intercepting(self):
145 def disable_op(arg):
146 raise TemplateRuntimeError('that operator so does not work')
147 for expr, ctx, rv in ('-1', {}, '-1'), ('-a', {'a': 2}, '-2'):
148 env = SandboxedEnvironment()
149 env.unop_table['-'] = disable_op
150 t = env.from_string('{{ %s }}' % expr)
151 assert t.render(ctx) == rv
152 env.intercepted_unops = frozenset(['-'])
153 t = env.from_string('{{ %s }}' % expr)
154 try:
155 t.render(ctx)
156 except TemplateRuntimeError, e:
157 pass
158 else:
159 self.fail('expected runtime error')
160
Armin Ronacher644a2812010-02-09 18:06:32 +0100161
162def suite():
163 suite = unittest.TestSuite()
164 suite.addTest(unittest.makeSuite(SandboxTestCase))
165 return suite