.../src/Cache/NodeAccessViewGrantsCacheContext.php | 48 ++++--- .../Tests/NodeAccessViewGrantsCacheContextTest.php | 143 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 17 deletions(-) diff --git a/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php index b19dd6f..a074257 100644 --- a/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php +++ b/core/modules/node/src/Cache/NodeAccessViewGrantsCacheContext.php @@ -13,18 +13,27 @@ /** * Defines the node access view grants cache context service. * - * This allows for node access grants sensitive caching. + * This allows for node access grants-sensitive caching when viewing nodes. * * node_query_node_access_alter(). */ class NodeAccessViewGrantsCacheContext implements CacheContextInterface { /** - * Const - * @param \Drupal\Core\Session\AccountInterface $account + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface */ - public function __construct(AccountInterface $account) { - $this->account = $account; + protected $user; + + /** + * Constructs a new NodeAccessViewGrantsCacheContext service. + * + * @param \Drupal\Core\Session\AccountInterface $user + * The current user. + */ + public function __construct(AccountInterface $user) { + $this->user = $user; } /** @@ -38,25 +47,30 @@ public static function getLabel() { * {@inheritdoc} */ public function getContext() { - // If $account can bypass node access, or there are no node access modules, - // or the operation is 'view' and the $account has a global view grant - // (such as a view grant for node ID 0), we don't need to alter the query. - if ($this->account->hasPermission('bypass node access')) { - return ''; + // If the current user either: + // - can bypass node access + // - no node access modules exist (no hook_node_grants() implementations) + // - the operation is 'view' and the current user has a global view grant + // (such as a view grant for node ID 0) + // then we don't need to determine the exact node view grants for the + // current user. + if ($this->user->hasPermission('bypass node access')) { + return 'bypass'; } if (!count(\Drupal::moduleHandler()->getImplementations('node_grants'))) { - return ''; + return 'n/a'; } - if (node_access_view_all_nodes($this->account)) { - return ''; + + if (node_access_view_all_nodes($this->user)) { + return 'all'; } - $grants = node_access_grants('view', $this->account); - $grants_context = ''; + $grants = node_access_grants('view', $this->user); + $grants_context_parts = []; foreach ($grants as $realm => $gids) { - $grants_context = ';' . $realm . ':' . implode(',', $gids); + $grants_context_parts[] = $realm . ':' . implode(',', $gids); } - return $grants_context; + return implode(';', $grants_context_parts); } } diff --git a/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php b/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php index e69de29..c44dc62 100644 --- a/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php +++ b/core/modules/node/src/Tests/NodeAccessViewGrantsCacheContextTest.php @@ -0,0 +1,143 @@ +drupalCreateNode(); + $this->drupalCreateNode(); + $this->drupalCreateNode(); + $this->drupalCreateNode(); + + // Create user with simple node access permission. The 'node test view' + // permission is implemented and granted by the node_access_test module. + $this->accessUser = $this->drupalCreateUser(array('access content overview', 'access content', 'node test view')); + $this->noAccessUser = $this->drupalCreateUser(array('access content overview', 'access content')); + $this->noAccessUser2 = $this->drupalCreateUser(array('access content overview', 'access content')); + + $this->userMapping = [ + 1 => $this->root_user, + 2 => $this->accessUser, + 3 => $this->noAccessUser, + ]; + } + + /** + * Asserts that for each given user, the expected cache context is returned. + * + * @param array $expected + * Expected values, keyed by user ID, expected cache contexts as values. + */ + protected function assertCacheContext(array $expected) { + foreach ($expected as $uid => $context) { + if ($uid > 0) { + $this->drupalLogin($this->userMapping[$uid]); + } + $this->pass('Asserting cache context for user ' . $uid . '.'); + $this->assertIdentical($context, $this->container->get('cache_context.node_view_grants')->getContext()); + } + $this->drupalLogout(); + } + + /** + * Tests NodeAccessViewGrantsCacheContext::getContext(). + */ + public function testCacheContext() { + $this->assertCacheContext([ + 0 => 'all:0;node_access_test_author:0;node_access_all:0', + 1 => 'bypass', + 2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889', + 3 => 'all:0;node_access_test_author:3', + ]); + + // Grant view to all nodes (because nid = 0) for users in the + // 'node_access_all' realm. + $record = array( + 'nid' => 0, + 'gid' => 0, + 'realm' => 'node_access_all', + 'grant_view' => 1, + 'grant_update' => 0, + 'grant_delete' => 0, + ); + db_insert('node_access')->fields($record)->execute(); + + // Put user accessUser (uid 0) in the realm. + \Drupal::state()->set('node_access_test.no_access_uid', 0); + drupal_static_reset('node_access_view_all_nodes'); + $this->assertCacheContext([ + 0 => 'all', + 1 => 'bypass', + 2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889', + 3 => 'all:0;node_access_test_author:3', + ]); + + // Put user accessUser (uid 2) in the realm. + \Drupal::state()->set('node_access_test.no_access_uid', $this->accessUser->id()); + drupal_static_reset('node_access_view_all_nodes'); + $this->assertCacheContext([ + 0 => 'all:0;node_access_test_author:0', + 1 => 'bypass', + 2 => 'all', + 3 => 'all:0;node_access_test_author:3', + ]); + + // Put user noAccessUser (uid 3) in the realm. + \Drupal::state()->set('node_access_test.no_access_uid', $this->noAccessUser->id()); + drupal_static_reset('node_access_view_all_nodes'); + $this->assertCacheContext([ + 0 => 'all:0;node_access_test_author:0', + 1 => 'bypass', + 2 => 'all:0;node_access_test_author:2;node_access_test:8888,8889', + 3 => 'all', + ]); + + // Uninstall the node_access_test module + $this->container->get('module_installer')->uninstall(['node_access_test']); + drupal_static_reset('node_access_view_all_nodes'); + $this->assertCacheContext([ + 0 => 'n/a', + 1 => 'bypass', + 2 => 'n/a', + 3 => 'n/a', + ]); + } + +}