ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
SandboxTest.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of Twig.
5  *
6  * (c) Fabien Potencier
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
12 class Twig_Tests_Extension_SandboxTest extends \PHPUnit\Framework\TestCase
13 {
14  protected static $params;
15  protected static $templates;
16 
17  protected function setUp()
18  {
19  self::$params = array(
20  'name' => 'Fabien',
21  'obj' => new FooObject(),
22  'arr' => array('obj' => new FooObject()),
23  );
24 
25  self::$templates = array(
26  '1_basic1' => '{{ obj.foo }}',
27  '1_basic2' => '{{ name|upper }}',
28  '1_basic3' => '{% if name %}foo{% endif %}',
29  '1_basic4' => '{{ obj.bar }}',
30  '1_basic5' => '{{ obj }}',
31  '1_basic6' => '{{ arr.obj }}',
32  '1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
33  '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
34  '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}',
35  '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
36  '1_layout' => '{% block content %}{% endblock %}',
37  '1_child' => "{% extends \"1_layout\" %}\n{% block content %}\n{{ \"a\"|json_encode }}\n{% endblock %}",
38  '1_include' => '{{ include("1_basic1", sandboxed=true) }}',
39  '1_range_operator' => '{{ (1..2)[0] }}',
40  );
41  }
42 
47  public function testSandboxWithInheritance()
48  {
49  $twig = $this->getEnvironment(true, array(), self::$templates, array('block'));
50  $twig->loadTemplate('1_child')->render(array());
51  }
52 
53  public function testSandboxGloballySet()
54  {
55  $twig = $this->getEnvironment(false, array(), self::$templates);
56  $this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');
57  }
58 
60  {
61  $twig = $this->getEnvironment(true, array(), self::$templates);
62  try {
63  $twig->loadTemplate('1_basic1')->render(self::$params);
64  $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
65  } catch (Twig_Sandbox_SecurityError $e) {
66  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError');
67  $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class');
68  $this->assertEquals('foo', $e->getMethodName(), 'Exception should be raised on the "foo" method');
69  }
70  }
71 
72  public function testSandboxUnallowedFilter()
73  {
74  $twig = $this->getEnvironment(true, array(), self::$templates);
75  try {
76  $twig->loadTemplate('1_basic2')->render(self::$params);
77  $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
78  } catch (Twig_Sandbox_SecurityError $e) {
79  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedFilterError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedFilterError');
80  $this->assertEquals('upper', $e->getFilterName(), 'Exception should be raised on the "upper" filter');
81  }
82  }
83 
84  public function testSandboxUnallowedTag()
85  {
86  $twig = $this->getEnvironment(true, array(), self::$templates);
87  try {
88  $twig->loadTemplate('1_basic3')->render(self::$params);
89  $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
90  } catch (Twig_Sandbox_SecurityError $e) {
91  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedTagError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedTagError');
92  $this->assertEquals('if', $e->getTagName(), 'Exception should be raised on the "if" tag');
93  }
94  }
95 
96  public function testSandboxUnallowedProperty()
97  {
98  $twig = $this->getEnvironment(true, array(), self::$templates);
99  try {
100  $twig->loadTemplate('1_basic4')->render(self::$params);
101  $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');
102  } catch (Twig_Sandbox_SecurityError $e) {
103  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedPropertyError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedPropertyError');
104  $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class');
105  $this->assertEquals('bar', $e->getPropertyName(), 'Exception should be raised on the "bar" property');
106  }
107  }
108 
110  {
111  $twig = $this->getEnvironment(true, array(), self::$templates);
112  try {
113  $twig->loadTemplate('1_basic5')->render(self::$params);
114  $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
115  } catch (Twig_Sandbox_SecurityError $e) {
116  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError');
117  $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class');
118  $this->assertEquals('__tostring', $e->getMethodName(), 'Exception should be raised on the "__toString" method');
119  }
120  }
121 
123  {
124  $twig = $this->getEnvironment(true, array(), self::$templates);
125  try {
126  $twig->loadTemplate('1_basic6')->render(self::$params);
127  $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
128  } catch (Twig_Sandbox_SecurityError $e) {
129  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedMethodError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedMethodError');
130  $this->assertEquals('FooObject', $e->getClassName(), 'Exception should be raised on the "FooObject" class');
131  $this->assertEquals('__tostring', $e->getMethodName(), 'Exception should be raised on the "__toString" method');
132  }
133  }
134 
136  {
137  $twig = $this->getEnvironment(true, array(), self::$templates);
138  try {
139  $twig->loadTemplate('1_basic7')->render(self::$params);
140  $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template');
141  } catch (Twig_Sandbox_SecurityError $e) {
142  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedFunctionError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedFunctionError');
143  $this->assertEquals('cycle', $e->getFunctionName(), 'Exception should be raised on the "cycle" function');
144  }
145  }
146 
148  {
149  $twig = $this->getEnvironment(true, array(), self::$templates);
150  try {
151  $twig->loadTemplate('1_range_operator')->render(self::$params);
152  $this->fail('Sandbox throws a SecurityError exception if the unallowed range operator is called');
153  } catch (Twig_Sandbox_SecurityError $e) {
154  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedFunctionError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedFunctionError');
155  $this->assertEquals('range', $e->getFunctionName(), 'Exception should be raised on the "range" function');
156  }
157  }
158 
159  public function testSandboxAllowMethodFoo()
160  {
161  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => 'foo'));
163  $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods');
164  $this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once');
165  }
166 
168  {
169  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => '__toString'));
171  $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods');
172  $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');
173  }
174 
176  {
177  $twig = $this->getEnvironment(false, array(), self::$templates);
179  $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allows __toString when sandbox disabled');
180  $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');
181  }
182 
183  public function testSandboxAllowFilter()
184  {
185  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper'));
186  $this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters');
187  }
188 
189  public function testSandboxAllowTag()
190  {
191  $twig = $this->getEnvironment(true, array(), self::$templates, array('if'));
192  $this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags');
193  }
194 
195  public function testSandboxAllowProperty()
196  {
197  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('FooObject' => 'bar'));
198  $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties');
199  }
200 
201  public function testSandboxAllowFunction()
202  {
203  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle'));
204  $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions');
205  }
206 
208  {
209  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('range'));
210  $this->assertEquals('1', $twig->loadTemplate('1_range_operator')->render(self::$params), 'Sandbox allow the range operator');
211  }
212 
214  {
215  foreach (array('getfoobar', 'getFoobar', 'getFooBar') as $name) {
216  $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => $name));
218  $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way');
219  $this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once');
220 
221  $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic9')->render(self::$params), 'Sandbox allow methods via shortcut names (ie. without get/set)');
222  }
223  }
224 
226  {
227  self::$templates = array(
228  '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
229  '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
230  );
231 
232  $twig = $this->getEnvironment(false, array(), self::$templates);
233  $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
234 
235  self::$templates = array(
236  '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
237  '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
238  );
239 
240  $twig = $this->getEnvironment(true, array(), self::$templates);
241  try {
242  $twig->loadTemplate('3_basic')->render(self::$params);
243  $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
244  } catch (Twig_Sandbox_SecurityError $e) {
245  $this->assertInstanceOf('Twig_Sandbox_SecurityNotAllowedTagError', $e, 'Exception should be an instance of Twig_Sandbox_SecurityNotAllowedTagError');
246  $this->assertEquals('sandbox', $e->getTagName());
247  }
248  }
249 
250  public function testMacrosInASandbox()
251  {
252  $twig = $this->getEnvironment(true, array('autoescape' => 'html'), array('index' => <<<EOF
253 {%- import _self as macros %}
254 
255 {%- macro test(text) %}<p>{{ text }}</p>{% endmacro %}
256 
257 {{- macros.test('username') }}
258 EOF
259  ), array('macro', 'import'), array('escape'));
260 
261  $this->assertEquals('<p>username</p>', $twig->loadTemplate('index')->render(array()));
262  }
263 
265  {
266  $twig = $this->getEnvironment(false, array(), self::$templates);
267 
268  $e = null;
269  try {
270  $twig->loadTemplate('1_include')->render(self::$params);
271  } catch (Throwable $e) {
272  } catch (Exception $e) {
273  }
274  if (null === $e) {
275  $this->fail('An exception should be thrown for this test to be valid.');
276  }
277 
278  $this->assertFalse($twig->getExtension('Twig_Extension_Sandbox')->isSandboxed(), 'Sandboxed include() function call should not leave Sandbox enabled when an error occurs.');
279  }
280 
281  protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array())
282  {
283  $loader = new Twig_Loader_Array($templates);
284  $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options));
285  $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
286  $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
287 
288  return $twig;
289  }
290 }
291 
293 {
294  public static $called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
295 
296  public $bar = 'bar';
298  public static function reset()
299  {
300  self::$called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
301  }
302 
303  public function __toString()
304  {
305  ++self::$called['__toString'];
306 
307  return 'foo';
308  }
309 
310  public function foo()
311  {
312  ++self::$called['foo'];
313 
314  return 'foo';
315  }
316 
317  public function getFooBar()
318  {
319  ++self::$called['getFooBar'];
320 
321  return 'foobar';
322  }
323 }
Exception thrown when a security error occurs at runtime.
static $called
testSandboxWithInheritance()
Twig_Sandbox_SecurityError Filter "json_encode" is not allowed in "1_child" at line 3...
Definition: SandboxTest.php:47
$tags
Definition: croninfo.php:19
getEnvironment($sandboxed, $options, $templates, $tags=array(), $filters=array(), $methods=array(), $properties=array(), $functions=array())
test()
Definition: build.php:107
Represents a security policy which need to be enforced when sandbox mode is enabled.
Stores the Twig configuration.
Definition: Environment.php:17
Loads a template from an array.
Definition: Array.php:26
static reset()
const EOF
How fgetc() reports an End Of File.
Definition: JSMin_lib.php:92