Loading core/lib/Drupal/Core/Template/Attribute/TwigAllowed.php 0 → 100644 +21 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Template\Attribute; /** * Allow twig access to methods. * * Twig "sandboxes" templates to prevent them from * - having unwanted side effects (like calling node.delete()) * - getting access to information outside the sandbox * This access attribute must only be given to methods that can not break the * sandbox. * * Note that Twig is not only used in templating, but also as a templating and * configuration language in core (e.g. views) and custom modules, which makes * its power available to site builders and maybe even site users with proper * permissions. */ #[\Attribute(\Attribute::TARGET_METHOD)] final class TwigAllowed {} core/lib/Drupal/Core/Template/TwigSandboxPolicy.php +7 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\Core\Template; use Drupal\Core\Site\Settings; use Drupal\Core\Template\Attribute\TwigAllowed; use Twig\Sandbox\SecurityError; use Twig\Sandbox\SecurityPolicyInterface; Loading Loading @@ -101,6 +102,12 @@ public function checkMethodAllowed($obj, $method): void { } } // Allow the method if it has a TwigAllowed attribute. $reflectionMethod = new \ReflectionMethod($obj, $method); if ($reflectionMethod->getAttributes(TwigAllowed::class)) { return; } throw new SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); } Loading core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php +35 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\Tests\Core\Template; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute\TwigAllowed; use Drupal\Core\Template\Loader\StringLoader; use Drupal\Core\Template\TwigSandboxPolicy; use Drupal\Tests\Core\Entity\ContentEntityBaseMockableClass; Loading Loading @@ -156,9 +157,43 @@ public function testUrlSafeMethods(): void { $this->assertEquals('http://kittens.cat/are/cute', $result, 'Sandbox policy allows toString() to be called.'); } /** * Tests that method with TwigAllowed attribute is allowed. */ public function testTwigMethodAttributeAllowed(): void { $object = new AttributeAllowTestClass(); $result = $this->twig->render('{{ object.allowed() }}', ['object' => $object]); $this->assertTrue((bool) $result, 'TwigAllowed attribute allows method to be called.'); } /** * Tests that method without TwigAllowed attribute is not allowed. */ public function testTwigMethodAttributeNotAllowed(): void { $object = new AttributeAllowTestClass(); $this->expectException(SecurityError::class); $this->twig->render('{{ object.notAllowed() }}', ['object' => $object]); } } /** * Test class for HTML attributes collector, sanitizer, and renderer. */ class TestAttribute extends Attribute {} /** * Test class for TwigAllowed attribute. */ class AttributeAllowTestClass { #[TwigAllowed] public function allowed(): string { return __METHOD__; } public function notAllowed(): string { return __METHOD__; } } Loading
core/lib/Drupal/Core/Template/Attribute/TwigAllowed.php 0 → 100644 +21 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Template\Attribute; /** * Allow twig access to methods. * * Twig "sandboxes" templates to prevent them from * - having unwanted side effects (like calling node.delete()) * - getting access to information outside the sandbox * This access attribute must only be given to methods that can not break the * sandbox. * * Note that Twig is not only used in templating, but also as a templating and * configuration language in core (e.g. views) and custom modules, which makes * its power available to site builders and maybe even site users with proper * permissions. */ #[\Attribute(\Attribute::TARGET_METHOD)] final class TwigAllowed {}
core/lib/Drupal/Core/Template/TwigSandboxPolicy.php +7 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ namespace Drupal\Core\Template; use Drupal\Core\Site\Settings; use Drupal\Core\Template\Attribute\TwigAllowed; use Twig\Sandbox\SecurityError; use Twig\Sandbox\SecurityPolicyInterface; Loading Loading @@ -101,6 +102,12 @@ public function checkMethodAllowed($obj, $method): void { } } // Allow the method if it has a TwigAllowed attribute. $reflectionMethod = new \ReflectionMethod($obj, $method); if ($reflectionMethod->getAttributes(TwigAllowed::class)) { return; } throw new SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); } Loading
core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php +35 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\Tests\Core\Template; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\Attribute\TwigAllowed; use Drupal\Core\Template\Loader\StringLoader; use Drupal\Core\Template\TwigSandboxPolicy; use Drupal\Tests\Core\Entity\ContentEntityBaseMockableClass; Loading Loading @@ -156,9 +157,43 @@ public function testUrlSafeMethods(): void { $this->assertEquals('http://kittens.cat/are/cute', $result, 'Sandbox policy allows toString() to be called.'); } /** * Tests that method with TwigAllowed attribute is allowed. */ public function testTwigMethodAttributeAllowed(): void { $object = new AttributeAllowTestClass(); $result = $this->twig->render('{{ object.allowed() }}', ['object' => $object]); $this->assertTrue((bool) $result, 'TwigAllowed attribute allows method to be called.'); } /** * Tests that method without TwigAllowed attribute is not allowed. */ public function testTwigMethodAttributeNotAllowed(): void { $object = new AttributeAllowTestClass(); $this->expectException(SecurityError::class); $this->twig->render('{{ object.notAllowed() }}', ['object' => $object]); } } /** * Test class for HTML attributes collector, sanitizer, and renderer. */ class TestAttribute extends Attribute {} /** * Test class for TwigAllowed attribute. */ class AttributeAllowTestClass { #[TwigAllowed] public function allowed(): string { return __METHOD__; } public function notAllowed(): string { return __METHOD__; } }