Added sandbox support for functions
authorArnaud Le Blanc <arnaud.lb@gmail.com>
Sat, 25 Dec 2010 15:08:12 +0000 (16:08 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Thu, 30 Dec 2010 08:31:08 +0000 (09:31 +0100)
lib/Twig/Extension/Sandbox.php
lib/Twig/Node/SandboxedModule.php
lib/Twig/NodeVisitor/Sandbox.php
lib/Twig/Sandbox/SecurityPolicy.php
lib/Twig/Sandbox/SecurityPolicyInterface.php
test/Twig/Tests/Extension/SandboxTest.php
test/Twig/Tests/Node/SandboxedModuleTest.php

index bc049fe..c5cbbbf 100644 (file)
@@ -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);
         }
     }
 
index 23c4ba7..d03df81 100644 (file)
@@ -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")
         ;
index 3f9d168..d910673 100644 (file)
@@ -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;
index b85b42d..0d9a1a8 100644 (file)
@@ -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)
index b5e7272..2d5db86 100644 (file)
@@ -17,7 +17,7 @@
  */
 interface Twig_Sandbox_SecurityPolicyInterface
 {
-    public function checkSecurity($tags, $filters);
+    public function checkSecurity($tags, $filters, $functions);
 
     public function checkMethodAllowed($obj, $method);
 
index 34cc961..f76e72f 100644 (file)
@@ -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('<p>username</p>', $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;
index 5b41117..209b2de 100644 (file)
@@ -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, <<<EOF
 <?php
@@ -76,7 +76,8 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
     protected function checkSecurity() {
         \$this->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, <<<EOF
 <?php
@@ -124,7 +125,8 @@ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
     protected function checkSecurity() {
         \$this->env->getExtension('sandbox')->checkSecurity(
             array('upper'),
-            array('for')
+            array('for'),
+            array('cycle')
         );
 
         \$this->parent->checkSecurity();