diff --git a/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php index b0688db..3ca65f2 100644 --- a/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php +++ b/core/lib/Drupal/Core/Template/TwigSandboxPolicy.php @@ -29,20 +29,30 @@ public function checkPropertyAllowed($obj, $property) {} */ public function checkMethodAllowed($obj, $method) { if ($obj instanceof EntityInterface) { - $blacklist = [ - 'create', - 'delete', - 'save', + // Only allow idempotent methods to be called on Entity objects. + $whitelist = [ + 'id', + 'label', ]; - foreach ($blacklist as $no) { - foreach (['pre', 'post'] as $prefix) { - $blacklist[] = $prefix . $no; + foreach ($whitelist as $exact_match) { + if ($method == $exact_match) { + return; } } - if (in_array(strtolower($method), $blacklist)) { - throw new \Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); + $whitelist_patterns = [ + '/^get[A-Z\(]/', + '/^has[A-Z]/', + '/^is[A-Z]/', + ]; + foreach ($whitelist_patterns as $pattern) { + // If the method name starts with a whitelisted prefix, allow it. + if (preg_match($pattern, $method)) { + return; + } } + + throw new \Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); } } diff --git a/core/modules/node/templates/node.html.twig b/core/modules/node/templates/node.html.twig index 5ed0775..af59efd 100644 --- a/core/modules/node/templates/node.html.twig +++ b/core/modules/node/templates/node.html.twig @@ -4,12 +4,10 @@ * Default theme implementation to display a node. * * Available variables: - * - node: Full node entity. - * - id: The node ID. - * - bundle: The type of the node, for example, "page" or "article". - * - authorid: The user ID of the node author. - * - createdtime: Time the node was published formatted in Unix timestamp. - * - changedtime: Time the node was changed formatted in Unix timestamp. + * - node: The node entity with limited access to object properties and methods. + Only "getter" methods (method names starting with "get", "has", or "is") + and a few common attributes such as "id" and "label" are available. Calling + other methods (such as node.delete) will result in an exception. * - label: The title of the node. * - content: All node items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use @@ -68,7 +66,6 @@ */ #} - {{ title_prefix }} {% if not page %} diff --git a/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php b/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php index 827bf65..46ed159 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigSandboxTest.php @@ -31,30 +31,59 @@ public function testEntityDangerousMethods() { $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); - $catched = FALSE; + $caught = FALSE; try { $twig->render('{{ entity.delete }}', ['entity' => $entity]); } catch (\Twig_Sandbox_SecurityError $e) { - $catched = TRUE; + $caught = TRUE; } - $this->assertTrue($catched, '\Twig_sandbox_security exception was thrown when entity was tried to be removed.'); + $this->assertTrue($caught, '\Twig_sandbox_security exception was thrown when entity was tried to be removed.'); - $catched = FALSE; + $caught = FALSE; try { $twig->render('{{ entity.save }}', ['entity' => $entity]); } catch (\Twig_Sandbox_SecurityError $e) { - $catched = TRUE; + $caught = TRUE; } - $this->assertTrue($catched, '\Twig_sandbox_security exception was thrown when entity was tried to be saved.'); + $this->assertTrue($caught, '\Twig_sandbox_security exception was thrown when entity was tried to be saved.'); - $catched = FALSE; + $caught = FALSE; try { $twig->render('{{ entity.create }}', ['entity' => $entity]); } catch (\Twig_Sandbox_SecurityError $e) { - $catched = TRUE; + $caught = TRUE; } - $this->assertTrue($catched, '\Twig_sandbox_security exception was thrown when new entity was tried to be created.'); + $this->assertTrue($caught, '\Twig_sandbox_security exception was thrown when new entity was tried to be created.'); + } + + public function testEntitySafeMethods() { + $loader = new \Twig_Loader_String(); + $twig = new \Twig_Environment($loader); + $policy = new TwigSandboxPolicy(); + $sandbox = new \Twig_Extension_Sandbox($policy, TRUE); + $twig->addExtension($sandbox); + + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->atLeastOnce()) + ->method('hasLinkTemplate') + ->with('test') + ->willReturn(TRUE); + $result = $twig->render('{{ entity.hasLinkTemplate("test") }}', ['entity' => $entity]); + $this->assertTrue((bool)$result, 'Sandbox policy allows has* functions to be called.'); + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->atLeastOnce()) + ->method('isNew') + ->willReturn(TRUE); + $result = $twig->render('{{ entity.isNew }}', ['entity' => $entity]); + $this->assertTrue((bool)$result, 'Sandbox policy allows is* functions to be called.'); + + $entity = $this->getMock('Drupal\Core\Entity\EntityInterface'); + $entity->expects($this->atLeastOnce()) + ->method('getEntityType') + ->willReturn('test'); + $result = $twig->render('{{ entity.getEntityType }}', ['entity' => $entity]); + $this->assertEquals($result, 'test', 'Sandbox policy allows get* functions to be called.'); } } diff --git a/core/themes/bartik/templates/node.html.twig b/core/themes/bartik/templates/node.html.twig index 25144bf..1d1afd3 100644 --- a/core/themes/bartik/templates/node.html.twig +++ b/core/themes/bartik/templates/node.html.twig @@ -4,12 +4,10 @@ * Bartik's theme implementation to display a node. * * Available variables: - * - node: Full node entity. - * - id: The node ID. - * - bundle: The type of the node, for example, "page" or "article". - * - authorid: The user ID of the node author. - * - createdtime: Time the node was published formatted in Unix timestamp. - * - changedtime: Time the node was changed formatted in Unix timestamp. + * - node: The node entity with limited access to object properties and methods. + Only "getter" methods (method names starting with "get", "has", or "is") + and a few common attributes such as "id" and "label" are available. Calling + other methods (such as node.delete) will result in an exception. * - label: The title of the node. * - content: All node items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use @@ -64,7 +62,7 @@ {% set classes = [ 'node', - 'node--type-' ~ node.bundle|clean_class, + 'node--type-' ~ node.getType()|clean_class, node.isPromoted() ? 'node--promoted', node.isSticky() ? 'node--sticky', not node.isPublished() ? 'node--unpublished', diff --git a/core/themes/classy/templates/content/node.html.twig b/core/themes/classy/templates/content/node.html.twig index 5d746a6..cbc1899 100644 --- a/core/themes/classy/templates/content/node.html.twig +++ b/core/themes/classy/templates/content/node.html.twig @@ -4,12 +4,10 @@ * Theme override to display a node. * * Available variables: - * - node: Full node entity. - * - id: The node ID. - * - bundle: The type of the node, for example, "page" or "article". - * - authorid: The user ID of the node author. - * - createdtime: Time the node was published formatted in Unix timestamp. - * - changedtime: Time the node was changed formatted in Unix timestamp. + * - node: The node entity with limited access to object properties and methods. + Only "getter" methods (method names starting with "get", "has", or "is") + and a few common attributes such as "id" and "label" are available. Calling + other methods (such as node.delete) will result in an exception. * - label: The title of the node. * - content: All node items. Use {{ content }} to print them all, * or print a subset such as {{ content.field_example }}. Use @@ -68,7 +66,7 @@ {% set classes = [ 'node', - 'node--type-' ~ node.bundle|clean_class, + 'node--type-' ~ node.getType()|clean_class, node.isPromoted() ? 'node--promoted', node.isSticky() ? 'node--sticky', not node.isPublished() ? 'node--unpublished', diff --git a/core/themes/classy/templates/content/taxonomy-term.html.twig b/core/themes/classy/templates/content/taxonomy-term.html.twig index 7e0446e..4284950 100644 --- a/core/themes/classy/templates/content/taxonomy-term.html.twig +++ b/core/themes/classy/templates/content/taxonomy-term.html.twig @@ -26,7 +26,7 @@ {% set classes = [ 'taxonomy-term', - 'vocabulary-' ~ term.bundle|clean_class, + 'vocabulary-' ~ term.getType()|clean_class, ] %}