Commit d8859389 authored by Jess's avatar Jess
Browse files

Issue #3028301 by tedbow, tim.plunkett, Wim Leers, xjm: Do not expose to...

Issue #3028301 by tedbow, tim.plunkett, Wim Leers, xjm: Do not expose to Layout Builder's sections either in defaults or overrides to REST and other external APIs
parent 902611a1
Loading
Loading
Loading
Loading
+10 −0
Changes for core/modules/layout_builder/src/Field/LayoutSectionItemList.php: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2,8 +2,10 @@

namespace Drupal\layout_builder\Field;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionListInterface;
use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
@@ -74,4 +76,12 @@ public function equals(FieldItemListInterface $list_to_compare) {
    return $convert($this) === $convert($list_to_compare);
  }

  /**
   * {@inheritdoc}
   */
  public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
    // @todo Allow access in https://www.drupal.org/node/2942975.
    return AccessResult::forbidden();
  }

}
+10 −0
Changes for core/modules/layout_builder/src/LayoutBuilderServiceProvider.php: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency;
use Drupal\layout_builder\Normalizer\LayoutEntityDisplayNormalizer;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

@@ -35,6 +37,14 @@ public function register(ContainerBuilder $container) {
      $definition->addTag('event_subscriber');
      $container->setDefinition('layout_builder.get_block_dependency_subscriber', $definition);
    }
    if (isset($modules['serialization'])) {
      $definition = (new ChildDefinition('serializer.normalizer.config_entity'))
        ->setClass(LayoutEntityDisplayNormalizer::class)
        // Ensure that this normalizer takes precedence for Layout Builder data
        // over the generic serializer.normalizer.config_entity.
        ->addTag('normalizer', ['priority' => 5]);
      $container->setDefinition('layout_builder.normalizer.layout_entity_display', $definition);
    }
  }

}
+33 −0
Changes for core/modules/layout_builder/src/Normalizer/LayoutEntityDisplayNormalizer.php: 33 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\layout_builder\Normalizer;

use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface;
use Drupal\serialization\Normalizer\ConfigEntityNormalizer;

/**
 * Normalizes/denormalizes LayoutEntityDisplay objects into an array structure.
 *
 * @internal
 *   All tagged services are internal.
 */
class LayoutEntityDisplayNormalizer extends ConfigEntityNormalizer {

  /**
   * {@inheritdoc}
   */
  protected $supportedInterfaceOrClass = LayoutEntityDisplayInterface::class;

  /**
   * {@inheritdoc}
   */
  protected static function getDataWithoutInternals(array $data) {
    $data = parent::getDataWithoutInternals($data);
    // Do not expose the actual layout sections in normalization.
    // @todo Determine what to expose here in
    //   https://www.drupal.org/node/2942975.
    unset($data['third_party_settings']['layout_builder']['sections']);
    return $data;
  }

}
+61 −0
Changes for core/modules/layout_builder/tests/src/Functional/Rest/EntityDisplaySectionsTest.php: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\layout_builder\Functional\Rest;

use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Url;

/**
 * Tests that default layout sections are not exposed via the REST API.
 *
 * @group layout_builder
 * @group rest
 */
class EntityDisplaySectionsTest extends LayoutRestTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $resourceConfigId = 'entity.entity_view_display';

  /**
   * Tests the normalization does not contain layout sections.
   */
  public function testLayoutEntityDisplay() {
    $display_id = 'node.bundle_with_section_field.default';
    $display = EntityViewDisplay::load($display_id);

    // Assert the display has 1 section.
    $this->assertCount(1, $display->getThirdPartySetting('layout_builder', 'sections'));
    $response = $this->request(
      'GET',
      Url::fromRoute(
        'rest.entity.entity_view_display.GET',
        ['entity_view_display' => 'node.bundle_with_section_field.default'])
    );
    $this->assertResourceResponse(
      200,
      FALSE,
      $response,
      [
        'config:core.entity_view_display.node.bundle_with_section_field.default',
        'config:rest.resource.entity.entity_view_display',
        'config:rest.settings',
        'http_response',
      ],
      [
        'user.permissions',
      ],
      FALSE,
      'MISS'
    );
    $response_data = $this->getDecodedContents($response);
    $this->assertSame($display_id, $response_data['id']);
    // Ensure the sections are not present in the serialized data, but other
    // Layout Builder data is.
    $this->assertArrayHasKey('layout_builder', $response_data['third_party_settings']);
    $this->assertArrayNotHasKey('sections', $response_data['third_party_settings']['layout_builder']);
    $this->assertEquals(['enabled' => TRUE, 'allow_custom' => TRUE], $response_data['third_party_settings']['layout_builder']);
  }

}
+154 −0
Changes for core/modules/layout_builder/tests/src/Functional/Rest/LayoutRestTestBase.php: 154 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\layout_builder\Functional\Rest;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Url;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\ResourceTestBase;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\ResponseInterface;

/**
 * Base class for Layout Builder REST tests.
 */
abstract class LayoutRestTestBase extends ResourceTestBase {

  use BasicAuthResourceTestTrait;

  /**
   * {@inheritdoc}
   */
  public static $modules = [
    'node',
    'layout_builder',
    'serialization',
    'basic_auth',
  ];

  /**
   * A test node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $node;

  /**
   * The node storage.
   *
   * @var \Drupal\node\NodeStorageInterface
   */
  protected $nodeStorage;

  /**
   * {@inheritdoc}
   */
  public function setUp() {
    parent::setUp();
    $assert_session = $this->assertSession();

    // @todo The Layout Builder UI relies on local tasks; fix in
    //   https://www.drupal.org/project/drupal/issues/2917777.
    $this->drupalPlaceBlock('local_tasks_block');

    $this->createContentType(['type' => 'bundle_with_section_field']);

    $this->drupalLogin($this->drupalCreateUser([
      'configure any layout',
      'administer node display',
      'administer display modes',
      'bypass node access',
      'create bundle_with_section_field content',
      'edit any bundle_with_section_field content',
    ]));
    $page = $this->getSession()->getPage();
    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field/display';

    // Enable Layout Builder for the default view modes, and overrides.
    $this->drupalGet("$field_ui_prefix/default");
    $page->checkField('layout[enabled]');
    $page->pressButton('Save');
    $page->checkField('layout[allow_custom]');
    $page->pressButton('Save');

    // Create a node.
    $this->node = $this->createNode([
      'type' => 'bundle_with_section_field',
      'title' => 'A node at rest will stay at rest.',
    ]);

    $this->drupalGet('node/' . $this->node->id() . '/layout');
    $page->clickLink('Layout');
    $page->clickLink('Add Block');
    $page->clickLink('Powered by Drupal');
    $page->fillField('settings[label]', 'This is an override');
    $page->checkField('settings[label_display]');
    $page->pressButton('Add Block');
    $page->clickLink('Save Layout');
    $assert_session->pageTextContains('This is an override');

    $this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');
    $this->node = $this->nodeStorage->load($this->node->id());

    $this->drupalLogout();
    $this->setUpAuthorization('ALL');

    $this->provisionResource([static::$format], ['basic_auth']);
  }

  /**
   * {@inheritdoc}
   */
  protected function request($method, Url $url, array $request_options = []) {
    $request_options[RequestOptions::HEADERS] = [
      'Content-Type' => static::$mimeType,
    ];
    $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions($method));
    $request_options[RequestOptions::QUERY] = ['_format' => static::$format];
    return parent::request($method, $url, $request_options);
  }

  /**
   * {@inheritdoc}
   */
  protected function setUpAuthorization($method) {
    $permissions = array_keys($this->container->get('user.permissions')->getPermissions());
    // Give the test user all permissions on the site. There should be no
    // permission that gives the user access to layout sections over REST.
    $this->account = $this->drupalCreateUser($permissions);
  }

  /**
   * {@inheritdoc}
   */
  protected function assertResponseWhenMissingAuthentication($method, ResponseInterface $response) {}

  /**
   * {@inheritdoc}
   */
  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {}

  /**
   * {@inheritdoc}
   */
  protected function assertAuthenticationEdgeCases($method, Url $url, array $request_options) {}

  /**
   * {@inheritdoc}
   */
  protected function getExpectedUnauthorizedAccessCacheability() {}

  /**
   * Gets the decoded contents.
   *
   * @param \Psr\Http\Message\ResponseInterface $response
   *   The response.
   *
   * @return array
   *   The decoded contents.
   */
  protected function getDecodedContents(ResponseInterface $response) {
    return $this->serializer->decode((string) $response->getBody(), static::$format);
  }

}
Loading