Commit 1208ddd9 authored by catch's avatar catch
Browse files

Issue #2428703 by Wim Leers: Add a 'user.permissions' cache context (was:...

Issue #2428703 by Wim Leers: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?")
parent c7fd064c
......@@ -74,6 +74,11 @@ services:
arguments: ['@current_user']
tags:
- { name: cache.context}
cache_context.user.permissions:
class: Drupal\Core\Cache\AccountPermissionsCacheContext
arguments: ['@current_user', '@user_permissions_hash_generator']
tags:
- { name: cache.context}
cache_context.user.roles:
class: Drupal\Core\Cache\UserRolesCacheContext
arguments: ['@current_user']
......@@ -1190,6 +1195,9 @@ services:
account_switcher:
class: Drupal\Core\Session\AccountSwitcher
arguments: ['@current_user', '@session_handler.write_safe']
user_permissions_hash_generator:
class: Drupal\Core\Session\PermissionsHashGenerator
arguments: ['@private_key', '@cache.default']
current_user:
class: Drupal\Core\Session\AccountProxy
session_configuration:
......
......@@ -132,7 +132,7 @@ public static function forbiddenIf($condition) {
/**
* Creates an allowed access result if the permission is present, neutral otherwise.
*
* Convenience method, checks the permission and calls ::cachePerRole().
* Checks the permission and adds a 'user.permissions' cache context.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check a permission.
......@@ -144,13 +144,13 @@ public static function forbiddenIf($condition) {
* isNeutral() will be TRUE.
*/
public static function allowedIfHasPermission(AccountInterface $account, $permission) {
return static::allowedIf($account->hasPermission($permission))->cachePerRole();
return static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
}
/**
* Creates an allowed access result if the permissions are present, neutral otherwise.
*
* Convenience method, checks the permissions and calls ::cachePerRole().
* Checks the permission and adds a 'user.permissions' cache contexts.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which to check permissions.
......@@ -185,7 +185,7 @@ public static function allowedIfHasPermissions(AccountInterface $account, array
}
}
return static::allowedIf($access)->cachePerRole();
return static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
}
/**
......@@ -334,12 +334,12 @@ public function setCacheMaxAge($max_age) {
}
/**
* Convenience method, adds the "user.roles" cache context.
* Convenience method, adds the "user.permissions" cache context.
*
* @return $this
*/
public function cachePerRole() {
$this->addCacheContexts(array('user.roles'));
public function cachePerPermissions() {
$this->addCacheContexts(array('user.permissions'));
return $this;
}
......
<?php
/**
* @file
* Contains \Drupal\Core\Cache\UserRolesCacheContext.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\PermissionsHashGeneratorInterface;
/**
* Defines the AccountPermissionsCacheContext service, for "per permission" caching.
*/
class AccountPermissionsCacheContext extends UserCacheContext implements CacheContextInterface{
/**
* The permissions hash generator.
*
* @var \Drupal\user\PermissionsHashInterface
*/
protected $permissionsHashGenerator;
/**
* Constructs a new UserCacheContext service.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Drupal\user\PermissionsHashInterface $permissions_hash_generator
* The permissions hash generator.
*/
public function __construct(AccountInterface $user, PermissionsHashGeneratorInterface $permissions_hash_generator) {
$this->user = $user;
$this->permissionsHashGenerator = $permissions_hash_generator;
}
/**
* {@inheritdoc}
*/
public static function getLabel() {
return t("Account's permissions");
}
/**
* {@inheritdoc}
*/
public function getContext() {
return 'ph.' . $this->permissionsHashGenerator->generate($this->user);
}
}
......@@ -2,21 +2,21 @@
/**
* @file
* Contains \Drupal\user\PermissionsHash.
* Contains \Drupal\Core\Session\PermissionsHashGenerator.
*/
namespace Drupal\user;
namespace Drupal\Core\Session;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\PrivateKey;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Site\Settings;
use Drupal\user\Entity\Role;
/**
* Generates and caches the permissions hash for a user.
*/
class PermissionsHash implements PermissionsHashInterface {
class PermissionsHashGenerator implements PermissionsHashGeneratorInterface {
/**
* The private key service.
......@@ -33,7 +33,7 @@ class PermissionsHash implements PermissionsHashInterface {
protected $cache;
/**
* Constructs a PermissionsHash object.
* Constructs a PermissionsHashGenerator object.
*
* @param \Drupal\Core\PrivateKey $private_key
* The private key service.
......@@ -69,7 +69,7 @@ public function generate(AccountInterface $account) {
/**
* Generates a hash that uniquely identifies the user's permissions.
*
* @param \Drupal\user\Entity\Role[] $roles
* @param string[] $roles
* The user's roles.
*
* @return string
......
......@@ -2,17 +2,15 @@
/**
* @file
* Contains Drupal\user\PermissionsHashInterface.
* Contains Drupal\Core\Session\PermissionsHashGeneratorInterface.
*/
namespace Drupal\user;
use Drupal\Core\Session\AccountInterface;
namespace Drupal\Core\Session;
/**
* Defines the user permissions hash interface.
* Defines the user permissions hash generator interface.
*/
interface PermissionsHashInterface {
interface PermissionsHashGeneratorInterface {
/**
* Generates a hash that uniquely identifies a user's permissions.
......
......@@ -28,21 +28,21 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
/** @var \Drupal\Core\Entity\EntityInterface|\Drupal\user\EntityOwnerInterface $entity */
if ($account->hasPermission('administer comments')) {
$access = AccessResult::allowed()->cachePerRole();
$access = AccessResult::allowed()->cachePerPermissions();
return ($operation != 'view') ? $access : $access->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
}
switch ($operation) {
case 'view':
return AccessResult::allowedIf($account->hasPermission('access comments') && $entity->isPublished())->cachePerRole()->cacheUntilEntityChanges($entity)
return AccessResult::allowedIf($account->hasPermission('access comments') && $entity->isPublished())->cachePerPermissions()->cacheUntilEntityChanges($entity)
->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
case 'update':
return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerRole()->cachePerUser()->cacheUntilEntityChanges($entity);
return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($entity);
default:
// No opinion.
return AccessResult::neutral()->cachePerRole();
return AccessResult::neutral()->cachePerPermissions();
}
}
......@@ -103,7 +103,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
$anonymous_contact = $commented_entity->get($entity->getFieldName())->getFieldDefinition()->getSetting('anonymous');
$admin_access = AccessResult::allowedIfHasPermission($account, 'administer comments');
$anonymous_access = AccessResult::allowedIf($entity->isNew() && $account->isAnonymous() && $anonymous_contact != COMMENT_ANONYMOUS_MAYNOT_CONTACT && $account->hasPermission('post comments'))
->cachePerRole()
->cachePerPermissions()
->cacheUntilEntityChanges($entity)
->cacheUntilEntityChanges($field_definition->getConfig($commented_entity->bundle()))
->cacheUntilEntityChanges($commented_entity);
......@@ -117,9 +117,9 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
// "access comments" permission and for the comment to be published. The
// mail field is hidden from non-admins.
$admin_access = AccessResult::allowedIf($account->hasPermission('administer comments') && $field_definition->getName() != 'hostname')
->cachePerRole();
->cachePerPermissions();
$anonymous_access = AccessResult::allowedIf($account->hasPermission('access comments') && (!$entity || $entity->isPublished()) && !in_array($field_definition->getName(), array('mail', 'hostname'), TRUE))
->cachePerRole();
->cachePerPermissions();
if ($entity) {
$anonymous_access->cacheUntilEntityChanges($entity);
}
......
......@@ -77,7 +77,7 @@ public function access(Route $route, AccountInterface $account) {
$mapper->hasTranslatable() &&
$source_language_access;
return AccessResult::allowedIf($access)->cachePerRole();
return AccessResult::allowedIf($access)->cachePerPermissions();
}
}
......@@ -71,7 +71,7 @@ public function access(UserInterface $user, AccountInterface $account) {
}
// User administrators should always have access to personal contact forms.
$access = AccessResult::neutral()->cachePerRole();
$access = AccessResult::neutral()->cachePerPermissions();
$permission_access = AccessResult::allowedIfHasPermission($account, 'administer users');
if ($permission_access->isAllowed()) {
return $access->orIf($permission_access);
......
......@@ -25,12 +25,12 @@ class ContactFormAccessControlHandler extends EntityAccessControlHandler {
public function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
if ($operation == 'view') {
// Do not allow access personal form via site-wide route.
return AccessResult::allowedIf($account->hasPermission('access site-wide contact form') && $entity->id() !== 'personal')->cachePerRole();
return AccessResult::allowedIf($account->hasPermission('access site-wide contact form') && $entity->id() !== 'personal')->cachePerPermissions();
}
elseif ($operation == 'delete' || $operation == 'update') {
// Do not allow the 'personal' form to be deleted, as it's used for
// the personal contact form.
return AccessResult::allowedIf($account->hasPermission('administer contact forms') && $entity->id() !== 'personal')->cachePerRole();
return AccessResult::allowedIf($account->hasPermission('administer contact forms') && $entity->id() !== 'personal')->cachePerPermissions();
}
return parent::checkAccess($entity, $operation, $langcode, $account);
......
......@@ -234,7 +234,7 @@ function content_translation_translate_access(EntityInterface $entity) {
$account = \Drupal::currentUser();
$condition = $entity instanceof ContentEntityInterface && !$entity->getUntranslated()->language()->isLocked() && \Drupal::languageManager()->isMultilingual() && $entity->isTranslatable() &&
($account->hasPermission('create content translations') || $account->hasPermission('update content translations') || $account->hasPermission('delete content translations'));
return AccessResult::allowedIf($condition)->cachePerRole()->cacheUntilEntityChanges($entity);
return AccessResult::allowedIf($condition)->cachePerPermissions()->cacheUntilEntityChanges($entity);
}
/**
......
......@@ -75,7 +75,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
if ($entity = $route_match->getParameter($entity_type_id)) {
if ($account->hasPermission('translate any entity')) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
$operation = $route->getRequirement('_access_content_translation_manage');
......@@ -95,7 +95,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
&& isset($languages[$source_language->getId()])
&& isset($languages[$target_language->getId()])
&& !isset($translations[$target_language->getId()]));
return AccessResult::allowedIf($is_new_translation)->cachePerRole()->cacheUntilEntityChanges($entity)
return AccessResult::allowedIf($is_new_translation)->cachePerPermissions()->cacheUntilEntityChanges($entity)
->andIf($handler->getTranslationAccess($entity, $operation));
case 'update':
......@@ -104,7 +104,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
$has_translation = isset($languages[$language->getId()])
&& $language->getId() != $entity->getUntranslated()->language()->getId()
&& isset($translations[$language->getId()]);
return AccessResult::allowedIf($has_translation)->cachePerRole()->cacheUntilEntityChanges($entity)
return AccessResult::allowedIf($has_translation)->cachePerPermissions()->cacheUntilEntityChanges($entity)
->andIf($handler->getTranslationAccess($entity, $operation));
}
}
......
......@@ -66,7 +66,7 @@ public function access(RouteMatchInterface $route_match, AccountInterface $accou
// Check "translate any entity" permission.
if ($account->hasPermission('translate any entity')) {
return AccessResult::allowed()->cachePerRole()->inheritCacheability($access);
return AccessResult::allowed()->cachePerPermissions()->inheritCacheability($access);
}
// Check per entity permission.
......
......@@ -208,7 +208,7 @@ public function getTranslationAccess(EntityInterface $entity, $op) {
if (!$current_user->hasPermission('translate any entity') && $permission_granularity = $entity_type->getPermissionGranularity()) {
$translate_permission = $current_user->hasPermission($permission_granularity == 'bundle' ? "translate {$entity->bundle()} {$entity->getEntityTypeId()}" : "translate {$entity->getEntityTypeId()}");
}
return AccessResult::allowedIf($translate_permission && $current_user->hasPermission("$op content translations"))->cachePerRole();
return AccessResult::allowedIf($translate_permission && $current_user->hasPermission("$op content translations"))->cachePerPermissions();
}
/**
......
......@@ -48,7 +48,7 @@ function field_test_entity_field_access($operation, FieldDefinitionInterface $fi
// Only grant view access to test_view_field fields when the user has
// 'view test_view_field content' permission.
if ($field_definition->getName() == 'test_view_field' && $operation == 'view') {
return AccessResult::forbiddenIf(!$account->hasPermission('view test_view_field content'))->cachePerRole();
return AccessResult::forbiddenIf(!$account->hasPermission('view test_view_field content'))->cachePerPermissions();
}
return AccessResult::allowed();
......
......@@ -124,7 +124,7 @@ function testFormatPermissions() {
// which they were granted access.
$fallback_format = entity_load('filter_format', filter_fallback_format());
$this->assertTrue($this->allowedFormat->access('use', $this->webUser), 'A regular user has access to use a text format they were granted access to.');
$this->assertEqual(AccessResult::allowed()->cachePerRole(), $this->allowedFormat->access('use', $this->webUser, TRUE), 'A regular user has access to use a text format they were granted access to.');
$this->assertEqual(AccessResult::allowed()->addCacheContexts(['user.permissions']), $this->allowedFormat->access('use', $this->webUser, TRUE), 'A regular user has access to use a text format they were granted access to.');
$this->assertFalse($this->disallowedFormat->access('use', $this->webUser), 'A regular user does not have access to use a text format they were not granted access to.');
$this->assertEqual(AccessResult::neutral(), $this->disallowedFormat->access('use', $this->webUser, TRUE)); //, 'A regular user does not have access to use a text format they were not granted access to.');
$this->assertTrue($fallback_format->access('use', $this->webUser), 'A regular user has access to use the fallback format.');
......
......@@ -59,11 +59,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
case 'update':
if (!$account->hasPermission('administer menu')) {
return AccessResult::neutral()->cachePerRole();
return AccessResult::neutral()->cachePerPermissions();
}
else {
// If there is a URL, this is an external link so always accessible.
$access = AccessResult::allowed()->cachePerRole()->cacheUntilEntityChanges($entity);
$access = AccessResult::allowed()->cachePerPermissions()->cacheUntilEntityChanges($entity);
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
// We allow access, but only if the link is accessible as well.
if (($url_object = $entity->getUrlObject()) && $url_object->isRouted()) {
......@@ -74,7 +74,7 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
}
case 'delete':
return AccessResult::allowedIf(!$entity->isNew() && $account->hasPermission('administer menu'))->cachePerRole()->cacheUntilEntityChanges($entity);
return AccessResult::allowedIf(!$entity->isNew() && $account->hasPermission('administer menu'))->cachePerPermissions()->cacheUntilEntityChanges($entity);
}
}
......
......@@ -336,18 +336,18 @@ function hook_node_access(\Drupal\node\NodeInterface $node, $op, \Drupal\Core\Se
case 'update':
if ($account->hasPermission('edit any ' . $type . ' content', $account)) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
else {
return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerRole()->cachePerUser()->cacheUntilEntityChanges($node);
return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
}
case 'delete':
if ($account->hasPermission('delete any ' . $type . ' content', $account)) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
else {
return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerRole()->cachePerUser()->cacheUntilEntityChanges($node);
return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
}
default:
......
......@@ -946,18 +946,18 @@ function node_node_access(NodeInterface $node, $op, $account) {
case 'update':
if ($account->hasPermission('edit any ' . $type . ' content', $account)) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
else {
return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerRole()->cachePerUser()->cacheUntilEntityChanges($node);
return AccessResult::allowedIf($account->hasPermission('edit own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
}
case 'delete':
if ($account->hasPermission('delete any ' . $type . ' content', $account)) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
else {
return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerRole()->cachePerUser()->cacheUntilEntityChanges($node);
return AccessResult::allowedIf($account->hasPermission('delete own ' . $type . ' content', $account) && ($account->id() == $node->getOwnerId()))->cachePerPermissions()->cachePerUser()->cacheUntilEntityChanges($node);
}
default:
......
......@@ -51,7 +51,7 @@ public function access(AccountInterface $account, NodeTypeInterface $node_type =
$access_control_handler = $this->entityManager->getAccessControlHandler('node');
// If checking whether a node of a particular type may be created.
if ($account->hasPermission('administer content types')) {
return AccessResult::allowed()->cachePerRole();
return AccessResult::allowed()->cachePerPermissions();
}
if ($node_type) {
return $access_control_handler->createAccess($node_type->id(), $account, [], TRUE);
......
......@@ -77,7 +77,7 @@ public function access(Route $route, AccountInterface $account, $node_revision =
$node = $this->nodeStorage->loadRevision($node_revision);
}
$operation = $route->getRequirement('_access_node_revision');
return AccessResult::allowedIf($node && $this->checkAccess($node, $account, $operation))->cachePerRole();
return AccessResult::allowedIf($node && $this->checkAccess($node, $account, $operation))->cachePerPermissions();
}
/**
......
Supports Markdown
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