Skip to content

Commit 768c5ab

Browse files
committed
Issue #3028490 by tim.plunkett, Kristen Pol, xjm, tedbow, r.aubin, phenaproxima: Users with "configure any layout" can see entities they don't have "view" access to
1 parent e0f0211 commit 768c5ab

File tree

3 files changed

+165
-4
lines changed

3 files changed

+165
-4
lines changed

modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,17 @@ public function getSectionListFromId($id) {
146146
*/
147147
public function buildRoutes(RouteCollection $collection) {
148148
foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
149+
// If the canonical route does not exist, do not provide any Layout
150+
// Builder UI routes for this entity type.
151+
if (!$collection->get("entity.$entity_type_id.canonical")) {
152+
continue;
153+
}
154+
149155
$defaults = [];
150156
$defaults['entity_type_id'] = $entity_type_id;
151157

152-
$requirements = [];
153-
if ($this->hasIntegerId($entity_type)) {
154-
$requirements[$entity_type_id] = '\d+';
155-
}
158+
// Retrieve the requirements from the canonical route.
159+
$requirements = $collection->get("entity.$entity_type_id.canonical")->getRequirements();
156160

157161
$options = [];
158162
// Ensure that upcasting is run in the correct order.

modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,38 @@ public function testLayoutBuilderUi() {
236236
$assert_session->elementNotExists('css', '.field--name-field-my-text');
237237
}
238238

239+
/**
240+
* Test that layout builder checks entity view access.
241+
*/
242+
public function testAccess() {
243+
$assert_session = $this->assertSession();
244+
245+
$this->drupalLogin($this->drupalCreateUser([
246+
'configure any layout',
247+
'administer node display',
248+
]));
249+
250+
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
251+
// Allow overrides for the layout.
252+
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
253+
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
254+
255+
$this->drupalLogin($this->drupalCreateUser(['configure any layout']));
256+
$this->drupalGet('node/1');
257+
$assert_session->pageTextContains('The first node body');
258+
$assert_session->pageTextNotContains('Powered by Drupal');
259+
$node = Node::load(1);
260+
$node->setUnpublished();
261+
$node->save();
262+
$this->drupalGet('node/1');
263+
$assert_session->pageTextNotContains('The first node body');
264+
$assert_session->pageTextContains('Access denied');
265+
266+
$this->drupalGet('node/1/layout');
267+
$assert_session->pageTextNotContains('The first node body');
268+
$assert_session->pageTextContains('Access denied');
269+
}
270+
239271
/**
240272
* Tests that a non-default view mode works as expected.
241273
*/

modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ public function testBuildRoutes() {
184184
$entity_types['no_canonical_link'] = $no_canonical_link->reveal();
185185
$this->entityFieldManager->getFieldStorageDefinitions('no_canonical_link')->shouldNotBeCalled();
186186

187+
$canonical_link_no_route = $this->prophesize(EntityTypeInterface::class);
188+
$canonical_link_no_route->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
189+
$canonical_link_no_route->hasViewBuilderClass()->willReturn(TRUE);
190+
$canonical_link_no_route->hasLinkTemplate('canonical')->willReturn(TRUE);
191+
$canonical_link_no_route->getLinkTemplate('canonical')->willReturn('/entity/{entity}');
192+
$canonical_link_no_route->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
193+
$entity_types['canonical_link_no_route'] = $canonical_link_no_route->reveal();
194+
$this->entityFieldManager->getFieldStorageDefinitions('canonical_link_no_route')->shouldNotBeCalled();
195+
196+
$from_canonical = $this->prophesize(EntityTypeInterface::class);
197+
$from_canonical->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
198+
$from_canonical->hasViewBuilderClass()->willReturn(TRUE);
199+
$from_canonical->hasLinkTemplate('canonical')->willReturn(TRUE);
200+
$from_canonical->getLinkTemplate('canonical')->willReturn('/entity/{entity}');
201+
$entity_types['from_canonical'] = $from_canonical->reveal();
202+
187203
$with_string_id = $this->prophesize(EntityTypeInterface::class);
188204
$with_string_id->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
189205
$with_string_id->hasViewBuilderClass()->willReturn(TRUE);
@@ -211,6 +227,109 @@ public function testBuildRoutes() {
211227
$this->entityTypeManager->getDefinitions()->willReturn($entity_types);
212228

213229
$expected = [
230+
'entity.from_canonical.canonical' => new Route(
231+
'/entity/{entity}',
232+
[],
233+
[
234+
'custom requirement' => 'from_canonical_route',
235+
]
236+
),
237+
'entity.with_string_id.canonical' => new Route(
238+
'/entity/{entity}'
239+
),
240+
'entity.with_integer_id.canonical' => new Route(
241+
'/entity/{entity}',
242+
[],
243+
[
244+
'with_integer_id' => '\d+',
245+
]
246+
),
247+
'layout_builder.overrides.from_canonical.view' => new Route(
248+
'/entity/{entity}/layout',
249+
[
250+
'entity_type_id' => 'from_canonical',
251+
'section_storage_type' => 'overrides',
252+
'section_storage' => '',
253+
'is_rebuilding' => FALSE,
254+
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
255+
'_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
256+
],
257+
[
258+
'_has_layout_section' => 'true',
259+
'_layout_builder_access' => 'view',
260+
'custom requirement' => 'from_canonical_route',
261+
],
262+
[
263+
'parameters' => [
264+
'section_storage' => ['layout_builder_tempstore' => TRUE],
265+
'from_canonical' => ['type' => 'entity:from_canonical'],
266+
],
267+
'_layout_builder' => TRUE,
268+
]
269+
),
270+
'layout_builder.overrides.from_canonical.save' => new Route(
271+
'/entity/{entity}/layout/save',
272+
[
273+
'entity_type_id' => 'from_canonical',
274+
'section_storage_type' => 'overrides',
275+
'section_storage' => '',
276+
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
277+
],
278+
[
279+
'_has_layout_section' => 'true',
280+
'_layout_builder_access' => 'view',
281+
'custom requirement' => 'from_canonical_route',
282+
],
283+
[
284+
'parameters' => [
285+
'section_storage' => ['layout_builder_tempstore' => TRUE],
286+
'from_canonical' => ['type' => 'entity:from_canonical'],
287+
],
288+
'_layout_builder' => TRUE,
289+
]
290+
),
291+
'layout_builder.overrides.from_canonical.cancel' => new Route(
292+
'/entity/{entity}/layout/cancel',
293+
[
294+
'entity_type_id' => 'from_canonical',
295+
'section_storage_type' => 'overrides',
296+
'section_storage' => '',
297+
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout',
298+
],
299+
[
300+
'_has_layout_section' => 'true',
301+
'_layout_builder_access' => 'view',
302+
'custom requirement' => 'from_canonical_route',
303+
],
304+
[
305+
'parameters' => [
306+
'section_storage' => ['layout_builder_tempstore' => TRUE],
307+
'from_canonical' => ['type' => 'entity:from_canonical'],
308+
],
309+
'_layout_builder' => TRUE,
310+
]
311+
),
312+
'layout_builder.overrides.from_canonical.revert' => new Route(
313+
'/entity/{entity}/layout/revert',
314+
[
315+
'entity_type_id' => 'from_canonical',
316+
'section_storage_type' => 'overrides',
317+
'section_storage' => '',
318+
'_form' => '\Drupal\layout_builder\Form\RevertOverridesForm',
319+
],
320+
[
321+
'_has_layout_section' => 'true',
322+
'_layout_builder_access' => 'view',
323+
'custom requirement' => 'from_canonical_route',
324+
],
325+
[
326+
'parameters' => [
327+
'section_storage' => ['layout_builder_tempstore' => TRUE],
328+
'from_canonical' => ['type' => 'entity:from_canonical'],
329+
],
330+
'_layout_builder' => TRUE,
331+
]
332+
),
214333
'layout_builder.overrides.with_string_id.view' => new Route(
215334
'/entity/{entity}/layout',
216335
[
@@ -382,6 +501,12 @@ public function testBuildRoutes() {
382501
];
383502

384503
$collection = new RouteCollection();
504+
// Entity types that declare a link template for canonical must have a
505+
// canonical route present in the route colletion.
506+
$collection->add('entity.from_canonical.canonical', $expected['entity.from_canonical.canonical']);
507+
$collection->add('entity.with_string_id.canonical', $expected['entity.with_string_id.canonical']);
508+
$collection->add('entity.with_integer_id.canonical', $expected['entity.with_integer_id.canonical']);
509+
385510
$this->plugin->buildRoutes($collection);
386511
$this->assertEquals($expected, $collection->all());
387512
$this->assertSame(array_keys($expected), array_keys($collection->all()));

0 commit comments

Comments
 (0)