diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 721ee9203cd3474ac4a478ba49e1a6e76cba4067..bb7034fd7252382113fdf10ff97b52adcb14b26b 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -651,6 +651,27 @@ public function renderPlaceholderFormAction() { ]; } + /** + * #lazy_builder callback; renders form CSRF token. + * + * @param string $placeholder + * A string containing a placeholder, matching the value of the form's + * #token. + * + * @return array + * A renderable array containing the CSRF token. + */ + public function renderFormTokenPlaceholder($placeholder) { + return [ + '#markup' => $this->csrfToken->get($placeholder), + '#cache' => [ + 'contexts' => [ + 'session', + ], + ], + ]; + } + /** * {@inheritdoc} */ @@ -725,15 +746,29 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { $form['#cache']['contexts'][] = 'user.roles:authenticated'; if ($user && $user->isAuthenticated()) { // Generate a public token based on the form id. - $form['#token'] = $form_id; + // Generates a placeholder based on the form ID. + $placeholder = 'form_token_placeholder_' . hash('crc32b', $form_id); + $form['#token'] = $placeholder; + $form['form_token'] = array( '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'), '#type' => 'token', - '#default_value' => $this->csrfToken->get($form['#token']), + '#default_value' => $placeholder, // Form processing and validation requires this value, so ensure the // submitted form value appears literally, regardless of custom #tree // and #parents being set elsewhere. '#parents' => array('form_token'), + // Instead of setting an actual CSRF token, we've set the placeholder + // in form_token's #default_value and #placeholder. These will be + // replaced at the very last moment. This ensures forms with a CSRF + // token don't have poor cacheability. + '#attached' => [ + 'placeholders' => [ + $placeholder => [ + '#lazy_builder' => ['form_builder:renderFormTokenPlaceholder', [$placeholder]] + ] + ] + ], '#cache' => [ 'max-age' => 0, ], diff --git a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php index 91ccfda1971d4c5ea6efb738dfe01c0b898acd6e..616c3a2384b47d312191cbcd1191b60197f8a721 100644 --- a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php +++ b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php @@ -35,6 +35,7 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase { */ protected $defaultCacheContexts = [ 'languages:language_interface', + 'session', 'theme', 'url.path', 'url.query_args', diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php index 21f2065346c6327fbd612ff23ca45679fedcd2ff..2c9a86733957e86daa7f5e62260c30867423c46a 100644 --- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -11,6 +11,9 @@ use Drupal\Core\Session\UserSession; use Drupal\comment\CommentInterface; use Drupal\system\Tests\Entity\EntityUnitTestBase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; /** * Tests the bubbling up of comment cache tags when using the Comment list @@ -35,6 +38,16 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase { protected function setUp() { parent::setUp(); + $session = new Session(); + + $request = Request::create('/'); + $request->setSession($session); + + /** @var RequestStack $stack */ + $stack = $this->container->get('request_stack'); + $stack->pop(); + $stack->push($request); + // Set the current user to one that can access comments. Specifically, this // user does not have access to the 'administer comments' permission, to // ensure only published comments are visible to the end user. diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/src/Tests/CommentTranslationUITest.php index 88c1d2fc1f01e5e5eb68d266cfa02ecf8e0202e8..26cfa9065006715424494156576f208bfcff0f17 100644 --- a/core/modules/comment/src/Tests/CommentTranslationUITest.php +++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php @@ -37,6 +37,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase { */ protected $defaultCacheContexts = [ 'languages:language_interface', + 'session', 'theme', 'timezone', 'url.query_args:_wrapper_format', diff --git a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php index 08b1d4ab2bb89bf4b518b70d32684b48dbe45cbd..ce9237cc31f4d9c69d9399877c30628a4c7de062 100644 --- a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php +++ b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php @@ -31,6 +31,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase { */ protected $defaultCacheContexts = [ 'languages:language_interface', + 'session', 'theme', 'url.path', 'url.query_args', diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php index 14043842613e0aa31e4e6f99c2101abcd0c86f4b..fa61fb38e7d34c9579b45787d2305bb4db93076a 100644 --- a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php +++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php @@ -20,7 +20,7 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase { /** * {inheritdoc} */ - protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.path', 'url.query_args', 'user.permissions', 'user.roles:authenticated']; + protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user.permissions', 'user.roles:authenticated']; /** * Modules to enable. diff --git a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php index b6855bae7292616410a9b4ecee339e46fcd5a5e9..386188f7347c8b974d46acf684da19672cd2ddb2 100644 --- a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php +++ b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php @@ -148,7 +148,7 @@ public function testRecentNodeBlock() { $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']); $this->drupalGet('node/add/article'); $this->assertText($label, 'Block was displayed on the node/add/article page.'); - $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.path', 'url.query_args', 'user', 'route']); + $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user', 'route']); $this->drupalGet('node/' . $node1->id()); $this->assertText($label, 'Block was displayed on the node/N when node is of type article.'); $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route', 'timezone']); diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php index 509a0d1afe166c130cc04476069bdea9a0ee49ca..490f5b1e92edc6590d1b46b62a1b2a405eff1a4d 100644 --- a/core/modules/node/src/Tests/NodeTranslationUITest.php +++ b/core/modules/node/src/Tests/NodeTranslationUITest.php @@ -26,6 +26,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase { */ protected $defaultCacheContexts = [ 'languages:language_interface', + 'session', 'theme', 'route', 'timezone', diff --git a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php index e42262fb5a067fbe0e826de01165788b56c0b3ee..9b09a37625d8cc2e4ab88923ef2e62f1acd627ce 100644 --- a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php +++ b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php @@ -21,7 +21,7 @@ class ShortcutTranslationUITest extends ContentTranslationUITestBase { /** * {inheritdoc} */ - protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user', 'url.path', 'url.query_args', 'url.site']; + protected $defaultCacheContexts = ['languages:language_interface', 'session', 'theme', 'user', 'url.path', 'url.query_args', 'url.site']; /** * Modules to enable. diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index e5f2963b17644129e8fd8735edbb6278791a255a..7bd51bce99dc65d307e5c7b3fae54350515545fd 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -783,10 +783,7 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated ->willReturnArgument(0); $this->csrfToken->expects($this->atLeastOnce()) ->method('validate') - ->will($this->returnValueMap([ - [$form_token, $form_id, $valid_token], - [$form_id, $form_id, $valid_token], - ])); + ->willReturn($valid_token); } $current_user = $this->prophesize(AccountInterface::class);