diff --git a/jinja2/testsuite/__init__.py b/jinja2/testsuite/__init__.py index 9d9daadf6..bb156e890 100644 --- a/jinja2/testsuite/__init__.py +++ b/jinja2/testsuite/__init__.py @@ -45,7 +45,7 @@ def assert_raises(self, *args, **kwargs): def suite(): from jinja2.testsuite import ext, filters, tests, core_tags, \ - loader, inheritance, imports, lexnparse + loader, inheritance, imports, lexnparse, security suite = unittest.TestSuite() suite.addTest(ext.suite()) suite.addTest(filters.suite()) @@ -55,4 +55,5 @@ def suite(): suite.addTest(inheritance.suite()) suite.addTest(imports.suite()) suite.addTest(lexnparse.suite()) + suite.addTest(security.suite()) return suite diff --git a/jinja2/testsuite/security.py b/jinja2/testsuite/security.py new file mode 100644 index 000000000..a17c3a304 --- /dev/null +++ b/jinja2/testsuite/security.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +""" + jinja2.testsuite.security + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Checks the sandbox and other security features. + + :copyright: (c) 2010 by the Jinja Team. + :license: BSD, see LICENSE for more details. +""" +import os +import time +import tempfile +import unittest + +from jinja2.testsuite import JinjaTestCase + +from jinja2 import Environment +from jinja2.sandbox import SandboxedEnvironment, \ + ImmutableSandboxedEnvironment, unsafe +from jinja2 import Markup, escape +from jinja2.exceptions import SecurityError, TemplateSyntaxError + + +class PrivateStuff(object): + + def bar(self): + return 23 + + @unsafe + def foo(self): + return 42 + + def __repr__(self): + return 'PrivateStuff' + + +class PublicStuff(object): + bar = lambda self: 23 + _foo = lambda self: 42 + + def __repr__(self): + return 'PublicStuff' + + +class SandboxTestCase(JinjaTestCase): + + def test_unsafe(self): + env = SandboxedEnvironment() + self.assert_raises(SecurityError, env.from_string("{{ foo.foo() }}").render, + foo=PrivateStuff()) + self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()), '23') + + self.assert_raises(SecurityError, env.from_string("{{ foo._foo() }}").render, + foo=PublicStuff()) + self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()), '23') + self.assert_equal(env.from_string("{{ foo.__class__ }}").render(foo=42), '') + self.assert_equal(env.from_string("{{ foo.func_code }}").render(foo=lambda:None), '') + self.assert_raises(SecurityError, env.from_string( + "{{ foo.__class__.__subclasses__() }}").render, foo=42) + + def test_restricted(self): + env = SandboxedEnvironment() + self.assert_raises(TemplateSyntaxError, env.from_string, + "{% for item.attribute in seq %}...{% endfor %}") + self.assert_raises(TemplateSyntaxError, env.from_string, + "{% for foo, bar.baz in seq %}...{% endfor %}") + + def test_markup_operations(self): + # adding two strings should escape the unsafe one + unsafe = '' + safe = Markup('username') + assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe) + + # string interpolations are safe to use too + assert Markup('%s') % '' == \ + '<bad user>' + assert Markup('%(username)s') % { + 'username': '' + } == '<bad user>' + + # an escaped object is markup too + assert type(Markup('foo') + 'bar') is Markup + + # and it implements __html__ by returning itself + x = Markup("foo") + assert x.__html__() is x + + # it also knows how to treat __html__ objects + class Foo(object): + def __html__(self): + return 'awesome' + def __unicode__(self): + return 'awesome' + assert Markup(Foo()) == 'awesome' + assert Markup('%s') % Foo() == \ + 'awesome' + + # escaping and unescaping + assert escape('"<>&\'') == '"<>&'' + assert Markup("Foo & Bar").striptags() == "Foo & Bar" + assert Markup("<test>").unescape() == "" + + + def test_template_data(self): + env = Environment(autoescape=True) + t = env.from_string('{% macro say_hello(name) %}' + '

Hello {{ name }}!

{% endmacro %}' + '{{ say_hello("foo") }}') + escaped_out = '

Hello <blink>foo</blink>!

' + assert t.render() == escaped_out + assert unicode(t.module) == escaped_out + assert escape(t.module) == escaped_out + assert t.module.say_hello('foo') == escaped_out + assert escape(t.module.say_hello('foo')) == escaped_out + + + def test_attr_filter(self): + env = SandboxedEnvironment() + tmpl = env.from_string('{{ 42|attr("__class__")|attr("__subclasses__")() }}') + self.assert_raises(SecurityError, tmpl.render) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(SandboxTestCase)) + return suite diff --git a/setup.py b/setup.py index f69fecc7c..3125599f1 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Processing :: Markup :: HTML' ], - packages=['jinja2', 'jinja2.testsuite'], + packages=['jinja2', 'jinja2.testsuite', 'jinja2.testsuite.res'], features={ 'speedups': Feature("optional C speed-enhancements", standard=False,