diff --git a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php index be5d75409fc1b143b1d5bd38050775e945e47901..9c4702ac46e85bd170d4f7433a5222cd28b902ca 100644 --- a/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/AccountPermissionsCacheContext.php @@ -57,7 +57,11 @@ public function getContext() { */ public function getCacheableMetadata() { $cacheable_metadata = new CacheableMetadata(); - $tags = []; + + // The permissions hash changes when: + // - a user is updated to have different roles; + $tags = ['user:' . $this->user->id()]; + // - a role is updated to have different permissions. foreach ($this->user->getRoles() as $rid) { $tags[] = "config:user.role.$rid"; } diff --git a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php index 94784f7cd12b1c231bb9fa3820aa1c041338968d..8143bed1fa929e271313b310ba4cbda2577803d3 100644 --- a/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/UserRolesCacheContext.php @@ -51,7 +51,7 @@ public function getContext($role = NULL) { * {@inheritdoc} */ public function getCacheableMetadata($role = NULL) { - return new CacheableMetadata(); + return (new CacheableMetadata())->setCacheTags(['user:' . $this->user->id()]); } } diff --git a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php index d304bf76e0933ef0de5c129997bb5495d8b428c8..767d2860591c8a1e8465e8be1f37213a5965ddaf 100644 --- a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php +++ b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php @@ -93,6 +93,11 @@ public function getCacheableMetadata($operation = NULL) { return $cacheable_metadata; } + // The node grants may change if the user is updated. (The max-age is set to + // zero below, but sites may override this cache context, and change it to a + // non-zero value. In such cases, this cache tag is needed for correctness.) + $cacheable_metadata->setCacheTags(['user:' . $this->user->id()]); + // If the site is using node grants, this cache context can not be // optimized. return $cacheable_metadata->setCacheMaxAge(0); diff --git a/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php index 050ef4ad4f87047750e73f87be16d2c6b3a53059..3bd46df7eb6161810c3a1d530d22820d4d919d96 100644 --- a/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php +++ b/core/modules/system/src/Tests/Cache/CacheContextOptimizationTest.php @@ -80,4 +80,45 @@ public function testUserPermissionCacheContextOptimization() { $this->assertEqual($output, 'this should be visible'); } + /** + * Ensures that 'user.roles' still works when it is optimized away. + */ + public function testUserRolesCacheContextOptimization() { + $root_user = $this->createUser(); + $this->assertEqual($root_user->id(), 1); + + $authenticated_user = $this->createUser(['administer permissions']); + $role = $authenticated_user->getRoles()[1]; + + $test_element = [ + '#cache' => [ + 'keys' => ['test'], + 'contexts' => ['user', 'user.roles'], + ], + ]; + \Drupal::service('account_switcher')->switchTo($authenticated_user); + $element = $test_element; + $element['#markup'] = 'content for authenticated users'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'content for authenticated users'); + + // Verify that the render caching is working so that other tests can be + // trusted. + $element = $test_element; + $element['#markup'] = 'this should not be visible'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'content for authenticated users'); + + // Even though the cache contexts have been optimized to only include 'user' + // cache context, the element should have been changed because 'user.roles' + // cache context defined a cache tag for user entity changes23, which should + // have bubbled up for the element when it was optimized away. + $authenticated_user->removeRole($role); + $authenticated_user->save(); + $element = $test_element; + $element['#markup'] = 'this should be visible'; + $output = \Drupal::service('renderer')->renderRoot($element); + $this->assertEqual($output, 'this should be visible'); + } + }