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.
Comment | File | Size | Author |
---|---|---|---|
#9 | interdiff.txt | 6.38 KB | tim.plunkett |
#9 | 2600666-entity_routes-9.patch | 23.74 KB | tim.plunkett |
#7 | interdiff.txt | 10.98 KB | tim.plunkett |
#7 | 2600666-entity_routes-7.patch | 17.36 KB | tim.plunkett |
#5 | 2600666-entity_routes-5.patch | 9.21 KB | tim.plunkett |
Comments
Comment #2
tim.plunkettSee also #1823494: Field API assumes serial/integer entity IDs, but the entity system does not
Comment #3
tim.plunkettComment #4
tim.plunkettMerging the two issues. This one has a better nid :)
Comment #5
tim.plunkettComment #6
dawehnerLet's also add it onto edit/delete/view and maybe even more things like all the revision UI ones.
Comment #7
tim.plunkettAlso realized that getFieldStorageDefinitions only supports fieldable entity types.
Comment #8
dawehnerIt 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.
Does that mean we ideally should add some test coverage for a test config entity type?
Comment #9
tim.plunkettAdded test coverage.
Comment #10
Crell CreditAttribution: Crell at Palantir.net commentedThis 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. :-)
Comment #11
xjmThis seems like a pretty significant BC break. If I have an entity type with serial entity IDs, what happens to it after this patch?
Comment #12
tim.plunkettNo BC break. Updated the issue summary.
Comment #13
tim.plunkettComment #14
xjmThanks @tim.plunkett! Based on that, @effulgentsia and I agreed on this being an RC target.
Comment #15
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThis patch looks great. Just a couple questions:
What is triggering this now that wasn't in HEAD? Or in other words, are we sure we have test coverage for it?
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.Comment #16
xjmI 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.Comment #17
effulgentsia CreditAttribution: effulgentsia at Acquia commentedSymfony documents it in http://symfony.com/doc/current/book/routing.html#adding-requirements.
Comment #18
xjmMaybe just a note on https://www.drupal.org/node/2092643 then?
Comment #19
tim.plunkettLinkFieldTest 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
andentity.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.
Comment #20
catchThe 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.
Comment #21
tim.plunkettOriginally 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.
Comment #22
catchI 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.
Comment #23
catchAlso issue title doesn't match the patch, this is more what the patch is actually doing.
Comment #24
catchAnd this could go into a patch release as far as I can see - no API addition/deprecation at all.
Comment #25
tim.plunkettThe regex requirements key couldn't care less about paramconverters.
This is about integers, I don't care if they turn into entities later.
Comment #26
catchIs 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.
Comment #27
effulgentsia CreditAttribution: effulgentsia at Acquia commentedExactly. 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.
Comment #28
dawehnerNote: This issue requires people to rebuild the container, just saying, see #2614866: Update rc3 to rc4 fails
Comment #29
catch#2507509: Service changes should not result in fatal errors between patch or minor releases should have completely fixed that for tagged core releases.