Skip to content

Commit d2bff1c

Browse files
ndrake0027fubhy
authored andcommitted
Add entity access checks to other entity loads. (#895)
1 parent ec0c6ee commit d2bff1c

6 files changed

Lines changed: 136 additions & 30 deletions

File tree

doc/producers/built-in.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ This list includes all available data producers inside GraphQL to this day and b
2222
* **Entity id** (`entity_id`) : Returns the entity identifier.
2323
* **Entity label** (`entity_label`) : Returns the entity label given an entity such as a node.
2424
* **Entity language** (`entity_language`) : Returns the entity language.
25-
* **Entity load** (`entity_load`) : Loads a single entity given parameters like type, id (optional), language (optional) or bundles (optional)
26-
* **Entity load multiple** (`entity_load_multiple`) : Loads a multiple entities given parameters like type, ids (optional), language (optional) or bundles (optional)
27-
* **Entity load by uuid** (`entity_load_by_uuid`) : Loads a single entity by Uuid.
25+
* **Entity load** (`entity_load`) : Loads a single entity given parameters like type, id (optional), language (optional) bundles (optional), access (TRUE by default), access_user (current user by default) or access_operation ("view" by default).
26+
* **Entity load multiple** (`entity_load_multiple`) : Loads a multiple entities given parameters like type, ids (optional), language (optional), bundles (optional), access (TRUE by default), access_user (current user by default) or access_operation ("view" by default)
27+
* **Entity load by uuid** (`entity_load_by_uuid`) : Loads a single entity by Uuid with access control parameters: access (TRUE by default), access_user (current user by default) or access_operation ("view" by default).
2828
* **Entity owner** (`entity_owner`) : Returns the entity owner given an entity such as a node.
2929
* **Entity published** (`entity_published`) : Returns whether the entity is published given an entity such as a node.
3030
* **Entity rendered** (`entity_rendered`) : Returns the rendered entity.
31-
* **Entity translation** (`entity_translation`) : Returns the translated entity.
32-
* **Entity translations** (`entity_translations`) : Returns all available translations of an entity.
31+
* **Entity translation** (`entity_translation`) : Returns the translated entity with access control parameters: access (TRUE by default), access_user (current user by default) or access_operation ("view" by default).
32+
* **Entity translations** (`entity_translations`) : Returns all available translations of an entity with access control parameters: access (TRUE by default), access_user (current user by default) or access_operation ("view" by default).
3333
* **Entity type** (`entity_type_id`) : Returns an entity's entity type.
3434
* **Entity url** (`entity_url`) : Returns the entity's url.
3535
* **Entity uuid** (`entity_uuid`) : Returns the entity's uuid.

src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,18 @@
4040
* ),
4141
* "access" = @ContextDefinition("boolean",
4242
* label = @Translation("Check access"),
43-
* required = FALSE
43+
* required = FALSE,
44+
* default_value = TRUE
4445
* ),
4546
* "access_user" = @ContextDefinition("entity:user",
4647
* label = @Translation("User"),
47-
* required = FALSE
48+
* required = FALSE,
49+
* default_value = NULL
4850
* ),
4951
* "access_operation" = @ContextDefinition("string",
5052
* label = @Translation("Operation"),
51-
* required = FALSE
53+
* required = FALSE,
54+
* default_value = "view"
5255
* )
5356
* }
5457
* )
@@ -136,7 +139,7 @@ public function __construct(
136139
*
137140
* @return \GraphQL\Deferred
138141
*/
139-
public function resolve($type, $id = NULL, $language = NULL, $bundles = NULL, $access = TRUE, AccountInterface $accessUser = NULL, string $accessOperation = 'view', FieldContext $context) {
142+
public function resolve($type, $id, $language, $bundles, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation, FieldContext $context) {
140143
$resolver = $this->entityBuffer->add($type, $id);
141144

142145
return new Deferred(function () use ($type, $id, $language, $bundles, $resolver, $context, $access, $accessUser, $accessOperation) {

src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Drupal\Core\Entity\EntityTypeManagerInterface;
77
use Drupal\Core\Entity\TranslatableInterface;
88
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9+
use Drupal\Core\Session\AccountInterface;
910
use Drupal\graphql\GraphQL\Buffers\EntityUuidBuffer;
1011
use Drupal\graphql\GraphQL\Execution\FieldContext;
1112
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
@@ -36,6 +37,21 @@
3637
* label = @Translation("Entity bundle(s)"),
3738
* multiple = TRUE,
3839
* required = FALSE
40+
* ),
41+
* "access" = @ContextDefinition("boolean",
42+
* label = @Translation("Check access"),
43+
* required = FALSE,
44+
* default_value = TRUE
45+
* ),
46+
* "access_user" = @ContextDefinition("entity:user",
47+
* label = @Translation("User"),
48+
* required = FALSE,
49+
* default_value = NULL
50+
* ),
51+
* "access_operation" = @ContextDefinition("string",
52+
* label = @Translation("Operation"),
53+
* required = FALSE,
54+
* default_value = "view"
3955
* )
4056
* }
4157
* )
@@ -116,14 +132,17 @@ public function __construct(
116132
* @param $uuid
117133
* @param array|string $language
118134
* @param array|string $bundles
135+
* @param bool $access
136+
* @param \Drupal\Core\Session\AccountInterface|NULL $accessUser
137+
* @param string $accessOperation
119138
* @param \Drupal\graphql\GraphQL\Execution\FieldContext $context
120139
*
121140
* @return \GraphQL\Deferred
122141
*/
123-
public function resolve($type, $uuid, $language = NULL, $bundles = NULL, FieldContext $context) {
142+
public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation, FieldContext $context) {
124143
$resolver = $this->entityBuffer->add($type, $uuid);
125144

126-
return new Deferred(function () use ($type, $language, $bundles, $resolver, $context) {
145+
return new Deferred(function () use ($type, $language, $bundles, $resolver, $context, $access, $accessUser, $accessOperation) {
127146
if (!$entity = $resolver()) {
128147
// If there is no entity with this id, add the list cache tags so that the
129148
// cache entry is purged whenever a new entity of this type is saved.
@@ -134,6 +153,17 @@ public function resolve($type, $uuid, $language = NULL, $bundles = NULL, FieldCo
134153
return NULL;
135154
}
136155

156+
// Check if the passed user (or current user if none is passed) has access
157+
// to the entity, if not return NULL.
158+
if ($access) {
159+
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
160+
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
161+
$context->addCacheableDependency($accessResult);
162+
if ($accessResult->isForbidden()) {
163+
return NULL;
164+
}
165+
}
166+
137167
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
138168
// If the entity is not among the allowed bundles, don't return it.
139169
$context->addCacheableDependency($entity);

src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Drupal\Core\Entity\EntityTypeManagerInterface;
77
use Drupal\Core\Entity\TranslatableInterface;
88
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9+
use Drupal\Core\Session\AccountInterface;
910
use Drupal\graphql\GraphQL\Buffers\EntityBuffer;
1011
use Drupal\graphql\GraphQL\Execution\FieldContext;
1112
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
@@ -37,6 +38,21 @@
3738
* label = @Translation("Entity bundle(s)"),
3839
* multiple = TRUE,
3940
* required = FALSE
41+
* ),
42+
* "access" = @ContextDefinition("boolean",
43+
* label = @Translation("Check access"),
44+
* required = FALSE,
45+
* default_value = TRUE
46+
* ),
47+
* "access_user" = @ContextDefinition("entity:user",
48+
* label = @Translation("User"),
49+
* required = FALSE,
50+
* default_value = NULL
51+
* ),
52+
* "access_operation" = @ContextDefinition("string",
53+
* label = @Translation("Operation"),
54+
* required = FALSE,
55+
* default_value = "view"
4056
* )
4157
* }
4258
* )
@@ -113,18 +129,21 @@ public function __construct(
113129
}
114130

115131
/**
116-
* @param string $type
117-
* @param int[] $ids
118-
* @param string $language
119-
* @param string[] $bundles
132+
* @param $type
133+
* @param array $ids
134+
* @param null $language
135+
* @param array|NULL $bundles
136+
* @param bool $access
137+
* @param \Drupal\Core\Session\AccountInterface|NULL $accessUser
138+
* @param string $accessOperation
120139
* @param \Drupal\graphql\GraphQL\Execution\FieldContext $context
121140
*
122141
* @return \GraphQL\Deferred
123142
*/
124-
public function resolve($type, array $ids, $language = NULL, array $bundles = NULL, FieldContext $context) {
143+
public function resolve($type, array $ids, $language, array $bundles, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation, FieldContext $context) {
125144
$resolver = $this->entityBuffer->add($type, $ids);
126145

127-
return new Deferred(function () use ($type, $ids, $language, $bundles, $resolver, $context) {
146+
return new Deferred(function () use ($type, $ids, $language, $bundles, $resolver, $context, $access, $accessUser, $accessOperation) {
128147
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
129148
if (!$entities = $resolver()) {
130149
// If there is no entity with this id, add the list cache tags so that
@@ -150,15 +169,15 @@ public function resolve($type, array $ids, $language = NULL, array $bundles = NU
150169
$entities[$id]->addCacheContexts(["static:language:{$language}"]);
151170
}
152171

153-
$access = $entity->access('view', NULL, TRUE);
154-
$context->addCacheableDependency($access);
155-
156-
if (!$access->isAllowed()) {
157-
// Do not return the entity if access is denied.
158-
unset($entities[$id]);
159-
continue;
172+
if ($access) {
173+
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
174+
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
175+
$context->addCacheableDependency($accessResult);
176+
if ($accessResult->isForbidden()) {
177+
unset($entities[$id]);
178+
continue;
179+
}
160180
}
161-
162181
}
163182

164183
return $entities;

src/Plugin/GraphQL/DataProducer/Entity/EntityTranslation.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Drupal\Core\Entity\EntityRepositoryInterface;
88
use Drupal\Core\Entity\TranslatableInterface;
99
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
10+
use Drupal\Core\Session\AccountInterface;
1011
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
1112
use Symfony\Component\DependencyInjection\ContainerInterface;
1213

@@ -24,6 +25,21 @@
2425
* ),
2526
* "language" = @ContextDefinition("string",
2627
* label = @Translation("Language")
28+
* ),
29+
* "access" = @ContextDefinition("boolean",
30+
* label = @Translation("Check access"),
31+
* required = FALSE,
32+
* default_value = TRUE
33+
* ),
34+
* "access_user" = @ContextDefinition("entity:user",
35+
* label = @Translation("User"),
36+
* required = FALSE,
37+
* default_value = NULL
38+
* ),
39+
* "access_operation" = @ContextDefinition("string",
40+
* label = @Translation("Operation"),
41+
* required = FALSE,
42+
* default_value = "view"
2743
* )
2844
* }
2945
* )
@@ -74,13 +90,25 @@ public function __construct(array $configuration, $pluginId, $pluginDefinition,
7490
/**
7591
* @param \Drupal\Core\Entity\EntityInterface $entity
7692
* @param $language
93+
* @param bool $access
94+
* @param \Drupal\graphql\Plugin\GraphQL\DataProducer\Entity\AccountInterface|NULL $accessUser
95+
* @param string $accessOperation
7796
*
78-
* @return \Drupal\Core\Entity\TranslatableInterface|null
97+
* @return |null
7998
*/
80-
public function resolve(EntityInterface $entity, $language) {
99+
public function resolve(EntityInterface $entity, $language, ?bool $access, ?AccountInterface $accessUser, ?string $accessOperation) {
81100
if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
82101
$entity = $entity->getTranslation($language);
83102
$entity->addCacheContexts(["static:language:{$language}"]);
103+
// Check if the passed user (or current user if none is passed) has access
104+
// to the entity, if not return NULL.
105+
if ($access) {
106+
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
107+
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
108+
if ($accessResult->isForbidden()) {
109+
return NULL;
110+
}
111+
}
84112
return $entity;
85113
}
86114

src/Plugin/GraphQL/DataProducer/Entity/EntityTranslations.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Drupal\Core\Entity\TranslatableInterface;
99
use Drupal\Core\Language\LanguageInterface;
1010
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
11+
use Drupal\Core\Session\AccountInterface;
1112
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
1213
use Symfony\Component\DependencyInjection\ContainerInterface;
1314

@@ -24,6 +25,21 @@
2425
* consumes = {
2526
* "entity" = @ContextDefinition("entity",
2627
* label = @Translation("Entity")
28+
* ),
29+
* "access" = @ContextDefinition("boolean",
30+
* label = @Translation("Check access"),
31+
* required = FALSE,
32+
* default_value = TRUE
33+
* ),
34+
* "access_user" = @ContextDefinition("entity:user",
35+
* label = @Translation("User"),
36+
* required = FALSE,
37+
* default_value = NULL
38+
* ),
39+
* "access_operation" = @ContextDefinition("string",
40+
* label = @Translation("Operation"),
41+
* required = FALSE,
42+
* default_value = "view"
2743
* )
2844
* }
2945
* )
@@ -73,17 +89,27 @@ public function __construct(array $configuration, $pluginId, $pluginDefinition,
7389

7490
/**
7591
* @param \Drupal\Core\Entity\EntityInterface $entity
92+
* @param bool $access
93+
* @param \Drupal\Core\Session\AccountInterface|NULL $accessUser
94+
* @param string $accessOperation
7695
*
77-
* @return \Drupal\Core\Entity\TranslatableInterface|null
96+
* @return array|null
7897
*/
79-
public function resolve(EntityInterface $entity) {
98+
public function resolve(EntityInterface $entity, ?bool $access , ?AccountInterface $accessUser, ?string $accessOperation) {
8099
if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
81100
$languages = $entity->getTranslationLanguages();
82101

83-
return array_map(function (LanguageInterface $language) use ($entity) {
102+
return array_map(function (LanguageInterface $language) use ($entity, $access, $accessOperation, $accessUser) {
84103
$langcode = $language->getId();
85104
$entity = $entity->getTranslation($langcode);
86105
$entity->addCacheContexts(["static:language:{$langcode}"]);
106+
if ($access) {
107+
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
108+
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
109+
if ($accessResult->isForbidden()) {
110+
return NULL;
111+
}
112+
}
87113
return $entity;
88114
}, $languages);
89115
}

0 commit comments

Comments
 (0)