Fixed a test case
[jinja2.git] / jinja2 / testsuite / security.py
1 # -*- 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 """
11 import unittest
12
13 from jinja2.testsuite import JinjaTestCase
14
15 from jinja2 import Environment
16 from jinja2.sandbox import SandboxedEnvironment, \
17      ImmutableSandboxedEnvironment, unsafe
18 from jinja2 import Markup, escape
19 from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
20      TemplateRuntimeError
21
22
23 class 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
36 class PublicStuff(object):
37     bar = lambda self: 23
38     _foo = lambda self: 42
39
40     def __repr__(self):
41         return 'PublicStuff'
42
43
44 class 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), '')
57         # security error comes from __class__ already.
58         self.assert_raises(SecurityError, env.from_string(
59             "{{ foo.__class__.__subclasses__() }}").render, foo=42)
60
61     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
68     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
110     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
122     def test_attr_filter(self):
123         env = SandboxedEnvironment()
124         tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
125         self.assert_raises(SecurityError, tmpl.render, cls=int)
126
127     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
161
162 def suite():
163     suite = unittest.TestSuite()
164     suite.addTest(unittest.makeSuite(SandboxTestCase))
165     return suite