Browse Source

Issue #3116410 by larowlan: The module may not bubble access cacheability metadata

8.x-1.x 8.x-1.10
Chi 5 years ago
parent
commit
f135078fa8
  1. 56
      src/TwigExtension.php
  2. 212
      tests/src/Kernel/AccessTest.php
  3. 18
      tests/twig_tweak_test/twig_tweak_test.module

56
src/TwigExtension.php

@ -2,7 +2,9 @@
namespace Drupal\twig_tweak; namespace Drupal\twig_tweak;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\TitleBlockPluginInterface; use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Site\Settings; use Drupal\Core\Site\Settings;
use Drupal\Core\Url; use Drupal\Core\Url;
@ -80,8 +82,16 @@ class TwigExtension extends \Twig_Extension {
public function drupalBlock($id, $check_access = TRUE) { public function drupalBlock($id, $check_access = TRUE) {
$entity_type_manager = \Drupal::entityTypeManager(); $entity_type_manager = \Drupal::entityTypeManager();
$block = $entity_type_manager->getStorage('block')->load($id); $block = $entity_type_manager->getStorage('block')->load($id);
if ($block && (!$check_access || $this->entityAccess($block))) { if ($block) {
return $entity_type_manager->getViewBuilder('block')->view($block); $access = $check_access ? $this->entityAccess($block) : AccessResult::allowed();
if ($access->isAllowed()) {
$build = $entity_type_manager->getViewBuilder('block')->view($block);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($block))
->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build;
}
} }
} }
@ -108,9 +118,13 @@ class TwigExtension extends \Twig_Extension {
$build = []; $build = [];
$cache_metadata = new CacheableMetadata();
/* @var $blocks \Drupal\block\BlockInterface[] */ /* @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $id => $block) { foreach ($blocks as $id => $block) {
if ($this->entityAccess($block)) { $access = $this->entityAccess($block);
$cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($access));
if ($access->isAllowed()) {
$block_plugin = $block->getPlugin(); $block_plugin = $block->getPlugin();
if ($block_plugin instanceof TitleBlockPluginInterface) { if ($block_plugin instanceof TitleBlockPluginInterface) {
$request = \Drupal::request(); $request = \Drupal::request();
@ -122,6 +136,10 @@ class TwigExtension extends \Twig_Extension {
} }
} }
if ($build) {
$cache_metadata->applyTo($build);
}
return $build; return $build;
} }
@ -146,9 +164,18 @@ class TwigExtension extends \Twig_Extension {
$entity = $id $entity = $id
? $entity_type_manager->getStorage($entity_type)->load($id) ? $entity_type_manager->getStorage($entity_type)->load($id)
: \Drupal::routeMatch()->getParameter($entity_type); : \Drupal::routeMatch()->getParameter($entity_type);
if ($entity && $this->entityAccess($entity)) { if ($entity) {
$render_controller = $entity_type_manager->getViewBuilder($entity_type); $access = $this->entityAccess($entity);
return $render_controller->view($entity, $view_mode, $langcode); if ($access->isAllowed()) {
$build = $entity_type_manager
->getViewBuilder($entity_type)
->view($entity, $view_mode, $langcode);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($entity))
->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build;
}
} }
} }
@ -174,12 +201,20 @@ class TwigExtension extends \Twig_Extension {
$entity = $id $entity = $id
? \Drupal::entityTypeManager()->getStorage($entity_type)->load($id) ? \Drupal::entityTypeManager()->getStorage($entity_type)->load($id)
: \Drupal::routeMatch()->getParameter($entity_type); : \Drupal::routeMatch()->getParameter($entity_type);
if ($entity && $this->entityAccess($entity)) { if ($entity) {
$access = $this->entityAccess($entity);
if ($access->isAllowed()) {
if ($langcode && $entity->hasTranslation($langcode)) { if ($langcode && $entity->hasTranslation($langcode)) {
$entity = $entity->getTranslation($langcode); $entity = $entity->getTranslation($langcode);
} }
if (isset($entity->{$field_name})) { if (isset($entity->{$field_name})) {
return $entity->{$field_name}->view($view_mode); $build = $entity->{$field_name}->view($view_mode);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($access))
->merge(CacheableMetadata::createFromObject($entity))
->applyTo($build);
return $build;
}
} }
} }
} }
@ -481,7 +516,7 @@ class TwigExtension extends \Twig_Extension {
* @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\Core\Entity\EntityInterface $entity
* Entity to check access. * Entity to check access.
* *
* @return bool * @return \Drupal\Core\Access\AccessResultInterface
* The access check result. * The access check result.
* *
* @TODO Remove "check_access" option in 9.x. * @TODO Remove "check_access" option in 9.x.
@ -489,7 +524,8 @@ class TwigExtension extends \Twig_Extension {
protected function entityAccess(EntityInterface $entity) { protected function entityAccess(EntityInterface $entity) {
// Prior version 8.x-1.7 entity access was not checked. The "check_access" // Prior version 8.x-1.7 entity access was not checked. The "check_access"
// option provides a workaround for possible BC issues. // option provides a workaround for possible BC issues.
return !Settings::get('twig_tweak_check_access', TRUE) || $entity->access('view'); return Settings::get('twig_tweak_check_access', TRUE) ?
$entity->access('view', NULL, TRUE) : AccessResult::allowed();
} }
} }

212
tests/src/Kernel/AccessTest.php

@ -0,0 +1,212 @@
<?php
namespace Drupal\Tests\twig_tweak\Kernel;
use Drupal\block\BlockViewBuilder;
use Drupal\block\Entity\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests for the Twig Tweak access control.
*
* @group twig_tweak
*/
class AccessTest extends KernelTestBase {
use UserCreationTrait;
/**
* A node for testing.
*
* @var \Drupal\node\NodeInterface
*/
private $node;
/**
* The Twig extension.
*
* @var \Drupal\twig_tweak\TwigExtension
*/
private $twigExtension;
/**
* {@inheritdoc}
*/
public static $modules = [
'twig_tweak',
'twig_tweak_test',
'node',
'user',
'system',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('node');
$this->installEntitySchema('user');
$this->installConfig(['system']);
$node_type = NodeType::create([
'type' => 'article',
'name' => 'Article',
]);
$node_type->save();
$values = [
'type' => 'article',
'status' => NodeInterface::PUBLISHED,
// @see twig_tweak_test_node_access()
'title' => 'Entity access test',
];
$this->node = Node::create($values);
$this->node->save();
$this->twigExtension = $this->container->get('twig_tweak.twig_extension');
}
/**
* Test callback.
*/
public function testDrupalEntity() {
// -- Unprivileged user.
$this->setUpCurrentUser(['name' => 'User 1']);
$build = $this->twigExtension->drupalEntity('node', $this->node->id());
self::assertNull($build);
// -- Privileged user.
$this->setUpCurrentUser(['name' => 'User 2'], ['access content']);
$build = $this->twigExtension->drupalEntity('node', $this->node->id());
self::assertArrayHasKey('#node', $build);
$expected_cache = [
'tags' => [
'node:1',
'node_view',
'tag_from_twig_tweak_test_node_access',
],
'contexts' => [
'user',
'user.permissions',
],
'max-age' => 50,
];
self::assertSame($expected_cache, $build['#cache']);
}
/**
* Test callback.
*/
public function testDrupalField() {
// -- Unprivileged user.
$this->setUpCurrentUser(['name' => 'User 1']);
$build = $this->twigExtension->drupalField('title', 'node', $this->node->id());
self::assertNull($build);
// -- Privileged user.
$this->setUpCurrentUser(['name' => 'User 2'], ['access content']);
$build = $this->twigExtension->drupalField('title', 'node', $this->node->id());
self::assertArrayHasKey('#items', $build);
$expected_cache = [
'contexts' => [
'user',
'user.permissions',
],
'tags' => [
'node:1',
'tag_from_twig_tweak_test_node_access',
],
'max-age' => 50,
];
self::assertSame($expected_cache, $build['#cache']);
}
/**
* Test callback.
*/
public function testDrupalRegion() {
// @codingStandardsIgnoreStart
$create_block = function ($id) {
return new class(['id' => $id], 'block') extends Block {
public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
$result = AccessResult::allowedIf($this->id == 'block_1');
$result->cachePerUser();
$result->addCacheTags(['tag_for_' . $this->id]);
$result->setCacheMaxAge(123);
return $return_as_object ? $result : $result->isAllowed();
}
public function getPlugin() {
return NULL;
}
};
};
// @codingStandardsIgnoreEnd
$storage = $this->createMock(EntityStorageInterface::class);
$blocks = [
'block_1' => $create_block('block_1'),
'block_2' => $create_block('block_2'),
];
$storage->expects($this->any())
->method('loadByProperties')
->willReturn($blocks);
$view_builder = $this->createMock(BlockViewBuilder::class);
$content = [
'#markup' => 'foo',
'#cache' => [
'tags' => ['tag_from_view'],
],
];
$view_builder->expects($this->any())
->method('view')
->willReturn($content);
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
$entity_type_manager->expects($this->any())
->method('getStorage')
->willReturn($storage);
$entity_type_manager->expects($this->any())
->method('getViewBuilder')
->willReturn($view_builder);
$this->container->set('entity_type.manager', $entity_type_manager);
$build = $this->twigExtension->drupalRegion('bar');
$expected_build = [
'block_1' => [
'#markup' => 'foo',
'#cache' => [
'tags' => ['tag_from_view'],
],
],
'#cache' => [
'contexts' => ['user'],
'tags' => [
'tag_for_block_1',
'tag_for_block_2',
],
'max-age' => 123,
],
];
self::assertSame($expected_build, $build);
}
}

18
tests/twig_tweak_test/twig_tweak_test.module

@ -5,6 +5,9 @@
* Primary module hooks for Twig Tweak test module. * Primary module hooks for Twig Tweak test module.
*/ */
use Drupal\Core\Access\AccessResult;
use Drupal\node\NodeInterface;
/** /**
* Implements hook_page_bottom(). * Implements hook_page_bottom().
*/ */
@ -25,3 +28,18 @@ function twig_tweak_test_theme() {
function template_preprocess_twig_tweak_test(&$vars) { function template_preprocess_twig_tweak_test(&$vars) {
$vars['node'] = Drupal::routeMatch()->getParameter('node'); $vars['node'] = Drupal::routeMatch()->getParameter('node');
} }
/**
* Implements hook_node_access().
*
* @see \Drupal\Tests\twig_tweak\Kernel\AccessTest
*/
function twig_tweak_test_node_access(NodeInterface $node) {
if ($node->getTitle() == 'Entity access test') {
$result = AccessResult::allowed();
$result->addCacheTags(['tag_from_' . __FUNCTION__]);
$result->cachePerUser();
$result->setCacheMaxAge(50);
return $result;
}
}

Loading…
Cancel
Save