Skip to content

Commit 0217534

Browse files
committed
Only expose a URL if the given user has access to the related route
1 parent bf9dea3 commit 0217534

File tree

2 files changed

+106
-6
lines changed

2 files changed

+106
-6
lines changed

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

+68-5
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
namespace Drupal\graphql\Plugin\GraphQL\DataProducer\Entity;
44

5+
use Drupal\Core\Access\AccessManagerInterface;
6+
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
57
use Drupal\Core\Entity\EntityInterface;
8+
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9+
use Drupal\Core\Session\AccountInterface;
10+
use Drupal\graphql\GraphQL\Execution\FieldContext;
611
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
12+
use Symfony\Component\DependencyInjection\ContainerInterface;
713

814
/**
915
* Returns the URL of an entity.
@@ -28,11 +34,57 @@
2834
* label = @Translation("URL Options"),
2935
* description = @Translation("Options to pass to the toUrl call"),
3036
* required = FALSE
31-
* )
37+
* ),
38+
* "access_user" = @ContextDefinition("entity:user",
39+
* label = @Translation("User"),
40+
* required = FALSE,
41+
* default_value = NULL
42+
* ),
3243
* }
3344
* )
3445
*/
35-
class EntityUrl extends DataProducerPluginBase {
46+
class EntityUrl extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
47+
use DependencySerializationTrait;
48+
49+
/**
50+
* The access manager.
51+
*
52+
* @var \Drupal\Core\Access\AccessManagerInterface
53+
*/
54+
protected $accessManager;
55+
56+
/**
57+
* {@inheritdoc}
58+
*
59+
* @codeCoverageIgnore
60+
*/
61+
public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
62+
return new static(
63+
$configuration,
64+
$pluginId,
65+
$pluginDefinition,
66+
$container->get('access_manager')
67+
);
68+
}
69+
70+
/**
71+
* EntityTranslation constructor.
72+
*
73+
* @param array $configuration
74+
* The plugin configuration array.
75+
* @param string $pluginId
76+
* The plugin id.
77+
* @param mixed $pluginDefinition
78+
* The plugin definition.
79+
* @param \Drupal\Core\Access\AccessManagerInterface $accessManager
80+
* The access manager service.
81+
*
82+
* @codeCoverageIgnore
83+
*/
84+
public function __construct(array $configuration, $pluginId, $pluginDefinition, AccessManagerInterface $accessManager) {
85+
parent::__construct($configuration, $pluginId, $pluginDefinition);
86+
$this->accessManager = $accessManager;
87+
}
3688

3789
/**
3890
* Resolver.
@@ -43,13 +95,24 @@ class EntityUrl extends DataProducerPluginBase {
4395
* The link relationship type, for example: canonical or edit-form.
4496
* @param array|null $options
4597
* The options to provided to the URL generator.
98+
* @param \Drupal\Core\Session\AccountInterface|null $accessUser
99+
* @param \Drupal\graphql\GraphQL\Execution\FieldContext $context
46100
*
47-
* @return \Drupal\Core\Url
101+
* @return \Drupal\Core\Url|null
48102
*
49103
* @throws \Drupal\Core\Entity\EntityMalformedException
50104
*/
51-
public function resolve(EntityInterface $entity, ?string $rel, ?array $options) {
52-
return $entity->toUrl($rel ?? 'canonical', $options ?? []);
105+
public function resolve(EntityInterface $entity, ?string $rel, ?array $options, ?AccountInterface $accessUser, FieldContext $context) {
106+
$url = $entity->toUrl($rel ?? 'canonical', $options ?? []);
107+
108+
// @see https://www.drupal.org/project/drupal/issues/2677902
109+
$access = $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $accessUser, TRUE);
110+
$context->addCacheableDependency($access);
111+
if ($access->isAllowed()) {
112+
return $url;
113+
}
114+
115+
return NULL;
53116
}
54117

55118
}

tests/src/Kernel/DataProducer/EntityTest.php

+38-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Drupal\Tests\graphql\Kernel\DataProducer;
44

5+
use Drupal\Core\Access\AccessManagerInterface;
6+
use Drupal\Core\Access\AccessResult;
57
use Drupal\Core\Language\LanguageInterface;
68
use Drupal\Tests\graphql\Kernel\GraphQLTestBase;
79
use Drupal\node\NodeInterface;
@@ -273,17 +275,44 @@ public function testResolveTranslation(): void {
273275
* @covers \Drupal\graphql\Plugin\GraphQL\DataProducer\Entity\EntityUrl::resolve
274276
*/
275277
public function testResolveUrl(): void {
278+
$accessManager = $this->getMockBuilder(AccessManagerInterface::class)
279+
->disableOriginalConstructor()
280+
->getMock();
281+
$accessManager->expects($this->exactly(2))
282+
->method('checkNamedRoute')
283+
->willReturnCallback(static function (): AccessResult {
284+
static $counter = 0;
285+
switch ($counter) {
286+
case 0:
287+
$counter++;
288+
return AccessResult::allowed();
289+
290+
case 1:
291+
$counter++;
292+
return AccessResult::forbidden();
293+
294+
default:
295+
throw new \LogicException('The access() method should not have been called more than twice.');
296+
}
297+
});
298+
$this->container->set('access_manager', $accessManager);
299+
276300
$url = $this->getMockBuilder(Url::class)
277301
->disableOriginalConstructor()
278302
->getMock();
303+
$url->method('getRouteParameters')->willReturn([]);
279304

280-
$this->entity->expects($this->once())
305+
$this->entity->expects($this->any())
281306
->method('toUrl')
282307
->willReturn($url);
283308

284309
$this->assertEquals($url, $this->executeDataProducer('entity_url', [
285310
'entity' => $this->entity,
286311
]));
312+
313+
$this->assertNull($this->executeDataProducer('entity_url', [
314+
'entity' => $this->entity,
315+
]));
287316
}
288317

289318
/**
@@ -293,6 +322,14 @@ public function testResolveAbsoluteUrl(): void {
293322
$url = $this->getMockBuilder(Url::class)
294323
->disableOriginalConstructor()
295324
->getMock();
325+
$url->method('getRouteParameters')->willReturn([]);
326+
327+
$accessManager = $this->getMockBuilder(AccessManagerInterface::class)
328+
->disableOriginalConstructor()
329+
->getMock();
330+
$accessManager->expects($this->once())
331+
->method('checkNamedRoute')->willReturn(AccessResult::allowed());
332+
$this->container->set('access_manager', $accessManager);
296333

297334
$this->entity->expects($this->once())
298335
->method('toUrl')

0 commit comments

Comments
 (0)