Issue #3213572 by larowlan, phenaproxima, kim.pepper, xjm, tim.plunkett: #date_time_callbacks and #date_date_callbacks bypass the TrustedCallbackInterface protections
parent
dbcc1a3613
commit
fae3763cca
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue