Unverified Commit 3acf8ea0 authored by Alex Pott's avatar Alex Pott
Browse files

feat: #2595805 Leverage attributes in Twig Sandbox Policy

By: @rgpublic
By: @geek-merlin
By: @alexpott
(cherry picked from commit c2e40edf)
parent 6c1e099e
Loading
Loading
Loading
Loading
Loading
+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 {}
+7 −0
Original line number Diff line number Diff line
@@ -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;

@@ -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)));
  }

+35 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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__;
  }

}