Commit b78d0078 authored by catch's avatar catch
Browse files

task: #3081025 Remove technical debt and complication from when...

task: #3081025 Remove technical debt and complication from when doTrustedCallback() could either trigger errors or throw exceptions

By: xjm
By: dww
By: berdir
By: alexpott
By: longwave
By: samuel.mortenson
By: larowlan
(cherry picked from commit de58972a)
parent 921268a5
Loading
Loading
Loading
Loading
Loading
+1 −2
Changes for core/lib/Drupal/Core/Render/Renderer.php: 1 added line, 2 removed lines.
Original line number Diff line number Diff line
@@ -12,7 +12,6 @@
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormHelper;
use Drupal\Core\Render\Element\RenderCallbackInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Security\DoTrustedCallbackTrait;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Utility\CallableResolver;
@@ -892,7 +891,7 @@ protected function doCallback($callback_type, $callback, array $args) {
    // - All public methods on Render elements are considered trusted.
    // - Helper classes that contain only callback methods can implement this
    //   instead of TrustedCallbackInterface.
    return $this->doTrustedCallback($callable, $args, $message, TrustedCallbackInterface::THROW_EXCEPTION, RenderCallbackInterface::class);
    return $this->doTrustedCallback($callable, $args, $message, RenderCallbackInterface::class);
  }

  /**
+8 −18
Changes for core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php: 8 added lines, 18 removed lines.
Original line number Diff line number Diff line
@@ -20,11 +20,8 @@ trait DoTrustedCallbackTrait {
   * \Drupal\Core\Security\Attribute\TrustedCallback attribute, or be methods of
   * a class that implements
   * \Drupal\Core\Security\TrustedCallbackInterface or $extra_trusted_interface,
   * or be an anonymous function. If the callback is not trusted then whether or
   * not the callback is called and what type of error is thrown depends on
   * $error_type. To provide time for dependent code to use trusted callbacks
   * use TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION and then at a
   * later date change this to TrustedCallbackInterface::THROW_EXCEPTION.
   * or be an anonymous function. If the callback is not trusted an
   * UntrustedCallbackException is thrown.
   *
   * @param callable $callback
   *   The callback to call. Note that callbacks which are objects and use the
@@ -34,11 +31,6 @@ trait DoTrustedCallbackTrait {
   * @param string $message
   *   The error message if the callback is not trusted. If the message contains
   *   "%s" it will be replaced in with the resolved callback.
   * @param string $error_type
   *   (optional) The type of error to trigger. One of:
   *   - TrustedCallbackInterface::THROW_EXCEPTION
   *   - TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION
   *   Defaults to TrustedCallbackInterface::THROW_EXCEPTION.
   * @param string $extra_trusted_interface
   *   (optional) An additional interface that if implemented by the callback
   *   object means any public methods on that object are trusted.
@@ -47,13 +39,16 @@ trait DoTrustedCallbackTrait {
   *   The callback's return value.
   *
   * @throws \Drupal\Core\Security\UntrustedCallbackException
   *   Exception thrown if the callback is not trusted and $error_type equals
   *   TrustedCallbackInterface::THROW_EXCEPTION.
   *   Thrown if the callback is not trusted.
   *
   * @see \Drupal\Core\Security\Attribute\TrustedCallback
   * @see \Drupal\Core\Security\TrustedCallbackInterface
   */
  public function doTrustedCallback(callable $callback, array $args, $message, $error_type = TrustedCallbackInterface::THROW_EXCEPTION, $extra_trusted_interface = NULL) {
  public function doTrustedCallback(callable $callback, array $args, $message, $extra_trusted_interface = NULL) {
    if (func_num_args() > 4) {
      @trigger_error('Calling Drupal\\Core\\Security\\StaticTrustedCallbackHelper::callback() with 5 arguments is deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. See https://www.drupal.org/node/3627046', E_USER_DEPRECATED);
      $extra_trusted_interface = func_get_arg(4);
    }
    $object_or_classname = $callback;
    $safe_callback = FALSE;

@@ -95,13 +90,8 @@ public function doTrustedCallback(callable $callback, array $args, $message, $er
        $description .= '::' . $method_name;
      }
      $message = sprintf($message, $description);
      if ($error_type === TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION) {
        @trigger_error($message, E_USER_DEPRECATED);
      }
      else {
      throw new UntrustedCallbackException($message);
    }
    }

    // @todo Allow named arguments in https://www.drupal.org/node/3174150
    return call_user_func_array($callback, array_values($args));
+6 −7
Changes for core/lib/Drupal/Core/Security/StaticTrustedCallbackHelper.php: 6 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -20,11 +20,6 @@ class StaticTrustedCallbackHelper {
   * @param string $message
   *   The error message if the callback is not trusted. If the message contains
   *   "%s" it will be replaced in with the resolved callback.
   * @param string $error_type
   *   (optional) The type of error to trigger. One of:
   *   - TrustedCallbackInterface::THROW_EXCEPTION
   *   - TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION
   *   Defaults to TrustedCallbackInterface::THROW_EXCEPTION.
   * @param string $extra_trusted_interface
   *   (optional) An additional interface that if implemented by the callback
   *   object means any public methods on that object are trusted.
@@ -39,8 +34,12 @@ class StaticTrustedCallbackHelper {
   * @see \Drupal\Core\Security\TrustedCallbackInterface
   * @see \Drupal\Core\Security\DoTrustedCallbackTrait::doTrustedCallback()
   */
  public static function callback(callable $callback, array $args, string $message, $error_type = TrustedCallbackInterface::THROW_EXCEPTION, $extra_trusted_interface = NULL) {
    return (new static())->doTrustedCallback($callback, $args, $message, $error_type, $extra_trusted_interface);
  public static function callback(callable $callback, array $args, string $message, $extra_trusted_interface = NULL) {
    if (func_num_args() > 4) {
      @trigger_error('Calling Drupal\\Core\\Security\\StaticTrustedCallbackHelper::callback() with 5 arguments is deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. See https://www.drupal.org/node/3627046', E_USER_DEPRECATED);
      $extra_trusted_interface = func_get_arg(4);
    }
    return (new static())->doTrustedCallback($callback, $args, $message, $extra_trusted_interface);
  }

}
+10 −0
Changes for core/lib/Drupal/Core/Security/TrustedCallbackInterface.php: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -11,11 +11,21 @@ interface TrustedCallbackInterface {

  /**
   * Untrusted callbacks throw exceptions.
   *
   * @deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. There is
   *   no replacement. This is the default behavior.
   *
   * @see https://www.drupal.org/node/3627046
   */
  const THROW_EXCEPTION = 'exception';

  /**
   * Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
   *
   * @deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. There is
   *   no replacement. This behavior is no longer supported.
   *
   * @see https://www.drupal.org/node/3627046
   */
  const TRIGGER_SILENCED_DEPRECATION = 'silenced_deprecation';

+31 −16
Changes for core/tests/Drupal/Tests/Core/Security/DoTrustedCallbackTraitTest.php: 31 added lines, 16 removed lines.
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@

use Drupal\Core\Security\Attribute\TrustedCallback;
use Drupal\Core\Security\DoTrustedCallbackTrait;
use Drupal\Core\Security\StaticTrustedCallbackHelper;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Security\UntrustedCallbackException;
use Drupal\Tests\UnitTestCase;
@@ -29,7 +30,31 @@ class DoTrustedCallbackTraitTest extends UnitTestCase {
   */
  #[DataProvider('providerTestTrustedCallbacks')]
  public function testTrustedCallbacks(callable $callback, $extra_trusted_interface = NULL): void {
    $return = $this->doTrustedCallback($callback, [], '%s is not trusted', TrustedCallbackInterface::THROW_EXCEPTION, $extra_trusted_interface);
    $return = $this->doTrustedCallback($callback, [], '%s is not trusted', $extra_trusted_interface);
    $this->assertSame('test', $return);
  }

  /**
   * Tests trusted callback argument deprecation.
   *
   * @legacy-covers ::doTrustedCallback
   */
  #[DataProvider('providerTestTrustedCallbacks')]
  #[IgnoreDeprecations]
  public function testTrustedCallbacksWithDeprecatedArg(callable $callback, $extra_trusted_interface = NULL): void {
    $this->expectUserDeprecationMessage('Calling Drupal\\Core\\Security\\StaticTrustedCallbackHelper::callback() with 5 arguments is deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. See https://www.drupal.org/node/3627046');
    $return = $this->doTrustedCallback($callback, [], '%s is not trusted', 'exception', $extra_trusted_interface);
    $this->assertSame('test', $return);
  }

  /**
   * Tests StaticTrustedCallbackHelper::callback() argument deprecation.
   */
  #[DataProvider('providerTestTrustedCallbacks')]
  #[IgnoreDeprecations]
  public function testStaticTrustedCallbackHelperWithDeprecatedArg(callable $callback, $extra_trusted_interface = NULL): void {
    $this->expectUserDeprecationMessage('Calling Drupal\\Core\\Security\\StaticTrustedCallbackHelper::callback() with 5 arguments is deprecated in drupal:11.5.0 and is removed from drupal:13.0.0. See https://www.drupal.org/node/3627046');
    $return = StaticTrustedCallbackHelper::callback($callback, [], '%s is not trusted', 'exception', $extra_trusted_interface);
    $this->assertSame('test', $return);
  }

@@ -103,7 +128,7 @@ public static function providerTestTrustedCallbacks(): array {
  #[DataProvider('providerTestUntrustedCallbacks')]
  public function testUntrustedCallbacks(callable $callback, $extra_trusted_interface = NULL): void {
    $this->expectException(UntrustedCallbackException::class);
    $this->doTrustedCallback($callback, [], '%s is not trusted', TrustedCallbackInterface::THROW_EXCEPTION, $extra_trusted_interface);
    $this->doTrustedCallback($callback, [], '%s is not trusted', $extra_trusted_interface);
  }

  /**
@@ -151,9 +176,9 @@ public static function providerTestUntrustedCallbacks(): array {
  }

  /**
 * Tests exception.
 * Tests exceptions are thrown for untrusted callbacks.
 */
  #[DataProvider('errorTypeProvider')]
  #[DataProvider('providerTestException')]
  public function testException(callable $callback): void {
    $this->expectException(UntrustedCallbackException::class);
    $this->expectExceptionMessageIs('Drupal\Tests\Core\Security\UntrustedObject::callback is not trusted');
@@ -161,19 +186,9 @@ public function testException(callable $callback): void {
  }

  /**
 * Tests silenced deprecation.
 */
  #[DataProvider('errorTypeProvider')]
  #[IgnoreDeprecations]
  public function testSilencedDeprecation(callable $callback): void {
    $this->expectUserDeprecationMessage('Drupal\Tests\Core\Security\UntrustedObject::callback is not trusted');
    $this->doTrustedCallback($callback, [], '%s is not trusted', TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION);
  }

  /**
   * Data provider for tests of ::doTrustedCallback $error_type argument.
   * Data provider for testException().
   */
  public static function errorTypeProvider(): array {
  public static function providerTestException(): array {
    $tests['untrusted_object'] = [[new UntrustedObject(), 'callback']];
    $tests['untrusted_object_static_string'] = ['Drupal\Tests\Core\Security\UntrustedObject::callback'];
    $tests['untrusted_object_static_array'] = [[UntrustedObject::class, 'callback']];