Issue #3213572 by larowlan, phenaproxima, kim.pepper, xjm, tim.plunkett: #date_time_callbacks and #date_date_callbacks bypass the TrustedCallbackInterface protections

merge-requests/862/head
xjm 2021-06-28 19:36:20 -05:00
parent dbcc1a3613
commit fae3763cca
3 changed files with 154 additions and 15 deletions

View File

@ -3,9 +3,13 @@
namespace Drupal\Core\Datetime\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Variable;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Security\DoTrustedCallbackTrait;
use Drupal\Core\Security\StaticTrustedCallbackHelper;
use Drupal\Core\Security\TrustedCallbackInterface;
/**
* Provides a datetime element.
@ -14,6 +18,8 @@ use Drupal\Core\Datetime\Entity\DateFormat;
*/
class Datetime extends DateElementBase {
use DoTrustedCallbackTrait;
/**
* @var \DateTimeInterface
*/
@ -267,9 +273,8 @@ class Datetime extends DateElementBase {
// Allows custom callbacks to alter the element.
if (!empty($element['#date_date_callbacks'])) {
foreach ($element['#date_date_callbacks'] as $callback) {
if (is_callable($callback)) {
$callback($element, $form_state, $date);
}
$message = sprintf('DateTime element #date_date_callbacks callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. Support for this callback implementation is deprecated in drupal:9.3.0 and will be removed in drupal:10.0.0. See https://www.drupal.org/node/3217966', Variable::callableToString($callback));
StaticTrustedCallbackHelper::callback($callback, [$element, $form_state, $date], $message, TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION);
}
}
}
@ -299,9 +304,8 @@ class Datetime extends DateElementBase {
// Allows custom callbacks to alter the element.
if (!empty($element['#date_time_callbacks'])) {
foreach ($element['#date_time_callbacks'] as $callback) {
if (function_exists($callback)) {
$callback($element, $form_state, $date);
}
$message = sprintf('DateTime element #date_time_callbacks callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. Support for this callback implementation is deprecated in drupal:9.3.0 and will be removed in drupal:10.0.0. See https://www.drupal.org/node/3217966', Variable::callableToString($callback));
StaticTrustedCallbackHelper::callback($callback, [$element, $form_state, $date], $message, TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION);
}
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\Core\Security;
/**
* Defines a class for performing trusted callbacks in a static context.
*/
class StaticTrustedCallbackHelper {
use DoTrustedCallbackTrait;
/**
* Performs a callback.
*
* @param callable $callback
* The callback to call. Note that callbacks which are objects and use the
* magic method __invoke() are not supported.
* @param array $args
* The arguments to pass the callback.
* @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_WARNING
* - 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.
*
* @return mixed
* 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.
*
* @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);
}
}

View File

@ -2,9 +2,11 @@
namespace Drupal\KernelTests\Core\Datetime;
use Drupal\Component\Utility\Variable;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\KernelTests\KernelTestBase;
/**
@ -12,14 +14,21 @@ use Drupal\KernelTests\KernelTestBase;
*
* @group Form
*/
class DatetimeElementFormTest extends KernelTestBase implements FormInterface {
class DatetimeElementFormTest extends KernelTestBase implements FormInterface, TrustedCallbackInterface {
/**
* The variable under test.
* Tracks whether a date-time date callback was executed.
*
* @var string
* @var bool
*/
protected $flag;
protected $dateCallbackExecuted = FALSE;
/**
* Tracks whether a date-time time callback was executed.
*
* @var bool
*/
protected $timeCallbackExecuted = FALSE;
/**
* Modules to enable.
@ -45,14 +54,35 @@ class DatetimeElementFormTest extends KernelTestBase implements FormInterface {
/**
* {@inheritdoc}
*/
public function datetimecallback($date) {
$this->flag = 'Date time callback called.';
public function datetimeDateCallbackTrusted() {
$this->dateCallbackExecuted = TRUE;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
public function datetimeDateCallback() {
$this->dateCallbackExecuted = TRUE;
}
/**
* {@inheritdoc}
*/
public function datetimeTimeCallbackTrusted() {
$this->timeCallbackExecuted = TRUE;
}
/**
* {@inheritdoc}
*/
public function datetimeTimeCallback() {
$this->timeCallbackExecuted = TRUE;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, string $date_callback = 'datetimeDateCallbackTrusted', string $time_callback = 'datetimeTimeCallbackTrusted') {
$form['datetime_element'] = [
'#title' => 'datelist test',
@ -63,7 +93,8 @@ class DatetimeElementFormTest extends KernelTestBase implements FormInterface {
'#date_date_element' => 'HTML Date',
'#date_time_element' => 'HTML Time',
'#date_increment' => 1,
'#date_date_callbacks' => [[$this, 'datetimecallback']],
'#date_date_callbacks' => [[$this, $date_callback]],
'#date_time_callbacks' => [[$this, $time_callback]],
];
// Element without specifying the default value.
@ -105,7 +136,54 @@ class DatetimeElementFormTest extends KernelTestBase implements FormInterface {
$form = \Drupal::formBuilder()->getForm($this);
$this->render($form);
$this->assertEquals(t('Date time callback called.'), $this->flag);
$this->assertTrue($this->dateCallbackExecuted);
$this->assertTrue($this->timeCallbackExecuted);
}
/**
* Tests that deprecations are raised if untrusted callbacks are used.
*
* @param string $date_callback
* Name of the callback to use for the date-time date callback.
* @param string $time_callback
* Name of the callback to use for the date-time time callback.
* @param string|null $expected_deprecation
* The expected deprecation message if a deprecation should be raised, or
* NULL if otherwise.
*
* @dataProvider providerUntrusted
* @group legacy
*/
public function testDatetimeElementUntrustedCallbacks(string $date_callback = 'datetimeDateCallbackTrusted', string $time_callback = 'datetimeTimeCallbackTrusted', string $expected_deprecation = NULL) : void {
$form = \Drupal::formBuilder()->getForm($this, $date_callback, $time_callback);
if ($expected_deprecation) {
$this->expectDeprecation($expected_deprecation);
}
$this->render($form);
$this->assertTrue($this->dateCallbackExecuted);
$this->assertTrue($this->timeCallbackExecuted);
}
/**
* Data provider for ::testDatetimeElementUntrustedCallbacks().
*
* @return string[][]
* Test cases.
*/
public function providerUntrusted() : array {
return [
'untrusted date' => [
'datetimeDateCallback',
'datetimeTimeCallbackTrusted',
sprintf('DateTime element #date_date_callbacks callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. Support for this callback implementation is deprecated in drupal:9.3.0 and will be removed in drupal:10.0.0. See https://www.drupal.org/node/3217966', Variable::callableToString([$this, 'datetimeDateCallback'])),
],
'untrusted time' => [
'datetimeDateCallbackTrusted',
'datetimeTimeCallback',
sprintf('DateTime element #date_time_callbacks callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. Support for this callback implementation is deprecated in drupal:9.3.0 and will be removed in drupal:10.0.0. See https://www.drupal.org/node/3217966', Variable::callableToString([$this, 'datetimeTimeCallback'])),
],
];
}
/**
@ -125,4 +203,14 @@ class DatetimeElementFormTest extends KernelTestBase implements FormInterface {
$this->assertEquals('UTC', $form['datetime_element']['#date_timezone']);
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return [
'datetimeDateCallbackTrusted',
'datetimeTimeCallbackTrusted',
];
}
}