Problem/Motivation

Subissue of #2300677: JSON:API POST/PATCH support for fully validatable config entities

Given that full REST support requires for example config validation we limit the scope for now onto GET requests.
All what GET requests need is a way to serialize those entities, which is already given using the serializer API.

Proposed resolution

  • Just provide GET routes for config entities, remove POST, PUT, DELETE, and PATCH routes which are available now but don't actually work.
  • The permission logic will not change. It uses entity view access for all entities on GET. Since core config entities don't have separate view permissions core config entities will rely on admin access permissions. At least one config entity, field_storage_config, doesn't provide admin permissions so will effectively it will not be available via REST because there is currently no way to grant entity view access. There may be other config entities that have this problem.
  • Fix a couple of places in the code where we assume fieldable entities, so just run it in those cases
  • Since most configuration entities do not canonical links they will be available at the path 'entity/[ENTITY_TYPE]/[ENTITY_ID]'.
    For example to retrieve the tags vocabulary use.
    curl --user admin:admin --request GET "http://drupal.d8/entity/taxonomy_vocabulary/tags?_format=json"
  • Configuration entities that do have canonical links, contact forms are the only example in core, will be available at their canonical link and not the path pattern above.

    For instance to retrieve the Feedback contact form you can use:
    curl --user admin:admin --request GET "http://drupal.d8/contact/feedback?_format=json"

Remaining tasks

None

User interface changes

None

API changes

Removed methods 'POST', 'PUT', 'DELETE', 'PATCH' from Config Entity resources. While this methods were available before this change they would result in fatal errors if you actually tried to use them.

Data model changes

None

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner created an issue. See original summary.

dawehner’s picture

Copying the existing patch file from the other issue

dawehner’s picture

Status: Active » Needs review

.

Status: Needs review » Needs work

The last submitted patch, 2: 2300677-70.patch, failed testing.

clemens.tolboom’s picture

tedbow’s picture

Title: Provide a read only rest config » Provide a read only(GET) REST for Configuration Entities
Status: Needs work » Needs review
FileSize
14.72 KB
14.72 KB

Just got rid of debug line and some old commented out lines.

In \Drupal\rest\Tests\ReadTest::testRead we have

// Only assert one example property here, other properties should be
      // checked in serialization tests.

Is this still true? Is there any use in making a ConfigTest class in REST to test or property of the test configuration objects that make sure config entities are being returned correctly?
If not then we probably won't need ConfigTest class until we support updating them.

tedbow’s picture

clemens.tolboom’s picture

Issue summary: View changes
curl --user admin:admin --request GET "http://drupal.d8/entity/taxonomy_vocabulary/tags?_format=json" | json_pp

{
   "description" : "Use tags to group articles on similar topics into categories.",
   "hierarchy" : 0,
   "weight" : 0,
   "_core" : {
      "default_config_hash" : "lO5ziR5dVI1PpEeHZsSOfQ-Y7NWihSDKW8-MMf6uoms"
   },
   "langcode" : "en",
   "vid" : "tags",
   "uuid" : "aba325c0-c57f-4bea-8daa-4792133eeb29",
   "dependencies" : [],
   "status" : true,
   "name" : "Tags"
}

What is this _core? Should we filter out '_' values?
Is it OK to expose default_config_hash?

clemens.tolboom’s picture

Status: Needs review » Needs work

Using a non-admin user + basic auth I get access denied although having REST permission to view taxonomy is set.

curl --user clemens:clemens --request GET "http://drupal.d8/entity/taxonomy_vocabulary/tags?_format=json&XDEBUG_SESSION_START=12889" | json_pp
{
   "error" : ""
}

The code checks for 'administer taxonomy' which make reading a vocabulary useless for getting the name of the vocabulary.

//core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php:149
    if ($admin_permission = $this->entityType->getAdminPermission()) {
      return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission());
    }

We need view permission for config entities.

clemens.tolboom’s picture

+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -48,13 +50,16 @@ public function get(EntityInterface $entity) {
@@ -95,9 +100,11 @@ public function post(EntityInterface $entity = NULL) {

@@ -95,9 +100,11 @@ public function post(EntityInterface $entity = NULL) {
     // Only check 'edit' permissions for fields that were actually
     // submitted by the user. Field access makes no difference between 'create'
     // and 'update', so the 'edit' operation is used here.
-    foreach ($entity->_restSubmittedFields as $key => $field_name) {
-      if (!$entity->get($field_name)->access('edit')) {
-        throw new AccessDeniedHttpException("Access denied on creating field '$field_name'");
+    if ($entity instanceof FieldableEntityInterface) {
+      foreach ($entity->_restSubmittedFields as $key => $field_name) {
+        if (!$entity->get($field_name)->access('edit')) {
+          throw new AccessDeniedHttpException("Access denied on creating field '$field_name'");
+        }
       }
     }
 
@@ -109,7 +116,13 @@ public function post(EntityInterface $entity = NULL) {

@@ -109,7 +116,13 @@ public function post(EntityInterface $entity = NULL) {
 
       // 201 Created responses return the newly created entity in the response
       // body.
-      $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+      if ($entity->getEntityType()->hasLinkTemplate('canonical')) {
+        $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+      }
+      else {
+        $url = Url::fromRoute('<front>')->setAbsolute()->toString(TRUE);
+      }
+
       $response = new ResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
       // Responses after creating an entity are not cacheable, so we add no
       // cacheability metadata here.
@@ -212,6 +225,9 @@ public function delete(EntityInterface $entity) {

@@ -212,6 +225,9 @@ public function delete(EntityInterface $entity) {
    *   If validation errors are found.
    */
   protected function validate(EntityInterface $entity) {
+    if (!$entity instanceof FieldableEntityInterface) {
+      return;
+    }
     $violations = $entity->validate();

This [edit]post code[/edit] belongs not in this issue as this is about to view a config entity

dawehner’s picture

What is this _core? Should we filter out '_' values?
Is it OK to expose default_config_hash?

I would agree we should filter them out. They seem to be pretty internal.

We need view permission for config entities.

Total good point! It feels like having this as a follow up seems to be a good idea. It conceptually doesn't belong into the issue we have.

This [edit]post code[/edit] belongs not in this issue as this is about to view a config entity

Fair point.

tedbow’s picture

Assigned: Unassigned » tedbow

Assigning to myself to work on the issues noted above.

tedbow’s picture

What is this _core? Should we filter out '_' values?
Is it OK to expose default_config_hash?
I would agree we should filter them out. They seem to be pretty internal.

Was looking into this. It seems weird to be filtering out different values.

I could see a use case you might want to default_config_hash externally.

What if you had a decoupled admin application that was maintaining multiple Drupal sites. If the application was keeping config entities on their side default_config_hash would be 1 way of keeping track of which config entities came from environments that were from the same install(dev, stage, prod of the same site).

tedbow’s picture

Assigned: tedbow » Unassigned
FileSize
13.21 KB
1.81 KB

Removed post code from the patch

tedbow’s picture

Status: Needs work » Needs review
clemens.tolboom’s picture

@tedbow thanks for cleaning up a bit.

My #9 is not fixed. Do you have any idea?

tedbow’s picture

@clemens.tolboom I agree with @dawehner in #11

Total good point! It feels like having this as a follow up seems to be a good idea. It conceptually doesn't belong into the issue we have.

We should make a follow up issue for exposing permissions for Config Entities once this is fixed

Status: Needs review » Needs work

The last submitted patch, 14: 2724823-14.patch, failed testing.

clemens.tolboom’s picture

Hmmm I miss read #11.

We need to make clear this patch only works for users with permission administer taxonomy [edit]or other permissions depending on request.[/edit]

Wim Leers’s picture

Title: Provide a read only(GET) REST for Configuration Entities » EntityResource: read-only (GET) support for configuration entities
Category: Plan » Task
Priority: Normal » Major
  1. +++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
    @@ -229,6 +229,10 @@ public function title(RouteMatchInterface $route_match, EntityInterface $_entity
    +  public function configController() {
    +    return [];
    +  }
    

    I think this can be deleted? :)

  2. +++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
    @@ -0,0 +1,36 @@
    +/**
    + * @file
    + * Contains \Drupal\Core\Entity\Routing\EmptyRouteProvider.
    + */
    

    Nit: we don't do this anymore since 8.2.

  3. +++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
    @@ -0,0 +1,36 @@
    +class EmptyRouteProvider implements EntityRouteProviderInterface {
    ...
    +    if ($link_template = $entity_type->getLinkTemplate('canonical')) {
    +      $entity_type_id = $entity_type->id();
    +      $route = new Route($link_template);
    +      $route
    +        ->setDefault('_controller', '\Drupal\Core\Entity\Controller\EntityController::configController')
    +        ->setRequirement('_entity_access', "{$entity_type_id}.view")
    +        ->setOption('parameters', [
    +          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
    +        ]);
    +      $routes->add("entity.{$entity_type_id}.canonical", $route);
    +    }
    
    +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php
    @@ -20,7 +20,10 @@
    + *       "default" = "\Drupal\Core\Entity\Routing\EmptyRouteProvider",
    

    I don't understand this name at all.

    It's meant to be another default route provider I think, but then it then omits "default" from the name unlike \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider.

    Even more confusingly, it's called "Empty". Why? It's not always empty.

  4. +++ b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
    @@ -69,6 +69,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +          'serialization_context' => [
    +            'entity_type' => $entity_type_id,
    +          ],
    
    +++ b/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
    @@ -17,6 +17,13 @@
    +  protected $defaults = [
    +    'serialization_context' => [],
    +  ];
    
    +++ b/core/modules/rest/src/RequestHandler.php
    @@ -57,7 +58,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
    -          $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method));
    +          $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method) + $definition['serialization_context']);
    

    Do we need this? AFAICT it's not used anywhere. I suspect it was used for methods other than GET, but that means this is out of scope here too.

  5. +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
    @@ -48,13 +50,16 @@ public function get(EntityInterface $entity) {
    +    if ($entity instanceof FieldableEntityInterface) {
    
    @@ -212,6 +217,9 @@ public function delete(EntityInterface $entity) {
    +    if (!$entity instanceof FieldableEntityInterface) {
    

    Why not typehint to ContentEntityTypeInterface? I suspect I know why, but this should be explicitly documented.

dawehner’s picture

Why not typehint to ContentEntityTypeInterface? I suspect I know why, but this should be explicitly documented.

Fair question. The code in the next line though always deals with iterating over fields, so its something that is bound to fieldable entities. We should use the entities which are out there.

It's meant to be another default route provider I think, but then it then omits "default" from the name unlike \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider.

Even more confusingly, it's called "Empty". Why? It's not always empty.

Well, the requirement here is to provide a canonical route. Here we provide an empty controller in order to not have to think about how to implement a view builder for a config entity.
Its empty in the sense of rendering nothing.

I think this can be deleted? :)

See before

Do we need this? AFAICT it's not used anywhere. I suspect it was used for methods other than GET, but that means this is out of scope here too.

Right, many of those changes have been bound to the idea of trying to support POST/PATCH etc.

tedbow’s picture

Status: Needs work » Needs review
FileSize
3.73 KB
82.77 KB

Here is patch the removes the serialization_context logic because it is not need for read.

It also adds comments that explains EmptyRouteProvider

Status: Needs review » Needs work

The last submitted patch, 22: 2724823-22.patch, failed testing.

Wim Leers’s picture

82 KB patch vs 13 KB before. Something unwanted snuck in there :)

tedbow’s picture

Status: Needs work » Needs review
FileSize
11.9 KB
3.73 KB

Whoops!!!

Okay here is an updated patch.

Wim Leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
    @@ -230,6 +230,18 @@ public function title(RouteMatchInterface $route_match, EntityInterface $_entity
       /**
    +   * Provides a generic callback for Configuration Entities.
    +   *
    +   * @return array
    +   *   An empty render array.
    +   */
    +  public function configController() {
    +    // Configuration entities do not have common view concept so default to an
    +    // empty array.
    +    return [];
    +  }
    

    The reason we have this was given in #21: Well, the requirement here is to provide a canonical route. Here we provide an empty controller in order to not have to think about how to implement a view builder for a config entity.

    Is it really impossible to get around this? Having this controller is very confusing/icky.

    If we need this, then why do we have this in EntityResource:

     *     "canonical" = "/entity/{entity_type}/{entity}",
    

    and this in EntityDeriver:

            $default_uris = array(
              'canonical' => "/entity/$entity_type_id/" . '{' . $entity_type_id . '}',
              'https://www.drupal.org/link-relations/create' => "/entity/$entity_type_id",
            );
    
            foreach ($default_uris as $link_relation => $default_uri) {
              // Check if there are link templates defined for the entity type and
              // use the path from the route instead of the default.
              if ($link_template = $entity_type->getLinkTemplate($link_relation)) {
                $this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $link_template;
              }
              else {
                $this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $default_uri;
              }
            }
    

    Why can't we rely on those defaults?

    Because we don't want /entity/vocabulary/tags, but we want /vocabulary/tags instead? If so, we should be updating all of the config entities' annotations too, otherwise this just works for config_test entities, which is less than helpful.

    This also indicates this is missing test coverage for config entity types other than config_test. Finally, this needs test coverage for a config entity type that does not have a canonical route. Because having a canonical route is an opt-in thing, so we must verify the behavior for those config entity types that don't have it.

  2. +++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
    @@ -0,0 +1,40 @@
    + * view route but need a canonical route for REST requests
    

    Nit: missing trailing period.

  3. +++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
    @@ -0,0 +1,40 @@
    + * ¶
    

    Nit: Trailing space.

  4. +++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
    @@ -0,0 +1,40 @@
    +class EmptyRouteProvider implements EntityRouteProviderInterface {
    

    I'd rename this to EmptyCanonicalRouteProvider.

  5. +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
    @@ -48,13 +50,16 @@ public function get(EntityInterface $entity) {
    +    if ($entity instanceof FieldableEntityInterface) {
    
    @@ -212,6 +217,9 @@ public function delete(EntityInterface $entity) {
    +    if (!$entity instanceof FieldableEntityInterface) {
    

    Can we document why we're not checking \Drupal\Core\Entity\ContentEntityInterface? #21 contains the answer.

dawehner’s picture

This also indicates this is missing test coverage for config entity types other than config_test. Finally, this needs test coverage for a config entity type that does not have a canonical route. Because having a canonical route is an opt-in thing, so we must verify the behavior for those config entity types that don't have it.

When I remember correctly the point is that all kind of other code reacts to the canonical link path being there. Stuff like config translation calls to $entity->toUrl('canonical') etc. , its not all about rest. There should be a test failure in the original issue about it: mh, the only one I could find is https://www.drupal.org/pift-ci-job/162268 but I remember having issues with stuff like that in the past.

Wim Leers’s picture

When I remember correctly the point is that all kind of other code reacts to the canonical link path being there.

What are all those things then? And why is it not broken today?

Stuff like config translation calls to $entity->toUrl('canonical')

So how can config translation work today then?

dawehner’s picture

What are all those things then? And why is it not broken today?

Well, feel free to remove it, but I seriously added it not just for the fun of it, but rather based upon subconsciousness from a lot of experience with #2350509: Implement auto-route generation for all core entities and convert all of the core entities. and co.

Wim Leers’s picture

Hah! :) I don't think at all you added it for fun! But I think for this patch to be committable, we at least need to understand why canonical URLs for config entities are a necessity.

I can only speak for myself, of course. And I have to say that I personally don't understand why we need this. We don't seem to be using it. As I said #26.1, I don't understand why we're adding a canonical route for config_test, but not for any other entity type. That makes it look like this issue itself doesn't actually add read-only/GET support for config entities: it looks like this only adds support for reading config_test config entities. That's a good step, but doesn't do what the issue title says.
So, assuming that we indeed must add a canonical route for every single config entity type, that means:

  1. we need a follow-up issue for updating every single config entity type in Drupal 8 core
  2. worse: it means contrib/custom config entity types actually won't be available over REST!

I sincerely hope that I'm wrong on both counts.

I hope this clarified where I'm coming from.


I also just grepped #2350509: Implement auto-route generation for all core entities and convert all of the core entities. for comments mentioning config entities. And based on #2350509-178: Implement auto-route generation for all core entities and convert all of the core entities., #2350509-201: Implement auto-route generation for all core entities and convert all of the core entities. and #2350509-202: Implement auto-route generation for all core entities and convert all of the core entities., it doesn't look like there was even agreement on how to deal with canonical for config entities there. So it seems wrong to start introducing it here?

dawehner’s picture

Hah! :) I don't think at all you added it for fun! But I think for this patch to be committable, we at least need to understand why canonical URLs for config entities are a necessity.

Total fair point!

we need a follow-up issue for updating every single config entity type in Drupal 8 core
worse: it means contrib/custom config entity types actually won't be available over REST!

Well, this is why we have change records ... Let's assume we validate the canonical as part of rest.settings we could provide a super helpful exception, can't we?

it doesn't look like there was even agreement on how to deal with canonical for config entities there. So it seems wrong to start introducing it here?

Yeah its tricky. We have no concept of how to render a config entity, and well, don't render anything, seemed to be a good compromise in this issue.

Wim Leers’s picture

So, per #26-#31, this needs documentation, a change record and an issue summary update. I think mostly the same text can be shared among them.

Per #26.1, I think this needs tests to show how this behaves for a config entity type that doesn't have a canonical route.

tedbow’s picture

Status: Needs work » Needs review
FileSize
10.67 KB
11.88 KB

Ok I removed the need in the test to have a canonical url for config entities.

This got rid of the EmtpyRouteProvider and updated ReadTest to handle entities without canonical urls.
So now config_test entity does not have a canonical url.

I have not addressed any other issues in this patch

dawehner’s picture

+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -93,23 +96,32 @@ public function testRead() {
-    $response = $this->httpRequest($account->urlInfo('canonical')->setRouteParameter('_format', $this->defaultFormat), 'GET');
-    // \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
-    // non-REST route a match, but a lower quality one: no format restrictions
-    // means there's always a match and hence when there is no matching REST
-    // route, the non-REST route is used, but can't render into
-    // application/hal+json, so it returns a 406.
-    $this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
-    $this->assertEqual($response, Json::encode([
-      'message' => 'Not acceptable format: hal_json',
-    ]));
+    $response = $this->httpRequest($this->getReadUrl($entity), 'GET');
+
+    if ($entity->hasLinkTemplate('canonical')) {
+      // \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
+      // non-REST route a match, but a lower quality one: no format restrictions
+      // means there's always a match and hence when there is no matching REST
+      // route, the non-REST route is used, but can't render into
+      // application/hal+json, so it returns a 406.
+      $this->assertResponse('406', 'HTTP response code is 406 when the resource does not define formats, because it falls back to the canonical, non-REST route.');
+      $this->assertEqual($response, Json::encode([
+        'message' => 'Not acceptable format: hal_json',
+      ]));
+    }
+    else {
+      // If there is not canonical link template we will get a 403 error.
+      // @todo Is this correct response? Why not a 404?
+      $this->assertResponse('403');
+    }

Mh, so we change the expected behaviour in the test rather than expecting the behaviour which will happen on the actual site? This feels wrong, honestly. Won't have those entities actually have canonical link templates?

e0ipso’s picture

I'll make sure to follow up on #2733097: [FEATURE] Add GET support for configuration entities when this gets in. It feels that we may be able to reuse code/concepts from this patch.

tedbow’s picture

Update the patch

Mh, so we change the expected behaviour in the test rather than expecting the behaviour which will happen on the actual site? This feels wrong, honestly. Won't have those entities actually have canonical link templates?

This whole change that was being commented on shouldn't have gone in. It was a problem in how I read the original patch. It actually is checking for access to the user entity, $account, because that resource is NOT rest enabled. I updated the comment so that it will be more obvious.

It seems like this patch now works expect that it sounds like we are going to address the permission of viewing configuration entities in another follow up issue. So that would be mean if this patch got in only user with "administer [CONFIG ENTITY TYPE]" could actually use rest to View configuration entities

dawehner’s picture

Issue summary: View changes
Status: Needs review » Needs work

It seems like this patch now works expected

Well, just because something is working doesn't mean something is ready . :)

  1. I think we really should not enable those routes as long we don't have any form of validation in place:
    mysql> select name from router where name like "%rest%";
    +----------------------------------------------+
    | name                                         |
    +----------------------------------------------+
    | rest.entity.node.POST                        |
    | rest.entity.taxonomy_vocabulary.POST         |
    | rest.entity.taxonomy_vocabulary.DELETE       |
    | rest.entity.taxonomy_vocabulary.GET.hal_json |
    | rest.entity.taxonomy_vocabulary.PATCH        |
    | rest.entity.node.DELETE                      |
    | rest.entity.node.GET.hal_json                |
    | rest.entity.node.PATCH                       |
    | rest.csrftoken                               |
    +----------------------------------------------+
    
  2. We should have explicit test coverage that POSTing config entities doesn't work yet

It seems like this patch now works expect that it sounds like we are going to address the permission of viewing configuration entities in another follow up issue. So that would be mean if this patch got in only user with "administer [CONFIG ENTITY TYPE]" could actually use rest to View configuration entities

Which, if you think about it, is kind of a legit behaviour. Most of the usecases are some form of special admin UI.

  1. +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
    @@ -223,6 +228,9 @@ public function delete(EntityInterface $entity) {
       protected function validate(EntityInterface $entity) {
    +    if (!$entity instanceof FieldableEntityInterface) {
    +      return;
    +    }
    

    Adding a todo referencing #2164373: [META] Untie config validation from form validation — enables validatable Recipes, decoupled admin UIs … would be great I think

  2. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +135,50 @@ public function testResourceStructure() {
    +  protected function getReadUrl(EntityInterface $entity, $format = NULL, $entity_id = NULL) {
    +    if (!$format) {
    +      $format = $this->defaultFormat;
    +    }
    +    if (!$entity_id) {
    +      $entity_id = $entity->id();
    +    }
    +    $entity_type = $entity->getEntityTypeId();
    +    if ($entity->hasLinkTemplate('canonical')) {
    +      $url = $entity->toUrl('canonical');
    +    }
    +    else {
    +      $route_name = 'rest.entity.' . $entity_type . ".GET.";
    +      // If testing unsupported format don't use the format to construct route
    +      // name. This would give a route not found exception.
    +      if ($format == 'wrongformat') {
    +        $route_name .= $this->defaultFormat;
    +      }
    +      else {
    +        $route_name .= $format;
    +      }
    +      $url = Url::fromRoute($route_name);
    +    }
    ...
    +    $url->setRouteParameter('_format', $format);
    +    return $url;
    +  }
    +
    

    For me it still feels wrong, given that means that user level code cannot just use $entity->toUrl(), but people seem to not worry about it.

    Nitpick: The $url->setRouteParameter($entity_type, $entity_id) call could be moved inside the else.

tedbow’s picture

Status: Needs work » Needs review
FileSize
4.77 KB
12.02 KB

Here is patch that addresses #37 and also add taxonomy_vocabulary entity to ReadTest

I think we really should not enable those routes as long we don't have any form of validation in place:

I chatted with @dawehner on IRC about this. He was in favor of leaving the routes but adding validation saying methods other than GET aren't supported on config entities.

I this patch I overwrote availableMethods in EntityResource to remove these methods. To me this seems better because:

  1. availableMethods "Returns the available HTTP request methods on this plugin." which seems like should not include methods PUT, PATCH, DELETE on config entities
  2. If we left the methods there then the permission would also be exposed which would be confusing
  3. The restui module would allow you to configure them. I check this patch removes them from configuration

Nitpick: The $url->setRouteParameter($entity_type, $entity_id) call could be moved inside the else.

I intentionally left this outside the else because if the method is receiving a value for $entity_id to test creating a URL for an non-existing entity then you would want to override it in both cases. Setting it if you are just using $entity->id() also doesn't cause a problem.

Wim Leers’s picture

Issue tags: -Needs tests

I wrote this in #32:

So, per #26-#31, this needs documentation, a change record and an issue summary update. I think mostly the same text can be shared among them.

Per #26.1, I think this needs tests to show how this behaves for a config entity type that doesn't have a canonical route.

The first thing is yet to be done. The second thing is effectively addressed. Although I personally would like to see proof that more config entities can be read, e.g. block, field_storage_config, role.


I don't have any actual further complaints besides nitpicks. Leaving at NR.

  1. +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
    @@ -256,4 +264,32 @@ protected function getBaseRoute($canonical_path, $method) {
    +      // Currently only get is supported for Config Entities.
    

    Nit: s/get/GET/

  2. +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -367,6 +376,12 @@ protected function entityPermissions($entity_type, $operation) {
    +        // Currently only view is support by rest for configuration entities.
    

    Nit: s/view/viewing/, s/rest/REST/

  3. +++ b/core/modules/rest/src/Tests/RESTTestBase.php
    @@ -367,6 +376,12 @@ protected function entityPermissions($entity_type, $operation) {
    +          // Currently there is no view permissions on configuration entities.
    

    IMO useless comment, because the prior comment already implies this.

  4. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -91,6 +121,7 @@ public function testRead() {
         ]));
    +
       }
    

    Unnecessary change.

  5. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +142,50 @@ public function testResourceStructure() {
    +   *   The entity to get the url for.
    

    s/url/URL/

  6. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +142,50 @@ public function testResourceStructure() {
    +   *   The format for the URL.
    

    Not the format for the URL… The format to request the entity in.

  7. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +142,50 @@ public function testResourceStructure() {
    +   *   The entity id to use in the URL.
    +   *   Defaults to the entity's id if know given.
    

    Single line, and s/id/ID/

  8. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +142,50 @@ public function testResourceStructure() {
    +   *   The URL object.
    

    s/URL/Url/

  9. +++ b/core/modules/rest/src/Tests/ReadTest.php
    @@ -111,8 +142,50 @@ public function testResourceStructure() {
    +      // name. This would give a route not found exception.
    

    s/route not found exception/RouteNotFoundException/

tedbow’s picture

Status: Needs review » Needs work
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -256,4 +264,32 @@ protected function getBaseRoute($canonical_path, $method) {
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);

Just noticed I didn't update this to use dependency injection. Fixing this and other issue in @Wim Leer's review

tedbow’s picture

Status: Needs work » Needs review
FileSize
10.72 KB
17.95 KB

Ok here is the patch that addresses @Wim Leers' review in #39. It also has 2 fails \Drupal\rest\Tests\ReadTest::testRead which I will explain.

Although I personally would like to see proof that more config entities can be read, e.g. block, field_storage_config, role.

I have added testing for these entities. We could add more but could do that as part of #2737719: EntityResource: Provide comprehensive test coverage: for every entity type, every format, every method

Adding field_storage_config ran into 2 problems.

  1. Field UI had to be enabled because the permission for field entities are defined there. @see #2562855: Fields cannot be administered without Field UI module
  2. Even with the "administer node fields" permission you still can't read a field storage config on nodes. This is because there is no single admin permission for fields and there is not an entity access controller for Field Storage entities like there is for field Config entities(FieldConfigAccessControlHandler). If you look at \Drupal\field_ui\Routing\RouteSubscriber you will see array('_permission' => 'administer ' . $entity_type_id . ' fields'), for field storage but _entity_access used for field config routes.

This feels like a separate issue where FieldStorageAccessControlHandler would be introduced and \Drupal\field_ui\Routing\RouteSubscriber should updated to use it.

But I wanted to put this patch with failing test out there and get feedback. IMO we should remove field_storage_config from the entities to check because we know it will fail for none REST reasons, i.e. in \Drupal\rest\Plugin\rest\resource\EntityResource

  public function get(EntityInterface $entity) {
    $entity_access = $entity->access('view', NULL, TRUE);
    if (!$entity_access->isAllowed()) {
      throw new AccessDeniedHttpException();
    }

is correct but fails because the problem mentioned.

So, per #26-#31, this needs documentation, a change record and an issue summary update. I think mostly the same text can be shared among them.

This remains to be done.

Status: Needs review » Needs work

The last submitted patch, 41: 2724823-41.patch, failed testing.

Wim Leers’s picture

Issue tags: -Needs documentation

This feels like a separate issue where FieldStorageAccessControlHandler would be introduced and \Drupal\field_ui\Routing\RouteSubscriber should updated to use it.

But I wanted to put this patch with failing test out there and get feedback. IMO we should remove field_storage_config from the entities to check because we know it will fail for none REST reasons

+1! This makes sense.

Thanks for adding tests proving that it works for multiple config entity types. That raises the confidence we can have in this patch.


I took care of updating the documentation:

So, remaining tasks:

  1. removal of field_storage_config from test coverage, out of scope change required for that one to work
  2. CR
  3. IS update

+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -286,7 +331,7 @@ public function availableMethods() {
   protected function isConfigEntityResource() {
     $entity_type_id = $this->getPluginDefinition()['entity_type'];
-    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
     $class = $entity_type->getClass();
     $interfaces = class_implements($class);
     return in_array('Drupal\Core\Config\Entity\ConfigEntityInterface', $interfaces);

+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -446,4 +454,60 @@ protected function assertResponseBody($expected, $message = '', $group = 'REST R
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+    $class = $entity_type->getClass();
+    $interfaces = class_implements($class);
+    return in_array('Drupal\Core\Config\Entity\ConfigEntityInterface', $interfaces);

This looks a bit wonky. But it's also what \Drupal\Core\Entity\TypedData\EntityDataDefinition::getPropertyDefinitions() does.

Note that you can make this much simpler:

$entity_type_id = …;
return $this->entityTypeManager->getDefinition($entity_type_id) instanceof \Drupal\Core\Config\Entity\ConfigEntityType;
tedbow’s picture

Status: Needs work » Needs review
FileSize
3.36 KB
17.53 KB

removal of field_storage_config from test coverage, out of scope change required for that one to work

This patch does that and simplifies \Drupal\rest\Plugin\rest\resource\EntityResource::isConfigEntityResource and \Drupal\rest\Tests\RESTTestBase::isConfigEntity according to @Wim Leers' suggestion

I took care of updating the documentation:

Thanks!
So, remaining tasks now

  1. CR
  2. IS Update

I hope to take care of those this afternoon.

Wim Leers’s picture

To me this patch looks ready.

Just one remaining nit, but keeping at Needs review:

+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -30,6 +35,48 @@
+   * @var \Drupal\Core\Entity\EntityTypeManager
...
+   * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
...
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, EntityTypeManager $entity_type_manager) {

Should typehint to the interface.

tedbow’s picture

Fixed type hints

tedbow’s picture

Issue summary: View changes
dawehner’s picture

+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -256,4 +310,29 @@ protected function getBaseRoute($canonical_path, $method) {
+    return $this->entityTypeManager->getDefinition($entity_type_id) instanceof ConfigEntityType;

+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -431,4 +452,49 @@ protected function assertResponseBody($expected, $message = '', $group = 'REST R
+    return \Drupal::entityTypeManager()->getDefinition($entity_type_id) instanceof ConfigEntityType;

One could use the ConfigEntityTypeInterface but I really don't care.

Are those issues tags still up to date?

tedbow’s picture

Mainly for follow up I did quick check to see if there were other config entity types that are like field_storage_config in that they don't have an admin permission and don't have there own access controller. From my understanding standing this would mean they won't be available for REST GET.

Here are the ones I found:

  1. config_query_test|Test configuration for query
  2. editor|Text Editor
  3. field_storage_config|Field storage
  4. rdf_mapping|RDF mapping
  5. tour|Tour
  6. entity_form_display|Entity form display
  7. entity_view_display|Entity view display
  8. base_field_override|Base field override

I think the good news is there is only 7 out 30 for the modules I enabled in core.

field_storage_config would actually be important for a headless client because as it is now it is the only way to figure out if a field is multiple value or not

dawehner’s picture

field_storage_config would actually be important for a headless client because as it is now it is the only way to figure out if a field is multiple value or not

Well, we have the other issue to figure that out ... so its really not something we have to care about.
I could imagine entity_view_display / entity_form_display being sort of helpful as well if you want to actually render it.

tedbow’s picture

Issue summary: View changes
tedbow’s picture

Issue summary: View changes
tedbow’s picture

Issue summary: View changes

Created change record https://www.drupal.org/node/2665276
and updated Issue Summary

Remaining tasks: none?!

dawehner’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs change record, -Needs issue summary update

Cool! Thank you!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 6a16932 and pushed to 8.2.x. Thanks!

+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -200,14 +201,14 @@ protected function entityCreate($entity_type) {
+   *   The id of the type of entity that should be created.

@@ -431,4 +452,49 @@ protected function assertResponseBody($expected, $message = '', $group = 'REST R
+   *   The entity type id to check.
...
+   *   The id of the type of entity that should be created.

+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -111,8 +144,50 @@ public function testResourceStructure() {
+   *   The entity id to use in the URL, defaults to the entity's ID if know

Should be ID... fixed on commit.

  • alexpott committed 6a16932 on 8.2.x
    Issue #2724823 by tedbow, dawehner, Wim Leers, clemens.tolboom:...
Wim Leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Gábor Hojtsy’s picture

Was discussing this issue in light of #2135829: [PP-1] EntityResource: translations support. The way read support is exposed for config entities here if I am not mistaken is via their current loaded values which may be in the current translation, may contain environment specific overrides, domain overrides, etc. As per the discussion in #2135829: [PP-1] EntityResource: translations support, that makes the endpoints deal with different data based on which language prefix is present in the URL for example. Is that desired? Was that considered or should there be a followup for that?

effulgentsia’s picture

Issue tags: +8.2.0 release notes