Problem/Motivation
Right now you have three ways of defining an access checker:
- implement the
StaticAccessCheckInterface
. You can add one of the strings returned by theappliesTo
to your route definition requirements key. - implement
AccessCheckInterface
for dynamic checking. - use the
_custom_access: \Drupal\mymodule\MyClass::myAccessMethod
approach in the routing requirements
While it is not particularly hard to write an appliesTo
method, the reverse is nearly impossible: to find where the actual code resides for a particular appliesTo
string in a routing definition. For a machine, you have no chance but to actually run the code. For a human you need to know very well Drupal 8 to be able to figure this out, you need to grep for a string and make sure you don't think for example that hook_entity_access has anything to do with appliesTo: _entity_access.
Proposed resolution
dawehner came up with a very easy and non-disruptive solution to this problem: nuke the appliesTo method and move the contents of it to the already existing service definition.
access_check.entity:
class: Drupal\Core\Entity\EntityAccessCheck
tags:
- { name: access_check, applies: _entity_access }
Since appliesTo() used to return an array, this also needs to support multiple keys.
Thankfully, Symfony allows for a service to be tagged twice with no extra overhead:
access_check.mymodule_checker:
class: Drupal\mymodule\Access\MyChecker
tags:
- { name: access_check, applies: _access_my_checker }
- { name: access_check, applies: _access_my_checker_alternate }
Now machines and humans alike can read a rather simple YAML file and quickly sort out what belongs where.
Remaining tasks
Code this.
User interface changes
None.
API changes
StaticAccessCheckInterface either becomes empty or is nuked altogether and the returned value of appliesTo moves to the service YAML. This can be scripted (as a drush php-script, I would not particularly want to make this a standalone one).
Related Issues
Comment | File | Size | Author |
---|---|---|---|
#51 | interdiff-2109035-51.txt | 580 bytes | damiankloip |
#51 | 2109035-51.patch | 66.53 KB | damiankloip |
#38 | interdiff.txt | 2.29 KB | tim.plunkett |
Comments
Comment #1
dawehnerWe currently have no plugins inside the routing layer. Which parts are involved in the routing layer which could be plugins as well:
I agree that some of these examples are not implemented that often though also breadcrumbs are potentially valuable as plugins.
If we move to plugins on access checkers we are less consistent inside the routing system but maybe more consistent throughout core.
So when the routes are compiled (added to the router table) each route store which access checkers apply to them. This adds
potentially N (count of times) times M (count of access checkers) calls to the applies() method. Static access checkers don't have an apply method so they don't have to be run.
AccessCheckInterface access checkers applies() method are also just executed on the COMPILE TIME.
Comment #2
Crell CreditAttribution: Crell commentedDisagree. Plugins are a good fit when you have:
1) Multiple instances of an object
2) Configured by a user
3) Through the UI
Access checkers, route enhancers, path processors, breadcrumbs... *none* of those criteria apply to any of those systems. One could have, say, a breadcrumb builder that is backed by user configuration, and that particular builder could leverage plugins internally if it makes sense to. But none of these systems "fit" the plugin use case.
"For everything else we use plugins" is simply not true.
Frequency of implementation has nothing to do with it whatsoever.
Comment #3
chx CreditAttribution: chx commented1) I am not sure what "multiple instances of an object" means in this case; very often we have only one plugin instance alive in a particular request. 2) Configured by the user -- well, what is a route YAML if not that? Is this splitting of hairs what is 'configured' means and more, what 'user' means? 3) There are quite a lot of plugin types already which have no UI and doubtful to have any in contrib.
And that all you list is in the routing system just means that the routing system made bad decisions and all those need to be plugins as well. Care to list something that is not a plugin outside of the routing system? Frequency of implementation has everything to do with it: people need to learn plugins and the more they can apply the better. I would look at this way: is there anything that makes plugins a bad fit for access checkers? Absolutely not! It's a great fit! The most common access checker, the static one, is already doing the exact same it just uses a static method to return the plugin in instead of using
@PluginId
.Comment #4
chx CreditAttribution: chx commentedComment #5
webchickI'm not comfortable this being listed as a critical DX task until / unless the issue summary is updated to explain the before/after and how this would be beneficial to people learning Drupal.
Comment #6
webchickAt a glance, though, it sounds like it might be more confusing, because it's broadening the definition of what plugins means to encompass things that don't fit its current definition, if #2 is valid.
Splitting the difference at normal, and re-adding the tag.
Comment #7
chx CreditAttribution: chx commentedUpdated the issue summary. Back to critical.
Comment #8
dawehnerIf you update the issue summary please take the last statement of #1 into account.
Comment #8.0
dawehnerUpdated issue summary.
Comment #9
chx CreditAttribution: chx commentedDone
Comment #10
dawehnerThank you very much!
Comment #10.0
dawehnerUpdated issue summary.
Comment #11
chx CreditAttribution: chx commentedBecause webchick asked in her AMA (even without mentioning names, I can read it well enough) I am willing to put up with the status quo if the internals gets documented and the DX gets fixed:
Comment #12
tstoecklerI am assuming that is an OR list, but even then we have a couple of plugin types in core that are a bad fit, in case that statement is true:
* Local tasks
* Local actions
* Contextual links
There are a couple others that are less obvious violations of the above, so I'm not bringing them up here to avoid derailing the discussion.
Comment #13
tim.plunkettThere is a third option for how access checkers can be defined: by just giving it a class::method pair.
Comment #14
chx CreditAttribution: chx commentedI am fine with that!
Comment #15
chx CreditAttribution: chx commentedComment #16
chx CreditAttribution: chx commentedComment #17
Crell CreditAttribution: Crell commentedRemoved incorrect statement. (The _custom_access approach doesn't require a service to be registered.)
With _custom_access, is this even necessary anymore? And what would be the performance impact of going through plugins for every request? Plus on link generation, which is already too slow...
Comment #18
Crell CreditAttribution: Crell commentedtstoeckler: No, it was an AND list, or maybe at best a "2 of 3". Things like tabs as plugins are a bit odd, but there was no better alternative. We have a viable alternative for access control now, which keeps the system loosely coupled.
Given how many hundreds of hours we've poured into decoupling systems in Drupal 8, and hiding details behind sane and purpose-built interfaces, I'm very reluctant to introduce more coupling unless there's a very very strong need. I don't see that strong need here.
Comment #19
chx CreditAttribution: chx commentedComment #20
chx CreditAttribution: chx commentedWe had a wonderful and fruitful IRC discussion today and I have posted an issue summary from it. First we identified the real pain points then dawehner solved it.
Comment #21
tim.plunkettWorking on this.
Comment #22
tim.plunkettSymfony has native support for attributes on tags, I chose to use that instead of adding a top-level key to the definition.
It still supports everything we need.
Comment #23
chx CreditAttribution: chx commentedHere's a handy awk script to facilitate the search, usage example:
Comment #24
chx CreditAttribution: chx commentedInstead of posting updated versions of this script, it now lives in a sandbox. https://drupal.org/sandbox/chx/2142873
Comment #25
Crell CreditAttribution: Crell commentedI like this. It does look cleaner, and doesn't increase coupling with any other systems. (Well, maybe with the DIC but that's incidental and you can still use the code without it.) +1.
Comment #26
tim.plunkettDamien did most of the work on StaticAccessCheckInterface so I really want his RTBC on this.
Comment #27
dawehnerMy implementation pretty much looks the same I coded offline.
Here are some small adapations.
* We don't need to support multiple ones ...
@tim Deleting a file is often just wrong, but I understand why you did that ... we have an issue to fix that already: https://drupal.org/node/1912602
Comment #29
dawehnerMaybe this works now ...
Comment #31
tim.plunkettThis is how they work. They are arrays, even when there is only one.
EDIT: I mean, this has to be reverted. It's just how Symfony does attributes in tagged services
When will the name not be 'access_check'? That's what this does, AFAIK.
Comment #32
tim.plunkettThis uses the applies_to and the correct changes to views that @dawehner had, but otherwise is my original patch.
It also makes the second param to addCheckService() optional.
Comment #33
dawehnerYeah I had a little bit of a rage mode there.
Comment #34
damiankloip CreditAttribution: damiankloip commentedThis method might as well be renamed I guess.
Do we need to alias these? There is currently a mixture of both.
Is this a bit too paranoid? Someone would have to register the same line twice in a YAML file? We don't do this checking in other places.
The only other thing that I would say is a 'concern' is having to do this:
It just looks weird having to specify the name each time to be honest.
Comment #35
tim.plunkett1) To what?
2) Yes, we do when the access checker is in Drupal\Core\Access
3) I'm not usually one for paranoia. You mean the array_unique? Doesn't bother me either way.
And yes, that is weird, but it is a) how it works in Symfony b) not used really in core.
Comment #37
damiankloip CreditAttribution: damiankloip commented1. Something with the word dynamic in? Not too bothered though really.
2. Ha, yes of course! Then another question, is it weird to have checkers in Core\Access using an interface in Core\Routing\Access?
3. Yep, the array_unique. Seems unnecessary.
4. I guess it's not a common case, so meh. Not the end of the world by any means.
I don't really see how this patch makes it that much easier to find access checkers but i can see the logic of looking in yaml. Plus does have some nice cleanups in.
Comment #38
tim.plunkettBorrowing a oneline fix from #1912602: Changing view access from "Permission" to "Role" causes AJAX error message re getRoles()
1) Renamed to match the protected property
2) Yes it is. Blame Xano (#2095125: Use access constants in every access control context)
3) If this causes a bug later on, it's on you :)
Comment #39
chx CreditAttribution: chx commented> I don't really see how this patch makes it that much easier to find access checkers but i can see the logic of looking in yaml.
While I did write a complicated script to do the search in the majority of cases
grep -B 3 applies_to: _entity_access **/*.services.yml|grep class
will just do fine. That's way, way easier than with current HEAD where the only thing you can grep for is the string _entity_access.Comment #40
damiankloip CreditAttribution: damiankloip commentedYes, it is easier. Only if you know where to look though :) That is why I said it is not that much easier. I do agree it *is* easier though.
This doc doesn't sound right. Doesn't it want to be something like "An array of route requirement keys the check service applies to", or something...
We check the AccessCheckInterface for dynamic checkers but nothing for static checkers anymore. Do you think we should do this too?
Ha, fair enough :) However, if we are wanting to make sure this isn't a problem, the RegisterAccessChecksPass is not the place to do this, seems like it should be in the access manager itself. If anywhere.
Comment #41
damiankloip CreditAttribution: damiankloip commentedComment #42
Crell CreditAttribution: Crell commentedComment #43
dawehnerI don't know what to check actually.
Comment #44
damiankloip CreditAttribution: damiankloip commentedRerolled. Plus, can't we just add it here to loadCheck() as all checkers will have to implement this interface in some form? That's what I was getting at in my comment about the checking.
Comment #45
dawehner+1 for that change.
Comment #46
dawehner44: 2109035-44.patch queued for re-testing.
Comment #48
damiankloip CreditAttribution: damiankloip commentedReroll
Comment #49
chx CreditAttribution: chx commentedAssigning to Crell for final review.
Comment #50
Crell CreditAttribution: Crell commentedThe docblock for AccessException says:
"An exception thrown for invalid access callback return values."
But that's not how it's being used here. Either we need to use a new exception class or update the docblock of AccessException. I don't have a strong preference at the moment.
Other than that nit, I'm good here. Go ahead and mark RTBC when fixing the above.
Comment #51
damiankloip CreditAttribution: damiankloip commentedRerolled, and changed that docbock for AccessException.
Comment #52
Crell CreditAttribution: Crell commentedWorks for me. Thanks.
Comment #53
webchickWow, this is a great change!
- 63 files changed, 231 insertions, 435 deletions FTW
- I also have no idea what a StaticAccessCheckerInterface is, so happy to see that removed in favour of something more general
- I also like the idea of encoding route relationships into the route definitions vs. having to open a separate file to get those
Coupled with the improved "greppability" defined in the issue summary, this patch looks like a big DX win to me!
Committed and pushed to 8.x. Thanks!
We need to update the existing change notices and documentation under https://drupal.org/node/2122071 now.
Comment #54
chx CreditAttribution: chx commentedAnd you can mention https://drupal.org/sandbox/chx/2142873 this handy script as well.
Comment #55
dawehnerUpdated the docs in https://drupal.org/node/2122195
Comment #56
Crell CreditAttribution: Crell commentedMade a slight tweak to the change notice. Looks good now.
Comment #57
BerdirUpdating tags and title.
Comment #59
ParisLiakos CreditAttribution: ParisLiakos commentedjunk
Opened #2187313: Remove reintroduced category code in aggregator