class EntityAccessChecker in Drupal 9
Same name and namespace in other branches
- 8 core/modules/jsonapi/src/Access/EntityAccessChecker.php \Drupal\jsonapi\Access\EntityAccessChecker
Checks access to entities.
JSON:API needs to check access to every single entity type. Some entity types have non-standard access checking logic. This class centralizes entity access checking logic.
@internal JSON:API maintains no PHP API. The API is the HTTP API. This class may change at any time and could break any dependencies on it.
Hierarchy
- class \Drupal\jsonapi\Access\EntityAccessChecker
Expanded class hierarchy of EntityAccessChecker
See also
https://www.drupal.org/project/drupal/issues/3032787
2 files declare their use of EntityAccessChecker
- EntityResource.php in core/
modules/ jsonapi/ src/ Controller/ EntityResource.php - IncludeResolver.php in core/
modules/ jsonapi/ src/ IncludeResolver.php
1 string reference to 'EntityAccessChecker'
- jsonapi.services.yml in core/
modules/ jsonapi/ jsonapi.services.yml - core/modules/jsonapi/jsonapi.services.yml
1 service uses EntityAccessChecker
- jsonapi.entity_access_checker in core/
modules/ jsonapi/ jsonapi.services.yml - Drupal\jsonapi\Access\EntityAccessChecker
File
- core/
modules/ jsonapi/ src/ Access/ EntityAccessChecker.php, line 37
Namespace
Drupal\jsonapi\AccessView source
class EntityAccessChecker {
/**
* The JSON:API resource type repository.
*
* @var \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface
*/
protected $resourceTypeRepository;
/**
* The router.
*
* @var \Symfony\Component\Routing\RouterInterface
*/
protected $router;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The entity repository.
*
* @var \Drupal\Core\Entity\EntityRepositoryInterface
*/
protected $entityRepository;
/**
* The node revision access check service.
*
* This will be NULL unless the node module is installed.
*
* @var \Drupal\node\Access\NodeRevisionAccessCheck|null
*/
protected $nodeRevisionAccessCheck = NULL;
/**
* The media revision access check service.
*
* This will be NULL unless the media module is installed.
*
* @var \Drupal\media\Access\MediaRevisionAccessCheck|null
*/
protected $mediaRevisionAccessCheck = NULL;
/**
* The latest revision check service.
*
* This will be NULL unless the content_moderation module is installed. This
* is a temporary measure. JSON:API should not need to be aware of the
* Content Moderation module.
*
* @var \Drupal\content_moderation\Access\LatestRevisionCheck
*/
protected $latestRevisionCheck = NULL;
/**
* EntityAccessChecker constructor.
*
* @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository
* The JSON:API resource type repository.
* @param \Symfony\Component\Routing\RouterInterface $router
* The router.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository.
*/
public function __construct(ResourceTypeRepositoryInterface $resource_type_repository, RouterInterface $router, AccountInterface $account, EntityRepositoryInterface $entity_repository) {
$this->resourceTypeRepository = $resource_type_repository;
$this->router = $router;
$this->currentUser = $account;
$this->entityRepository = $entity_repository;
}
/**
* Sets the node revision access check service.
*
* This is only called when node module is installed.
*
* @param \Drupal\node\Access\NodeRevisionAccessCheck $node_revision_access_check
* The node revision access check service.
*/
public function setNodeRevisionAccessCheck(NodeRevisionAccessCheck $node_revision_access_check) {
$this->nodeRevisionAccessCheck = $node_revision_access_check;
}
/**
* Sets the media revision access check service.
*
* This is only called when media module is installed.
*
* @param \Drupal\media\Access\MediaRevisionAccessCheck $media_revision_access_check
* The media revision access check service.
*/
public function setMediaRevisionAccessCheck(MediaRevisionAccessCheck $media_revision_access_check) {
$this->mediaRevisionAccessCheck = $media_revision_access_check;
}
/**
* Sets the media revision access check service.
*
* This is only called when content_moderation module is installed.
*
* @param \Drupal\content_moderation\Access\LatestRevisionCheck $latest_revision_check
* The latest revision access check service provided by the
* content_moderation module.
*
* @see self::$latestRevisionCheck
*/
public function setLatestRevisionCheck(LatestRevisionCheck $latest_revision_check) {
$this->latestRevisionCheck = $latest_revision_check;
}
/**
* Get the object to normalize and the access based on the provided entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to test access for.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The account with which access should be checked. Defaults to
* the current user.
*
* @return \Drupal\jsonapi\JsonApiResource\ResourceObject|\Drupal\jsonapi\JsonApiResource\LabelOnlyResourceObject|\Drupal\jsonapi\Exception\EntityAccessDeniedHttpException
* The ResourceObject, a LabelOnlyResourceObject or an
* EntityAccessDeniedHttpException object if neither is accessible. All
* three possible return values carry the access result cacheability.
*/
public function getAccessCheckedResourceObject(EntityInterface $entity, AccountInterface $account = NULL) {
$account = $account ?: $this->currentUser;
$resource_type = $this->resourceTypeRepository
->get($entity
->getEntityTypeId(), $entity
->bundle());
$entity = $this->entityRepository
->getTranslationFromContext($entity, NULL, [
'operation' => 'entity_upcast',
]);
$access = $this
->checkEntityAccess($entity, 'view', $account);
$entity
->addCacheableDependency($access);
if (!$access
->isAllowed()) {
// If this is the default revision or the entity is not revisionable, then
// check access to the entity label. Revision support is all or nothing.
if (!$entity
->getEntityType()
->isRevisionable() || $entity
->isDefaultRevision()) {
$label_access = $entity
->access('view label', NULL, TRUE);
$entity
->addCacheableDependency($label_access);
if ($label_access
->isAllowed()) {
return LabelOnlyResourceObject::createFromEntity($resource_type, $entity);
}
$access = $access
->orIf($label_access);
}
return new EntityAccessDeniedHttpException($entity, $access, '/data', 'The current user is not allowed to GET the selected resource.');
}
return ResourceObject::createFromEntity($resource_type, $entity);
}
/**
* Checks access to the given entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity for which access should be evaluated.
* @param string $operation
* The entity operation for which access should be evaluated.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The account with which access should be checked. Defaults to
* the current user.
*
* @return \Drupal\Core\Access\AccessResultInterface|\Drupal\Core\Access\AccessResultReasonInterface
* The access check result.
*/
public function checkEntityAccess(EntityInterface $entity, $operation, AccountInterface $account) {
$access = $entity
->access($operation, $account, TRUE);
if ($entity
->getEntityType()
->isRevisionable()) {
$access = AccessResult::neutral()
->addCacheContexts([
'url.query_args:' . JsonApiSpec::VERSION_QUERY_PARAMETER,
])
->orIf($access);
if (!$entity
->isDefaultRevision()) {
assert($operation === 'view', 'JSON:API does not yet support mutable operations on revisions.');
$revision_access = $this
->checkRevisionViewAccess($entity, $account);
$access = $access
->andIf($revision_access);
// The revision access reason should trump the primary access reason.
if (!$access
->isAllowed()) {
$reason = $access instanceof AccessResultReasonInterface ? $access
->getReason() : '';
$access
->setReason(trim('The user does not have access to the requested version. ' . $reason));
}
}
}
return $access;
}
/**
* Checks access to the given revision entity.
*
* This should only be called for non-default revisions.
*
* There is no standardized API for revision access checking in Drupal core
* and this method shims that missing API.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The revised entity for which to check access.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The account with which access should be checked. Defaults to
* the current user.
*
* @return \Drupal\Core\Access\AccessResultInterface|\Drupal\Core\Access\AccessResultReasonInterface
* The access check result.
*
* @todo: remove when a generic revision access API exists in Drupal core, and
* also remove the injected "node" and "media" services.
* @see https://www.drupal.org/project/drupal/issues/2992833#comment-12818386
*/
protected function checkRevisionViewAccess(EntityInterface $entity, AccountInterface $account) {
assert($entity instanceof RevisionableInterface);
assert(!$entity
->isDefaultRevision(), 'It is not necessary to check revision access when the entity is the default revision.');
$entity_type = $entity
->getEntityType();
switch ($entity_type
->id()) {
case 'node':
assert($entity instanceof NodeInterface);
$access = AccessResult::allowedIf($this->nodeRevisionAccessCheck
->checkAccess($entity, $account, 'view'))
->cachePerPermissions()
->addCacheableDependency($entity);
break;
case 'media':
assert($entity instanceof MediaInterface);
$access = AccessResult::allowedIf($this->mediaRevisionAccessCheck
->checkAccess($entity, $account, 'view'))
->cachePerPermissions()
->addCacheableDependency($entity);
break;
default:
$reason = 'Only node and media revisions are supported by JSON:API.';
$reason .= ' For context, see https://www.drupal.org/project/drupal/issues/2992833#comment-12818258.';
$reason .= ' To contribute, see https://www.drupal.org/project/drupal/issues/2350939 and https://www.drupal.org/project/drupal/issues/2809177.';
$access = AccessResult::neutral($reason);
}
// Apply content_moderation's additional access logic.
// @see \Drupal\content_moderation\Access\LatestRevisionCheck::access()
if ($entity_type
->getLinkTemplate('latest-version') && $entity
->isLatestRevision() && isset($this->latestRevisionCheck)) {
// The latest revision access checker only expects to be invoked by the
// routing system, which makes it necessary to fake a route match.
$routes = $this->router
->getRouteCollection();
$resource_type = $this->resourceTypeRepository
->get($entity
->getEntityTypeId(), $entity
->bundle());
$route_name = sprintf('jsonapi.%s.individual', $resource_type
->getTypeName());
$route = $routes
->get($route_name);
$route
->setOption('_content_moderation_entity_type', 'entity');
$route_match = new RouteMatch($route_name, $route, [
'entity' => $entity,
], [
'entity' => $entity
->uuid(),
]);
$moderation_access_result = $this->latestRevisionCheck
->access($route, $route_match, $account);
$access = $access
->andIf($moderation_access_result);
}
return $access;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
EntityAccessChecker:: |
protected | property | The current user. | |
EntityAccessChecker:: |
protected | property | The entity repository. | |
EntityAccessChecker:: |
protected | property | The latest revision check service. | |
EntityAccessChecker:: |
protected | property | The media revision access check service. | |
EntityAccessChecker:: |
protected | property | The node revision access check service. | |
EntityAccessChecker:: |
protected | property | The JSON:API resource type repository. | |
EntityAccessChecker:: |
protected | property | The router. | |
EntityAccessChecker:: |
public | function | Checks access to the given entity. | |
EntityAccessChecker:: |
protected | function | Checks access to the given revision entity. | |
EntityAccessChecker:: |
public | function | Get the object to normalize and the access based on the provided entity. | |
EntityAccessChecker:: |
public | function | Sets the media revision access check service. | |
EntityAccessChecker:: |
public | function | Sets the media revision access check service. | |
EntityAccessChecker:: |
public | function | Sets the node revision access check service. | |
EntityAccessChecker:: |
public | function | EntityAccessChecker constructor. |