mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 10:56:38 +00:00
Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source
This commit is contained in:
committed by
Fabien Potencier
parent
02262dee80
commit
a18da1614a
@@ -15,6 +15,7 @@ use Twig\NodeVisitor\SandboxNodeVisitor;
|
|||||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||||
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
||||||
use Twig\Sandbox\SecurityPolicyInterface;
|
use Twig\Sandbox\SecurityPolicyInterface;
|
||||||
|
use Twig\Sandbox\SourcePolicyInterface;
|
||||||
use Twig\Source;
|
use Twig\Source;
|
||||||
use Twig\TokenParser\SandboxTokenParser;
|
use Twig\TokenParser\SandboxTokenParser;
|
||||||
|
|
||||||
@@ -23,11 +24,13 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
private $sandboxedGlobally;
|
private $sandboxedGlobally;
|
||||||
private $sandboxed;
|
private $sandboxed;
|
||||||
private $policy;
|
private $policy;
|
||||||
|
private $sourcePolicy;
|
||||||
|
|
||||||
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
|
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, SourcePolicyInterface $sourcePolicy = null)
|
||||||
{
|
{
|
||||||
$this->policy = $policy;
|
$this->policy = $policy;
|
||||||
$this->sandboxedGlobally = $sandboxed;
|
$this->sandboxedGlobally = $sandboxed;
|
||||||
|
$this->sourcePolicy = $sourcePolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTokenParsers()
|
public function getTokenParsers()
|
||||||
@@ -50,9 +53,9 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
$this->sandboxed = false;
|
$this->sandboxed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isSandboxed()
|
public function isSandboxed(Source $source = null)
|
||||||
{
|
{
|
||||||
return $this->sandboxedGlobally || $this->sandboxed;
|
return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isSandboxedGlobally()
|
public function isSandboxedGlobally()
|
||||||
@@ -60,6 +63,15 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
return $this->sandboxedGlobally;
|
return $this->sandboxedGlobally;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isSourceSandboxed(?Source $source): bool
|
||||||
|
{
|
||||||
|
if (null === $source || null === $this->sourcePolicy) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sourcePolicy->enableSandbox($source);
|
||||||
|
}
|
||||||
|
|
||||||
public function setSecurityPolicy(SecurityPolicyInterface $policy)
|
public function setSecurityPolicy(SecurityPolicyInterface $policy)
|
||||||
{
|
{
|
||||||
$this->policy = $policy;
|
$this->policy = $policy;
|
||||||
@@ -70,16 +82,16 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
return $this->policy;
|
return $this->policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkSecurity($tags, $filters, $functions)
|
public function checkSecurity($tags, $filters, $functions, Source $source = null)
|
||||||
{
|
{
|
||||||
if ($this->isSandboxed()) {
|
if ($this->isSandboxed($source)) {
|
||||||
$this->policy->checkSecurity($tags, $filters, $functions);
|
$this->policy->checkSecurity($tags, $filters, $functions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null)
|
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null)
|
||||||
{
|
{
|
||||||
if ($this->isSandboxed()) {
|
if ($this->isSandboxed($source)) {
|
||||||
try {
|
try {
|
||||||
$this->policy->checkMethodAllowed($obj, $method);
|
$this->policy->checkMethodAllowed($obj, $method);
|
||||||
} catch (SecurityNotAllowedMethodError $e) {
|
} catch (SecurityNotAllowedMethodError $e) {
|
||||||
@@ -93,7 +105,7 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
|
|
||||||
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null)
|
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null)
|
||||||
{
|
{
|
||||||
if ($this->isSandboxed()) {
|
if ($this->isSandboxed($source)) {
|
||||||
try {
|
try {
|
||||||
$this->policy->checkPropertyAllowed($obj, $property);
|
$this->policy->checkPropertyAllowed($obj, $property);
|
||||||
} catch (SecurityNotAllowedPropertyError $e) {
|
} catch (SecurityNotAllowedPropertyError $e) {
|
||||||
@@ -107,7 +119,7 @@ final class SandboxExtension extends AbstractExtension
|
|||||||
|
|
||||||
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
|
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
|
||||||
{
|
{
|
||||||
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
|
if ($this->isSandboxed($source) && \is_object($obj) && method_exists($obj, '__toString')) {
|
||||||
try {
|
try {
|
||||||
$this->policy->checkMethodAllowed($obj, '__toString');
|
$this->policy->checkMethodAllowed($obj, '__toString');
|
||||||
} catch (SecurityNotAllowedMethodError $e) {
|
} catch (SecurityNotAllowedMethodError $e) {
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ class CheckSecurityNode extends Node
|
|||||||
->indent()
|
->indent()
|
||||||
->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n")
|
->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n")
|
||||||
->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n")
|
->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n")
|
||||||
->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n")
|
->write(!$functions ? "[],\n" : "['".implode("', '", array_keys($functions))."'],\n")
|
||||||
|
->write("\$this->source\n")
|
||||||
->outdent()
|
->outdent()
|
||||||
->write(");\n")
|
->write(");\n")
|
||||||
->outdent()
|
->outdent()
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Twig.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Twig\Sandbox;
|
||||||
|
|
||||||
|
use Twig\Source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a class that can optionally enable the sandbox mode based on a template's Twig\Source.
|
||||||
|
*
|
||||||
|
* @author Yaakov Saxon
|
||||||
|
*/
|
||||||
|
interface SourcePolicyInterface
|
||||||
|
{
|
||||||
|
public function enableSandbox(Source $source): bool;
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ use Twig\Sandbox\SecurityNotAllowedMethodError;
|
|||||||
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
||||||
use Twig\Sandbox\SecurityNotAllowedTagError;
|
use Twig\Sandbox\SecurityNotAllowedTagError;
|
||||||
use Twig\Sandbox\SecurityPolicy;
|
use Twig\Sandbox\SecurityPolicy;
|
||||||
|
use Twig\Source;
|
||||||
|
|
||||||
class SandboxTest extends TestCase
|
class SandboxTest extends TestCase
|
||||||
{
|
{
|
||||||
@@ -440,7 +441,7 @@ EOF
|
|||||||
$twig_parent_first->load('1_childobj_childmethod')->render(self::$params);
|
$twig_parent_first->load('1_childobj_childmethod')->render(self::$params);
|
||||||
} catch (SecurityError $e) {
|
} catch (SecurityError $e) {
|
||||||
$this->fail('checkMethodAllowed is exiting prematurely after matching a parent class and not seeing a method allowed on a child class later in the list');
|
$this->fail('checkMethodAllowed is exiting prematurely after matching a parent class and not seeing a method allowed on a child class later in the list');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$twig_child_first->load('1_childobj_parentmethod')->render(self::$params);
|
$twig_child_first->load('1_childobj_parentmethod')->render(self::$params);
|
||||||
@@ -449,15 +450,50 @@ EOF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [])
|
protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [], $sourcePolicy = null)
|
||||||
{
|
{
|
||||||
$loader = new ArrayLoader($templates);
|
$loader = new ArrayLoader($templates);
|
||||||
$twig = new Environment($loader, array_merge(['debug' => true, 'cache' => false, 'autoescape' => false], $options));
|
$twig = new Environment($loader, array_merge(['debug' => true, 'cache' => false, 'autoescape' => false], $options));
|
||||||
$policy = new SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
$policy = new SecurityPolicy($tags, $filters, $methods, $properties, $functions);
|
||||||
$twig->addExtension(new SandboxExtension($policy, $sandboxed));
|
$twig->addExtension(new SandboxExtension($policy, $sandboxed, $sourcePolicy));
|
||||||
|
|
||||||
return $twig;
|
return $twig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSandboxSourcePolicyEnableReturningFalse()
|
||||||
|
{
|
||||||
|
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
|
||||||
|
public function enableSandbox(Source $source): bool
|
||||||
|
{
|
||||||
|
return '1_basic' != $source->getName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$this->assertEquals('FOO', $twig->load('1_basic')->render(self::$params));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSandboxSourcePolicyEnableReturningTrue()
|
||||||
|
{
|
||||||
|
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
|
||||||
|
public function enableSandbox(Source $source): bool
|
||||||
|
{
|
||||||
|
return '1_basic' === $source->getName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$this->expectException(SecurityError::class);
|
||||||
|
$twig->load('1_basic')->render([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSandboxSourcePolicyFalseDoesntOverrideOtherEnables()
|
||||||
|
{
|
||||||
|
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
|
||||||
|
public function enableSandbox(Source $source): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$this->expectException(SecurityError::class);
|
||||||
|
$twig->load('1_basic')->render([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParentClass
|
class ParentClass
|
||||||
|
|||||||
Reference in New Issue
Block a user