From: Arnaud Le Blanc Date: Sat, 25 Dec 2010 15:08:12 +0000 (+0100) Subject: Added sandbox support for functions X-Git-Url: http://git.silmor.de/gitweb/?a=commitdiff_plain;h=7794463706680fa13108e196fe554def94c08203;p=web%2Fkonrad%2Ftwig.git Added sandbox support for functions --- diff --git a/lib/Twig/Extension/Sandbox.php b/lib/Twig/Extension/Sandbox.php index bc049fe..c5cbbbf 100644 --- a/lib/Twig/Extension/Sandbox.php +++ b/lib/Twig/Extension/Sandbox.php @@ -70,10 +70,10 @@ class Twig_Extension_Sandbox extends Twig_Extension return $this->policy; } - public function checkSecurity($tags, $filters) + public function checkSecurity($tags, $filters, $functions) { if ($this->isSandboxed()) { - $this->policy->checkSecurity($tags, $filters); + $this->policy->checkSecurity($tags, $filters, $functions); } } diff --git a/lib/Twig/Node/SandboxedModule.php b/lib/Twig/Node/SandboxedModule.php index 23c4ba7..d03df81 100644 --- a/lib/Twig/Node/SandboxedModule.php +++ b/lib/Twig/Node/SandboxedModule.php @@ -20,13 +20,15 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module { protected $usedFilters; protected $usedTags; + protected $usedFunctions; - public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags) + public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions) { parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag()); $this->usedFilters = $usedFilters; $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; } protected function compileDisplayBody($compiler) @@ -48,7 +50,8 @@ class Twig_Node_SandboxedModule extends Twig_Node_Module ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n") ->indent() ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n") - ->write(!$this->usedFilters ? "array()\n" : "array('".implode('\', \'', $this->usedFilters)."')\n") + ->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n") + ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n") ->outdent() ->write(");\n") ; diff --git a/lib/Twig/NodeVisitor/Sandbox.php b/lib/Twig/NodeVisitor/Sandbox.php index 3f9d168..d910673 100644 --- a/lib/Twig/NodeVisitor/Sandbox.php +++ b/lib/Twig/NodeVisitor/Sandbox.php @@ -20,6 +20,7 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface protected $inAModule = false; protected $tags; protected $filters; + protected $functions; /** * Called before child nodes are visited. @@ -35,6 +36,7 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface $this->inAModule = true; $this->tags = array(); $this->filters = array(); + $this->functions = array(); return $node; } elseif ($this->inAModule) { @@ -48,6 +50,11 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface $this->filters[] = $node->getNode('filter')->getAttribute('value'); } + // look for functions + if ($node instanceof Twig_Node_Expression_Function) { + $this->functions[] = $node->getNode('name')->getAttribute('name'); + } + // wrap print to check __toString() calls if ($node instanceof Twig_Node_Print) { return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag()); @@ -70,7 +77,7 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface if ($node instanceof Twig_Node_Module) { $this->inAModule = false; - return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags)); + return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions)); } return $node; diff --git a/lib/Twig/Sandbox/SecurityPolicy.php b/lib/Twig/Sandbox/SecurityPolicy.php index b85b42d..0d9a1a8 100644 --- a/lib/Twig/Sandbox/SecurityPolicy.php +++ b/lib/Twig/Sandbox/SecurityPolicy.php @@ -21,13 +21,15 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac protected $allowedFilters; protected $allowedMethods; protected $allowedProperties; + protected $allowedFunctions; - public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array()) + public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array()) { $this->allowedTags = $allowedTags; $this->allowedFilters = $allowedFilters; $this->allowedMethods = $allowedMethods; $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; } public function setAllowedTags(array $tags) @@ -50,7 +52,12 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac $this->allowedProperties = $properties; } - public function checkSecurity($tags, $filters) + public function setAllowedFunctions(array $functions) + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions) { foreach ($tags as $tag) { if (!in_array($tag, $this->allowedTags)) { @@ -63,6 +70,12 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter)); } } + + foreach ($functions as $function) { + if (!in_array($function, $this->allowedFunctions)) { + throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function)); + } + } } public function checkMethodAllowed($obj, $method) diff --git a/lib/Twig/Sandbox/SecurityPolicyInterface.php b/lib/Twig/Sandbox/SecurityPolicyInterface.php index b5e7272..2d5db86 100644 --- a/lib/Twig/Sandbox/SecurityPolicyInterface.php +++ b/lib/Twig/Sandbox/SecurityPolicyInterface.php @@ -17,7 +17,7 @@ */ interface Twig_Sandbox_SecurityPolicyInterface { - public function checkSecurity($tags, $filters); + public function checkSecurity($tags, $filters, $functions); public function checkMethodAllowed($obj, $method); diff --git a/test/Twig/Tests/Extension/SandboxTest.php b/test/Twig/Tests/Extension/SandboxTest.php index 34cc961..f76e72f 100644 --- a/test/Twig/Tests/Extension/SandboxTest.php +++ b/test/Twig/Tests/Extension/SandboxTest.php @@ -28,6 +28,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase '1_basic4' => '{{ obj.bar }}', '1_basic5' => '{{ obj }}', '1_basic6' => '{{ arr.obj }}', + '1_basic7' => '{{ cycle(["foo","bar"], 1) }}', '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', ); } @@ -79,6 +80,13 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase } catch (Twig_Sandbox_SecurityError $e) { } + $twig = $this->getEnvironment(true, array(), self::$templates); + try { + $twig->loadTemplate('1_basic7')->render(self::$params); + $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template'); + } catch (Twig_Sandbox_SecurityError $e) { + } + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('Object' => 'foo')); $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods'); @@ -93,6 +101,10 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('Object' => 'bar')); $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties'); + + $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle')); + $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions'); + } public function testSandboxLocallySetForAnInclude() @@ -129,11 +141,11 @@ EOF $this->assertEquals('

username

', $twig->loadTemplate('index')->render(array())); } - protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array()) + protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array()) { $loader = new Twig_Loader_Array($templates); $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options)); - $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties); + $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions); $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed)); return $twig; diff --git a/test/Twig/Tests/Node/SandboxedModuleTest.php b/test/Twig/Tests/Node/SandboxedModuleTest.php index 5b41117..209b2de 100644 --- a/test/Twig/Tests/Node/SandboxedModuleTest.php +++ b/test/Twig/Tests/Node/SandboxedModuleTest.php @@ -24,7 +24,7 @@ class Twig_Tests_Node_SandboxedModuleTest extends Twig_Tests_Node_TestCase $macros = new Twig_Node(); $filename = 'foo.twig'; $node = new Twig_Node_Module($body, $parent, $blocks, $macros, $filename); - $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper')); + $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle')); $this->assertEquals($body, $node->getNode('body')); $this->assertEquals($blocks, $node->getNode('blocks')); @@ -57,7 +57,7 @@ class Twig_Tests_Node_SandboxedModuleTest extends Twig_Tests_Node_TestCase $filename = 'foo.twig'; $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); - $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper')); + $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle')); $tests[] = array($node, <<env->getExtension('sandbox')->checkSecurity( array('upper'), - array('for') + array('for'), + array('cycle') ); } @@ -95,7 +96,7 @@ EOF $filename = 'foo.twig'; $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $filename); - $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper')); + $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle')); $tests[] = array($node, <<env->getExtension('sandbox')->checkSecurity( array('upper'), - array('for') + array('for'), + array('cycle') ); \$this->parent->checkSecurity();