Problem/Motivation

Since config entities have string-based IDs, their paths have to be careful with clashes.
If you have /entity_type/{entity_type} and /entity_type/add, you cannot have a config entity of that type called "add". We had this problem with vocabularies, and had to restructure all of their paths.

When overriding routes for something like /node/{node}, the router cannot distinguish between /node/1/ and /node/add. Book module works around this for /admin/structure/book/{node} by specifying a requirement in the route definition.

This should be done everywhere there is a potential for conflict.

Proposed resolution

Fix this in known places that specify their routes directly.
Fix DefaultHtmlRouteProvider to do this automatically

Note that this is purely a "route fit" hardening, and it is not possible to cause any breakage.
It merely helps the router determine in advance that /node/add does not meet the requirements to be a candidate for /node/{node}

Remaining tasks

N/A

User interface changes

N/A

API changes

N/A

Data model changes

N/A

RC Target Justification:
In order for dynamic code to be able to distinguish between conflicting routes like /node/add and /node/{node}, this requirement must be specified.
Without this, any contrib or custom code will need to independently determine the requirements for any routes they override.
Also, our current routing system seems to work by coincidence, and without this hardening, route matching could break in the future.

Note that this is purely a "route fit" hardening, and it is not possible to cause any breakage.
It merely helps the router determine in advance that /node/add does not meet the requirements to be a candidate for /node/{node}

This patch manually changes all custom *.routing.yml definitions for entity type with serial IDs, and the DefaultHtmlRouteProvider will only alter routes if the "id" field is explicitly set to be an integer.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

tim.plunkett’s picture

+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -1243,6 +1187,50 @@ public static function _fieldSqlSchema(FieldInterface $field, array $schema = NU
+    $entity_manager = \Drupal::entityManager();
+    $info = $entity_manager->getDefinition($entity_type);
+    $definitions = $entity_manager->getFieldDefinitions($entity_type);
+
+    // Define the entity ID schema based on the field definitions.
+    $id_definition = $definitions[$info->getKey('id')];
+    if ($id_definition->getType() == 'integer') {
tim.plunkett’s picture

Title: DefaultHtmlRouteProvider should add a route requirement for entity types with serial IDs » Entity types with serial IDs should specify this requirement in their route definition.
Issue summary: View changes
Issue tags: +rc target triage

Merging the two issues. This one has a better nid :)

tim.plunkett’s picture

Status: Active » Needs review
FileSize
9.21 KB
dawehner’s picture

+++ b/core/modules/block_content/block_content.routing.yml
+++ b/core/modules/block_content/block_content.routing.yml
@@ -44,6 +44,7 @@ entity.block_content.canonical:

@@ -44,6 +44,7 @@ entity.block_content.canonical:
     _admin_route: TRUE
   requirements:
     _entity_access: 'block_content.update'
+    block_content: \d+
 
 entity.block_content.edit_form:
   path: '/block/{block_content}'

Let's also add it onto edit/delete/view and maybe even more things like all the revision UI ones.

tim.plunkett’s picture

Also realized that getFieldStorageDefinitions only supports fieldable entity types.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -128,8 +170,33 @@ protected function getDeleteFormRoute(EntityTypeInterface $entity_type) {
+      return NULL;

It is still odd to return NULL in that, but I just guess there is no such thing as 'any' or 'mixed' in the typed data world.

Also realized that getFieldStorageDefinitions only supports fieldable entity types.

Does that mean we ideally should add some test coverage for a test config entity type?

tim.plunkett’s picture

Added test coverage.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

This seems like a good tightening of the route definitions to me. +1.

I defer to catch if it's RC-OK, but it's all straightforward enough and testbot is happy with it. If Tim says it causes problems for Page Manager without this, I will take his word for it. :-)

xjm’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update

This seems like a pretty significant BC break. If I have an entity type with serial entity IDs, what happens to it after this patch?

tim.plunkett’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

No BC break. Updated the issue summary.

tim.plunkett’s picture

Status: Needs work » Reviewed & tested by the community
xjm’s picture

Issue tags: -rc target triage +rc target

Thanks @tim.plunkett! Based on that, @effulgentsia and I agreed on this being an RC target.

effulgentsia’s picture

This patch looks great. Just a couple questions:

+++ b/core/modules/link/src/Plugin/Validation/Constraint/LinkNotExistingInternalConstraintValidator.php
@@ -50,9 +52,17 @@ public function validate($value, Constraint $constraint) {
+        catch (MissingMandatoryParametersException $e) {
+          $allowed = FALSE;
+        }

What is triggering this now that wasn't in HEAD? Or in other words, are we sure we have test coverage for it?

Without this, any contrib or custom code will need to independently determine the requirements for any routes they override.
Also, our current routing system seems to work by coincidence, and without this hardening, route matching could break in the future.

What do you mean by this? RouteProvider::getRoutesByPath() returns matched routes ordered by the 'fit' database column, which favors exact path parts over variable slugs. So why is this "by coincidence" and what prevents contrib/custom code from trusting the preference order returned by it? Note that I think this patch is very sensible regardless, but just trying to understand how it's a contrib blocker.

xjm’s picture

I also wondered about how to document this element of the route definition. @tim.plunkett said that eventually most entity types should probably use EntityRouteProviderInterface instead of needing to define *.routing.yml entries. But in the interim, it should be discoverable somehow what these mean.

effulgentsia’s picture

xjm’s picture

Maybe just a note on https://www.drupal.org/node/2092643 then?

tim.plunkett’s picture

LinkFieldTest was triggering those exceptions.

In HEAD, LinkNotExistingInternalConstraintValidator calls $url->toString(); and only catches RouteNotFoundException, because that is the most common. But those other two exceptions are documented as @throws (a bit up the call stack), so we should catch them.

As to the second part. In HEAD, when going to /node/add, two routes are retrieved from the route provider: node.add_page and entity.node.canonical. They *are* correctly ordered in this case. If no additional route filters run, they will continue to exist in this order, and the node add route will be chosen first.

But when overriding the /node/{node} route, we trigger our route filter for any collection containing a page_manager owned route.
In HEAD (without Page Manager's workaround for node/{node} specifically), this means our route filter runs for /node/add.

In attempting to find a valid page_manager route, the route filter reorganizes the collection, and the node.add_page is no longer found.

But *the route filter should not run at all!* This change prevents it from being picked up at all for that route.
So in that sense, it's a performance improvement as well.

catch’s picture

entity.comment.canonical:
   path: '/comment/{comment}'
@@ -41,6 +43,7 @@ entity.comment.canonical:
     _controller: '\Drupal\comment\Controller\CommentController::commentPermalink'
   requirements:
     _entity_access: 'comment.view'
+    comment: \d+

The path has {comment} in it, so why can't we figure out when building the router that {comment} needs an integer?

It's a bit odd specifying this in places like the book printer friendly page.

tim.plunkett’s picture

Originally I was going to just do it for entity routes. Those are the ones really care about.

@dawehner convinced me to do it everywhere, to be consistent.

I thought about doing further work in EntityRouteAlterSubscriber/EntityResolverManager directly, but I didn't think forcibly checking this everywhere was a good idea.

catch’s picture

I don't really think it's consistent - there's a difference between defining an entity route (which we might automate away for core entities too), and something like book which is adding its own tab. The latter can't be automated away from routing.yml.

However this is just adding information to the route definition, so we could look at doing that centrally or removing it again later if we find a better way to do it.

catch’s picture

Title: Entity types with serial IDs should specify this requirement in their route definition. » Route definitions using entity type paramconverters with serial IDs should add an integer requirement

Also issue title doesn't match the patch, this is more what the patch is actually doing.

catch’s picture

And this could go into a patch release as far as I can see - no API addition/deprecation at all.

tim.plunkett’s picture

The regex requirements key couldn't care less about paramconverters.
This is about integers, I don't care if they turn into entities later.

catch’s picture

This should be done everywhere there is a potential for conflict.

Is there anywhere this is done which is not using the entity paramconverter?

db log could potentially run into it, if someone added /admin/reports/dblog/event/foo (which any contrib module could do), but that's not touched here.

Also how does book module know that the print needs an integer entity ID - the only convention here is that these are actually entity IDs in the path and we know that those are ints, there is no other indication that would ever lead you to add that requirement. Page manager might not care that they're entities, people writing route definitions certainly will.

effulgentsia’s picture

Status: Reviewed & tested by the community » Fixed

However this is just adding information to the route definition, so we could look at doing that centrally or removing it again later if we find a better way to do it.

Exactly. I think getting this in before RC4 is better than not, as it gets the route definitions to have a registered requirement for what is currently only an implicit requirement. But, I like catch's idea of centralizing where the requirement gets registered, instead of making every route do it, and I don't see any reason such centralization couldn't be committed in a patch release.

Therefore, pushed to 8.0.x.

dawehner’s picture

Note: This issue requires people to rebuild the container, just saying, see #2614866: Update rc3 to rc4 fails

catch’s picture

  • effulgentsia committed 7114295 on 8.1.x
    Issue #2600666 by tim.plunkett: Route definitions using entity type...

Status: Fixed » Closed (fixed)

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