Commit bf38b66f authored by catch's avatar catch

Issue #2390691 by Wim Leers, Berdir, arlinsandbulte: Expose node grants as cache context

parent 79618421
......@@ -39,3 +39,8 @@ services:
arguments: ['@current_route_match']
tags:
- { name: page_cache_response_policy }
cache_context.node_view_grants:
class: Drupal\node\Cache\NodeAccessViewGrantsCacheContext
arguments: ['@current_user']
tags:
- { name: cache.context }
<?php
/**
* @file
* Contains \Drupal\Core\Cache\NodeAccessViewGrantsCacheContext.
*/
namespace Drupal\node\Cache;
use Drupal\Core\Cache\CacheContextInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines the node access view grants cache context service.
*
* This allows for node access grants-sensitive caching when viewing nodes.
*
* @see node_query_node_access_alter()
*/
class NodeAccessViewGrantsCacheContext implements CacheContextInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* Constructs a new NodeAccessViewGrantsCacheContext service.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(AccountInterface $user) {
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Content access view grants");
}
/**
* {@inheritdoc}
*/
public function getContext() {
// If the current user either:
// - can bypass node access
// - has a global view grant (such as a view grant for node ID 0) — note
// that this is automatically the case if no node access modules exist (no
// hook_node_grants() implementations)
// then we don't need to determine the exact node view grants for the
// current user.
if ($this->user->hasPermission('bypass node access') || node_access_view_all_nodes($this->user)) {
return 'all';
}
$grants = node_access_grants('view', $this->user);
$grants_context_parts = [];
foreach ($grants as $realm => $gids) {
$grants_context_parts[] = $realm . ':' . implode(',', $gids);
}
return implode(';', $grants_context_parts);
}
}
......@@ -88,16 +88,26 @@ public function access(NodeInterface $node, $operation, $langcode, AccountInterf
$query->condition($grants);
}
// Node grants currently don't have any cacheability metadata. Hopefully, we
// can add that in the future, which would allow this access check result to
// be cacheable. For now, this must remain marked as uncacheable, even when
// it is theoretically cacheable, because we don't have the necessary meta-
// data to know it for a fact.
// Only the 'view' node grant can currently be cached; the others currently
// don't have any cacheability metadata. Hopefully, we can add that in the
// future, which would allow this access check result to be cacheable in all
// cases. For now, this must remain marked as uncacheable, even when it is
// theoretically cacheable, because we don't have the necessary metadata to
// know it for a fact.
$set_cacheability = function (AccessResult $access_result) use ($operation) {
if ($operation === 'view') {
return $access_result->addCacheContexts(['cache_context.node_view_grants']);
}
else {
return $access_result->setCacheable(FALSE);
}
};
if ($query->execute()->fetchField()) {
return AccessResult::allowed()->setCacheable(FALSE);
return $set_cacheability(AccessResult::allowed());
}
else {
return AccessResult::forbidden()->setCacheable(FALSE);
return $set_cacheability(AccessResult::forbidden());
}
}
......
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeAccessViewGrantsCacheContextTest.
*/
namespace Drupal\node\Tests;
/**
* Tests the node access view grants cache context service.
*
* @group node
* @group Cache
*/
class NodeAccessViewGrantsCacheContextTest extends NodeTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node_access_test');
/**
* User with permission to view content.
*/
protected $accessUser;
/**
* User without permission to view content.
*/
protected $noAccessUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
node_access_rebuild();
// Create some content.
$this->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 => 'all',
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 => 'all',
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 => 'all',
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 => 'all',
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 => 'all',
1 => 'all',
2 => 'all',
3 => 'all',
]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment