Skip to content

Commit

Permalink
fix(data producer): Load translation of the entity before access chec…
Browse files Browse the repository at this point in the history
…k. (#923)
  • Loading branch information
rthideaway authored and klausi committed Nov 1, 2019
1 parent 1b404de commit 2e43003
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 39 deletions.
23 changes: 12 additions & 11 deletions src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ?
return NULL;
}

$context->addCacheableDependency($entity);
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
// If the entity is not among the allowed bundles, don't return it.
return NULL;
}

// Get the correct translation.
if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

// Check if the passed user (or current user if none is passed) has access
// to the entity, if not return NULL.
if ($access) {
Expand All @@ -164,17 +176,6 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ?
}
}

if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
// If the entity is not among the allowed bundles, don't return it.
$context->addCacheableDependency($entity);
return NULL;
}

if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

return $entity;
});
}
Expand Down
23 changes: 12 additions & 11 deletions src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou
return NULL;
}

$context->addCacheableDependency($entity);
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
// If the entity is not among the allowed bundles, don't return it.
return NULL;
}

// Get the correct translation.
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

// Check if the passed user (or current user if none is passed) has access
// to the entity, if not return NULL.
if ($access) {
Expand All @@ -164,17 +176,6 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou
}
}

if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
// If the entity is not among the allowed bundles, don't return it.
$context->addCacheableDependency($entity);
return NULL;
}

if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

return $entity;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public function resolve($type, array $ids, $language, ?array $bundles, ?bool $ac

if ($access) {
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
$accessResult = $entities[$id]->access($accessOperation, $accessUser, TRUE);
$context->addCacheableDependency($accessResult);
// We need to call isAllowed() because isForbidden() returns FALSE
// for neutral access results, which is dangerous. Only an explicitly
Expand Down
17 changes: 6 additions & 11 deletions src/Plugin/GraphQL/DataProducer/Field/EntityReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra
return FALSE;
}

// Get the correct translation.
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

// Check if the passed user (or current user if none is passed) has
// access to the entity, if not return NULL.
if ($access) {
Expand All @@ -183,17 +189,6 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra
return NULL;
}

if (isset($language)) {
$entities = array_map(function (EntityInterface $entity) use ($language) {
if ($language !== $entity->language()->getId() && $entity instanceof TranslatableInterface && $entity->hasTranslation($language)) {
$entity = $entity->getTranslation($language);
}

$entity->addCacheContexts(["static:language:{$language}"]);
return $entity;
}, $entities);
}

return $entities;
});
}
Expand Down
9 changes: 6 additions & 3 deletions src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,15 @@ public function resolve($url, $language = NULL, FieldContext $context) {
return NULL;
}

// Get the correct translation.
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
$entity->addCacheContexts(["static:language:{$language}"]);
}

$access = $entity->access('view', NULL, TRUE);
$context->addCacheableDependency($access);
if ($access->isAllowed()) {
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($language);
}
return $entity;
}
return NULL;
Expand Down
87 changes: 85 additions & 2 deletions tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function setUp() {
]);
$content_type->save();

// Published node and published translations.
$this->published_node = Node::create([
'title' => 'Test Event',
'type' => 'event',
Expand All @@ -42,6 +43,7 @@ public function setUp() {
$this->translation_de_published = $this->published_node->addTranslation('de', ['title' => 'Test Event DE']);
$this->translation_de_published->save();

// Unpublished node and unpublished translations.
$this->unpublished_node = Node::create([
'title' => 'Test Unpublished Event',
'type' => 'event',
Expand All @@ -50,18 +52,53 @@ public function setUp() {
$this->unpublished_node->save();

$this->translation_fr_unpublished = $this->unpublished_node->addTranslation('fr', ['title' => 'Test Unpublished Event FR']);
$this->translation_fr_unpublished->status = NodeInterface::NOT_PUBLISHED;
$this->translation_fr_unpublished->save();

$this->translation_de_unpublished = $this->unpublished_node->addTranslation('de', ['title' => 'Test Unpublished Event DE']);
$this->translation_de_unpublished->status = NodeInterface::NOT_PUBLISHED;
$this->translation_de_unpublished->save();

// Unpublished node to published translations.
$this->unpublished_to_published_node = Node::create([
'title' => 'Test Unpublished to Published Event',
'type' => 'event',
'status' => NodeInterface::NOT_PUBLISHED,
]);
$this->unpublished_to_published_node->save();

$this->translation_fr_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('fr', ['title' => 'Test Unpublished to Published Event FR']);
$this->translation_fr_unpublished_to_published->status = NodeInterface::PUBLISHED;
$this->translation_fr_unpublished_to_published->save();

$this->translation_de_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('de', ['title' => 'Test Unpublished to Published Event DE']);
$this->translation_de_unpublished_to_published->status = NodeInterface::PUBLISHED;
$this->translation_de_unpublished_to_published->save();

// Published node to unpublished translations.
$this->published_to_unpublished_node = Node::create([
'title' => 'Test Published to Unpublished Event',
'type' => 'event',
'status' => NodeInterface::PUBLISHED,
]);
$this->published_to_unpublished_node->save();

$this->translation_fr_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('fr', ['title' => 'Test Published to Unpublished Event FR']);
$this->translation_fr_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED;
$this->translation_fr_published_to_unpublished->save();

$this->translation_de_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('de', ['title' => 'Test Published to Unpublished Event DE']);
$this->translation_de_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED;
$this->translation_de_published_to_unpublished->save();

\Drupal::service('content_translation.manager')->setEnabled('node', 'event', TRUE);
}

/**
* @covers \Drupal\graphql\Plugin\GraphQL\DataProducer\Routing\RouteEntity::resolve
*/
public function testRouteEntity() {
// Published node to published translations.
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_node->id()]);

$result = $this->executeDataProducer('route_entity', [
Expand All @@ -87,8 +124,8 @@ public function testRouteEntity() {
$this->assertEquals($this->translation_de_published->id(), $result->id());
$this->assertEquals($this->translation_de_published->label(), $result->label());

// Make sure we are not allowed to get the unpublished nodes or
// translations.
// Unpublished node to unpublished translations. Make sure we are not
// allowed to get the unpublished nodes or translations.
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_node->id()]);
foreach ([NULL, 'fr', 'de'] as $lang) {
$result = $this->executeDataProducer('route_entity', [
Expand All @@ -99,6 +136,52 @@ public function testRouteEntity() {
$this->assertNull($result);
}

// Unpublished node to published translations. Make sure we are not able to
// get unpublished source, but we are able to get published translations.
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_to_published_node->id()]);

$result = $this->executeDataProducer('route_entity', [
'url' => $url,
]);

$this->assertNull($result);

$result = $this->executeDataProducer('route_entity', [
'url' => $url,
'language' => 'fr',
]);

$this->assertEquals($this->translation_fr_unpublished_to_published->id(), $result->id());
$this->assertEquals($this->translation_fr_unpublished_to_published->label(), $result->label());

$result = $this->executeDataProducer('route_entity', [
'url' => $url,
'language' => 'de',
]);

$this->assertEquals($this->translation_de_unpublished_to_published->id(), $result->id());
$this->assertEquals($this->translation_de_unpublished_to_published->label(), $result->label());

// Published node to unpublished translations. Make sure we are able to get
// published source, but we are not able to get unpublished translations.
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_to_unpublished_node->id()]);

$result = $this->executeDataProducer('route_entity', [
'url' => $url,
]);

$this->assertEquals($this->published_to_unpublished_node->id(), $result->id());
$this->assertEquals($this->published_to_unpublished_node->label(), $result->label());

foreach (['fr', 'de'] as $lang) {
$result = $this->executeDataProducer('route_entity', [
'url' => $url,
'language' => $lang,
]);

$this->assertNull($result);
}

// Test with something which is not a URL.
$this->assertNull($this->executeDataProducer('route_entity', [
'url' => 'not_a_url',
Expand Down

0 comments on commit 2e43003

Please sign in to comment.