SandboxTest.php 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
  11. {
  12. static protected $params, $templates;
  13. public function setUp()
  14. {
  15. self::$params = array(
  16. 'name' => 'Fabien',
  17. 'obj' => new FooObject(),
  18. 'arr' => array('obj' => new FooObject()),
  19. );
  20. self::$templates = array(
  21. '1_basic1' => '{{ obj.foo }}',
  22. '1_basic2' => '{{ name|upper }}',
  23. '1_basic3' => '{% if name %}foo{% endif %}',
  24. '1_basic4' => '{{ obj.bar }}',
  25. '1_basic5' => '{{ obj }}',
  26. '1_basic6' => '{{ arr.obj }}',
  27. '1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
  28. '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
  29. '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
  30. '1_layout' => '{% block content %}{% endblock %}',
  31. '1_child' => '{% extends "1_layout" %}{% block content %}{{ "a"|json_encode }}{% endblock %}',
  32. );
  33. }
  34. /**
  35. * @expectedException Twig_Sandbox_SecurityError
  36. * @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child".
  37. */
  38. public function testSandboxWithInheritance()
  39. {
  40. $twig = $this->getEnvironment(true, array(), self::$templates, array('block'));
  41. $twig->loadTemplate('1_child')->render(array());
  42. }
  43. public function testSandboxGloballySet()
  44. {
  45. $twig = $this->getEnvironment(false, array(), self::$templates);
  46. $this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');
  47. $twig = $this->getEnvironment(true, array(), self::$templates);
  48. try {
  49. $twig->loadTemplate('1_basic1')->render(self::$params);
  50. $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
  51. } catch (Twig_Sandbox_SecurityError $e) {
  52. }
  53. $twig = $this->getEnvironment(true, array(), self::$templates);
  54. try {
  55. $twig->loadTemplate('1_basic2')->render(self::$params);
  56. $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
  57. } catch (Twig_Sandbox_SecurityError $e) {
  58. }
  59. $twig = $this->getEnvironment(true, array(), self::$templates);
  60. try {
  61. $twig->loadTemplate('1_basic3')->render(self::$params);
  62. $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
  63. } catch (Twig_Sandbox_SecurityError $e) {
  64. }
  65. $twig = $this->getEnvironment(true, array(), self::$templates);
  66. try {
  67. $twig->loadTemplate('1_basic4')->render(self::$params);
  68. $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');
  69. } catch (Twig_Sandbox_SecurityError $e) {
  70. }
  71. $twig = $this->getEnvironment(true, array(), self::$templates);
  72. try {
  73. $twig->loadTemplate('1_basic5')->render(self::$params);
  74. $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
  75. } catch (Twig_Sandbox_SecurityError $e) {
  76. }
  77. $twig = $this->getEnvironment(true, array(), self::$templates);
  78. try {
  79. $twig->loadTemplate('1_basic6')->render(self::$params);
  80. $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
  81. } catch (Twig_Sandbox_SecurityError $e) {
  82. }
  83. $twig = $this->getEnvironment(true, array(), self::$templates);
  84. try {
  85. $twig->loadTemplate('1_basic7')->render(self::$params);
  86. $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template');
  87. } catch (Twig_Sandbox_SecurityError $e) {
  88. }
  89. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => 'foo'));
  90. FooObject::reset();
  91. $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods');
  92. $this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once');
  93. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => '__toString'));
  94. FooObject::reset();
  95. $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods');
  96. $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');
  97. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper'));
  98. $this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters');
  99. $twig = $this->getEnvironment(true, array(), self::$templates, array('if'));
  100. $this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags');
  101. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('FooObject' => 'bar'));
  102. $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties');
  103. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle'));
  104. $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions');
  105. foreach (array('getfoobar', 'getFoobar', 'getFooBar') as $name) {
  106. $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => $name));
  107. FooObject::reset();
  108. $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way');
  109. $this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once');
  110. }
  111. }
  112. public function testSandboxLocallySetForAnInclude()
  113. {
  114. self::$templates = array(
  115. '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
  116. '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
  117. );
  118. $twig = $this->getEnvironment(false, array(), self::$templates);
  119. $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
  120. self::$templates = array(
  121. '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
  122. '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
  123. );
  124. $twig = $this->getEnvironment(true, array(), self::$templates);
  125. try {
  126. $twig->loadTemplate('3_basic')->render(self::$params);
  127. $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
  128. } catch (Twig_Sandbox_SecurityError $e) {
  129. }
  130. }
  131. public function testMacrosInASandbox()
  132. {
  133. $twig = $this->getEnvironment(true, array('autoescape' => true), array('index' => <<<EOF
  134. {% macro test(text) %}<p>{{ text }}</p>{% endmacro %}
  135. {{ _self.test('username') }}
  136. EOF
  137. ), array('macro'), array('escape'));
  138. $this->assertEquals('<p>username</p>', $twig->loadTemplate('index')->render(array()));
  139. }
  140. protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array())
  141. {
  142. $loader = new Twig_Loader_Array($templates);
  143. $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options));
  144. $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
  145. $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
  146. return $twig;
  147. }
  148. }
  149. class FooObject
  150. {
  151. static public $called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
  152. public $bar = 'bar';
  153. static public function reset()
  154. {
  155. self::$called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
  156. }
  157. public function __toString()
  158. {
  159. ++self::$called['__toString'];
  160. return 'foo';
  161. }
  162. public function foo()
  163. {
  164. ++self::$called['foo'];
  165. return 'foo';
  166. }
  167. public function getFooBar()
  168. {
  169. ++self::$called['getFooBar'];
  170. return 'foobar';
  171. }
  172. }