Active
Project:
Drupal core
Version:
main
Component:
routing system
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
13 May 2014 at 21:33 UTC
Updated:
30 Mar 2026 at 13:57 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
effulgentsia commentedComment #2
xjmComment #3
jessebeach commentedUnpostponed!
Comment #4
cordoval commentedI worked on this, i am a newbie, hope i can make it into core!
Comment #5
cordoval commentedthanks yesCT
Comment #6
cordoval commentedok switched
guys please i will be begging if you could for once move to github for real, stop being like that ^_^
Comment #7
cordoval commentedI implemented the approach No. 1 of the description.
Comment #8
tim.plunkettWe haven't discussed this yet. I personally think #3 from the OP is preferable.
Comment #9
andyceo commentedAnybody live here? :)
Comment #10
mile23I vote for #1.
That means #4 needs a reroll.
Comment #11
xjm@effulgentsia, @webchick, @alexpott, @catch and I discussed this. It's too late to remove this, but we can either deprecate it or choose to use it still for instanceof checks. Either of those things can be done after 8.0.0 as well.
We don't need to revisit this before release, so removing the tag.
Comment #12
dawehnerThough we still should remove the usages.
Comment #14
neclimdulAs a contrib developer stumbling on this, should we still implement this interface following the documentation here: https://www.drupal.org/node/2122195 or something else?
Comment #16
Anonymous (not verified) commentedSame question as in #14. Should we continue using this interface? If not, is there something else we should be using instead, or do we just create the access classes without implementing an interface?
Comment #17
andypost+1 to #2 because it's confusing and hard to explain newcomers
Comment #18
andypostAlso argument resolving happens in strange way - no entity passed when controller does not define the same arguments as access check
PS: suppose need separate issue
Comment #19
klausiUpdated the issue summary. AccessInterface is pointless but has to be implemented at the moment by access checkers, which also does not make sense.
Let's get rid of it and deprecate it.
Comment #20
mile23Should be "@deprecated in Drupal 8.2.x, will be removed before Drupal 9.0.0." It also needs an explanation of what to do instead of using this interface, with a @see linking to alternatives.
https://www.drupal.org/coding-standards/docs#deprecated
Comment #21
klausiSure, added this: "@deprecated in Drupal 8.2.x, will be removed before Drupal 9.0.0. This interface was used by access check classes but is not necessary anymore and can simply be omitted."
Comment #22
mile23Patch applies, and like magic my IDE can't find any implementations of AccessInterface.
The new deprecation message is good, and the tests pass.
All the replacements look sane.
Comment #23
catchThere's nothing in the issue summary about type-hinting. I can't see why you'd ever want to do instance of on AccessInterface, but how sure are we?
Comment #24
klausiWe are sure that the core usages of AccessInterface are cleaned up and our tests pass.
Using AccessInterface in contrib modules on access checkers is fine, since the interface is still there, it is just deprecated. There might be some very rare cases where people iterate over access checkers and validate them against AccessInterface or use an AccessInterface type hint on a method. But that should not be the case in 99.9% of Drupal 8 modules, since we cannot even come up with any reason why they would do that.
To make absolutely sure we would have to do a checkout of all drupal 8 modules with https://www.drupal.org/sandbox/greggles/1481160 and ag for AccessInterface
Comment #25
catchYeah this is the case I was thinking of. IMO if we commit this to 8.2.x soonish, then there's sufficient time for people's tests to break and for them to complain, then we could revert if we had to. But probably no-one will notice.
Needs a change record though I think for 'several core access checkers don't implement AccessInterface any more' - communicates that edge case and reinforces the deprecation to anyone that might have copied core code for theirs.
Comment #26
klausiChange record: https://www.drupal.org/node/2731311
We must commit this to 8.1.x - otherwise people will remove AccessInterface in their contrib modules and they will stop working with Drupal 8.1.x. There is this instanceof comparision in CheckProvider.php we need to get rid of in all supported branches.
Comment #27
catchVery not keen on committing to 8.1.x - we can make it clear that people shouldn't remove the interface usage until 8.2.0 is out maybe?
Comment #28
klausiI think that makes it only more confusing and painful. We could just backport the hunk where we remove the 3 lines of instanceof checks in CheckProvider.php? That would keep the change minimal on 8.1.x and contrib modules keep working with any version of 8.x.
Comment #29
mile23Patch still applies to 8.2.x, not to 8.1.x. My IDE still can't find any usages of AccessInterface.
We've got our change record draft, which I just updated to refer to 8.2.x instead of 8.1.x.
Setting to RTBC and re-running the testbot.
Some work will be needed if this is to be applied to 8.1.x, though it looks like the maintainer doesn't want to.
Comment #30
xjmNice, I'd forgotten about this issue. I agree the current patch definitely should not go into 8.1.x. For 8.2.x it's an interesting question.
Regarding type hints, here is the relevant part of our BC policy:
This patch complies with that as written, because the interface is still in place (just deprecated). However, our BC policy doesn't cover changing the interfaces a class implements. The closest we do have is:
So access checkers would be covered by that since they are not tagged as
@api.The one change (as listed in the change record) is that for the core access checkers, they will no longer be
instanceof AccessInterface, so code that was relying on that for some reason would break. And it would break in a way that just fails silently, which is actually a concern. So maybe we should limit the scope of this issue to just the deprecation, and remove the usages in 9.x (or at least discuss it in a separate followup, because the missing deprecation and the missing docs are something we want to fix soon).Thanks for the change record; we need that either way. I think we should add to it what you should do instead, which was done in #2222719: Use parameter matching via reflection for access checks instead of pulling variables from request attributes. That issue missed the docs gate unfortunately. We still need to update the handbook documentation as well: https://www.drupal.org/node/2122195
So I'd suggest doing the deprecation, CR, and handbook updates here, and then creating a followup with the removed implementations to discuss there.
Comment #32
Anonymous (not verified) commentedI've also stumbled across this as a developer, and it's been quite frustrating to just figure out exactly what method needs to be implemented by access checks. Is there a reason why this interface needs to be removed at all? I feel like the reason for removing the methods on this interface was just treating the symptom, rather than the cause; the interface was probably correct in the first place, the underlying code is what needs to be fixed.
I've tried to trace this back to understand what the original design was for this system, and as I understand it:
access(Route $route, AccountInterface $account): AccessResultInterfaceto check if a user is allowed access to the route.Now the above, I'm on board with. I think that all makes perfect sense - in fact Symfony's Security component (http://symfony.com/doc/current/security/voters.html) does this well; with the same concept of 'voting' on whether or not access should be granted to a route. Without getting into why that component wasn't used in the first place, the least that we can do is take some tips from that, rather than adding headache for developers - and preferably look to replace this system in 9.
From looking through the code, for a few hours:
access_manager.check_providerservice at runtime inDrupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass::process(). Incidentally, this fails silently if theaccess_managerservice isn't defined, when it seems like we should be throwing a runtime exception?$method = 'access';, and allows the definition to choose the method anyway?Drupal\Core\Access\AccessManager::performCheck().Drupal\Component\Utility\ArgumentsResolverwhich is looking at what the particular service'saccess()method is type hinting, and trying to work out what dependencies to pass into it. As a result of this, several variations of theAccessInterface::access()have emerged in the codebase, most of which just ask forRouteMatchInterfaceas well.I feel it is fair to say that this has been very much so over-engineered, and the solutions we are looking at here are simply going to overcomplicate, what is essentially an already an over complicated and undocumented workflow. I think we need to just stop, apply some basic engineering principals to this system, and backtrack somewhat.
So firstly, the context that we are using all of these services is to determine access to a route for a user; therefore the original interface was perfectly fine, remember that an interface doesn't care how the underlying code actually goes about it, it defines a contract between the service and the dependent: in this case, to implement the method "does this user have access to this route?" Therefore we can remove the need to map any old method to every service in
RegisterAccessChecksPass, as we know we want to ask them all the same question, and for maintainability and structure that fits with an actual design pattern, they should all behave exactly the same.Secondly, the problem with dependencies is a kind of a non-issue, as our service has already been defined in the dependency injection container, where we already have access to any dependencies that we need in order for this service to perform it's function. Therefore, the need for the
ArgumentResolverto work out what dependencies need to be passed to the method is redundant overhead, and only adding unnecessary complexity for no benefit - while preventing us from using a sensible interface, that is self-documenting. Take a look at theVoteInterfacefor Symfony's security component (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Sec...) and it's the same principal of simply asking "does this user have access to this subject?" obviously that isn't always going to be enough information for a voter to do it's job, but that's all the caller needs to care about, and the functionality is encapsulated.The points raised in https://www.drupal.org/node/2222719 are a completely separate issue to this system, and is better addressed using the
Drupal\Core\Routing\RouteMatchobject anyway - if you want to provide a more accessible object, and the RouteMatch is more relevant, then just change your interface toaccess(RouteMatch $routeMatch, AccountInterface $account): AccessResultInterface?So this whole system can be simplified, and optimised in a self-documenting manner for developers using it:
access(Route $route, AccountInterface $account): AccessResultInterface, or amend it.RegisterAccessChecksPassandDrupal\Core\Access\CheckProviderto bother with variable method names, and enforce using the interface; we developers know what they're doing by just reading the interface, and all adhere to the same standard.Drupal\Core\Access\AccessManagerby removing theArgumentResolvermagic, and just use the interface method.I appreciate that had the documentation have been updated (10 months ago now) when it was last mentioned on that ticket, then perhaps this wouldn't have been such an issue. However, this should have taken me 10 seconds to understand by reading an interface, and it's taken me hours to trawl through and figure out what is expected of me as a developer - this is a serious flaw in the design of this application, and even had the documentation had been written, telling a developer that they can use any method they want, and they can ask for anything they want is not a design pattern, and only opens this up unmaintainable code, and potential problems down the line; not to mention encouraging other developers to implement similar API's without design or standards.
Comment #33
tim.plunkettEverything you proposed sounds like an interesting approach, for Drupal 9. Because nothing you mentioned has any plan for backwards compatibility.
ArgumentsResolver long predates RouteMatch. When it was written, the only other option was to use
$request->attributesdirectly, and hope you got a properly upcasted variable. See #2222719: Use parameter matching via reflection for access checks instead of pulling variables from request attributes.Additionally, the "magic" of ArgumentsResolver is directly mimicking Symfony\Component\HttpKernel\Controller\ControllerResolver. If you want to pick fights with reflection to satisfy the arguments of a routing-adjacent method, start there.
My apologies that the API wasn't up to your standards. We'd appreciate help improving it. But I'd recommend reading http://xjmdrupal.org/blog/someone-worked-hard-on-it and possibly doing a bit more research on WHY we ended up with the API we have before bashing it next time.
Comment #34
fgmSomething seems strange to me in that discussion:
* in 8.2.x this interface is still implement by around 35 classes and extended by 1 other interface,
* all implementations of the interface include an
access()method, albeit with varying parameters* the interface extending it
TestAccessCheckInterfaceactually includes this access() method, with no parametersSo it seems the presence of
access()is a de facto characteristic of this interface, although it was removed at some point. Why not have it without any parameters ?RegisterAccessChecksPass::process()does exactly that assumption. Conversely, that same method supports adding a "method" attribute, although this has exactly 0 use case in core right now. Adding/restoringaccess()in theAccessInterfaceand removing the unused logic supporting "method" looks like it could simplify our logic a bit, and make the system more understandable. And since core uses the Interface already, this would not be a compatibility break.Comment #35
tim.plunkettDoing what is proposed in #34 results in a BC break:
Results in
Comment #36
fgmIndeed in this case all concrete implementations must have all optional parameters, to avoid this problem.
Comment #37
tim.plunkettYes, but those implementations exist in the wild, and changing it is a BC break.
Comment #38
fgmI see your point. But I'd like to push the thought expriment a bit here: since the interface does /not/ currently define a method signature, any contrib implementing such a method is not using a core API anyway, so this is a case where this is not a BC break because there is no prior definition. And core methods can be aligned to this new interface.
Just to be clear, this is more about BC policy than actual code questions, since adding the method with optional parameters allow current implementations to stand while using the updated interface: what is our guarantee with regard to adding new methods ? AIUI additions are supposed to be possible in minor releases..
Comment #39
tim.plunkettIf we make the change you propose, node.module and user.module will be broken. Those are vaguely important modules
Since 8.0.0 I personally know of zero interfaces that had methods added.
Comment #40
mile23Deprecation would mean fixing usages in core, so we'd fix the stuff in #35. Except I don't think we should make those changes, and should instead:
@xjm #30:
I'd update some docs or something, but I'm not entirely clear what the replacement is.
Here's a patch which marks the interface as deprecated, and points the developer to https://www.drupal.org/node/2122195 Which is not up-to-date, but hopefully someone can update it.
Comment #41
Anonymous (not verified) commentedHi tim.plunkett, thanks for the response. I appreciate that I may have come across quite negative, and I've certainly over-engineered my fair share of solutions. I've see worse, and written worse - it's easily done, and it's easy to take criticism personally; but at the end of the day, open source software is built on constructive criticism, and I wouldn't be posting on here if I didn't have anything constructive to contribute - and I certainly wouldn't be doing it to pick a fight. I didn't want to simply post here and state that I'd had several frustrating hours trying to understand what I need to do in order to implement an access check, without explaining what I was expecting to see, and suggesting what needs to be done in order to improve this API - because that would just be bashing it. The reality however, is still that this is confusing for newcomers, it takes a considerable amount of time to understand as a developer reading through the code; and I just wanted to stress that interfaces are a developer's best friend! They will save you in the absence of documentation, and make it much easier for someone with little knowledge of the underlying system to write the documentation at a later date.
Naturally to see the interface, all I'm looking for is just the signature of a method to implement, and instead find the comment that points the developer to this issue, which doesn't particularly discuss the reasoning behind why the method had been removed, and only really has mention that the documentation won't help, as it is out of date.
Now, we could still use the interface to document the fact that this interface expects an access method, and will use reflection. For example:
@methodworks with IDE's, allows overloading, and wouldn't break if you ignore the suggested parameters entirely. However, the problem here still is that this is a code-smell, and suggests that the underlying system may have been overcomplicated. It would be easier to just accept it however, and at least allows the developer to just crack on with the knowledge they expected to gain from reading the interface.As I was alluding to (albeit negatively), code in core sets the example, and I would have thought it perfectly okay to just say "here's the standard, please fall in line". I can completely understand that accessing
$request->attributesdirectly is not a good API, and certainly abstracting accessing of request attributes via RouteMatchInterface is all great by me, so I still have to advocate that the right thing to do here is break backwards compatibility to amend the interface.I just also want to take a minute to talk about a potential problem with the use of reflection, as it's important that it weighs into the discussion. As you mentioned:
Now if I'm honest, when I've used that before, I actually had just thought this simply passed route parameters into the controller method, in the order they were in the route. That aside however, the use-case is as such (from http://symfony.com/doc/current/routing.html):
This use of reflection based on parameter name becomes somewhat acceptable because, rather than being a route-adjacent method, the method is in fact the route itself. Controller methods themselves are variable by nature, and cannot conform to an interface; the idea is that the route and it's parameters are defined immediately above; but even using YAML, the only place the method gets called is from the route, and so the code is still self-documenting. It also seems to have been engineered to allow for the route to ask for the Request object, if needed, without every route having to ask for it.
If we consider how we defining our access checks:
The difficulty we have here is that access checks are actually applied to multiple routes; in fact, in the access check class I ended up writing, following all of this investigation, I was still required to ask for the route and use a guard clause, to make sure that I was checking access to the route I was expecting. My concern here is that if I write an _entity_access check for i.e. user profile pages, and request
UserInterface $user, will this then break every other route with a requirement of _entity_access, due to the uncaught RuntimeException thrown inArgumentsResolver::handleUnresolvedArgument()?Comment #42
catchfwiw #1793520: Add access control mechanism for new router system is the original discussion/issue for the access system, it was added after the initial routing patch landed, which I didn't think was a good idea at the time and still don't.
Comment #43
Anonymous (not verified) commentedHi catch, thanks for chiming in, it certainly helps to have an audit trail to follow. Off topic, but quality control would probably be a lot higher and have a faster turn around if we were using github - the main branch should really be protected against merging commits without a pull request; that way code won't be merged without unanimous approval. Also I've no doubt Drupal would qualify for an Atlassian open-source license.
Something that jumps out at me is the following statement:
The security component is certainly a little simpler than it was 4 years ago, however I'm concerned that it seemed to get ruled out instantly, with no discussion? Following that, the implementation of this access checking system replicated much of the functionality anyway - if anything, becoming more complex by using an object result rather than lighter-weight constants.
Evidence that the original system was perhaps architected wrong, is that the behaviour of
AccessResult::neutral()does not work as it is documented, or as the code suggests semantically. This is particularly important in the situation where a voter/access checker can only be subscribed to a series of routes via requirement metadata. It is important that a voter can state that it has no opinion of whether or not a user has access to a route as in the event that something goes wrong, a unanimous vote of "no opinion" would be a good indicator that we didn't ask the voter that was expecting to deal with the route - which may well have returnedAccessResult::forbidden(). At the moment, a voter with no opinion has to returnAccessResult::allowed()in order to prevent existing functionality breaking, which seems dangerous to me.The reason for this is probably the logic in the following method:
Where
AccessResult::allowed()->andIf(AccessResult::neutral()) === AccessResult::neutral()- which ultimately means that the final check in the stack can deny access by returningAccessResult::neutral(). It doesn't appear that this is a logic error however, as the testAccessResultTest::testAndIf()clearly expectsALLOWED && NEUTRAL === NEUTRAL? Also I have to question if enough discussion has been had around whether or not unanimous access checking is suitable for every situation?Regarding request attribute dependencies, is there a reason why the Symfony expression language component wasn't considered? (http://symfony.com/doc/current/service_container/expression_language.html)
You could define access_check services as such:
This would allow you to request attribute dependencies from the YAML, without the need to have variable methods across every access check just to satisfy dependencies? This would also solve an issue I've had repeatedly in the past where you are unable to depend on an entity storage service, which is packed away in the entity_type.manager; you could just state a dependency on
@=service('entity_type.manager').getStorage('entity'), allowing you to type hint the dependency properly, and save having to do logic in your constructors to extract and validate it from the entity storage manager.Comment #44
catchJust quickly, that was announced in November 2013, the access system was added in November 2012. So one reason was not having a time machine ;)
github has been discussed at length elsewhere. The Drupal release cycle has been completely changed since 2012, with the intention of not merging unshippable code ever again. It's not perfect, but it's a social, not tooling issue.
Comment #45
dawehner@GroovyCarrot
If you want to discuss about the expression language, please add it as its own issue. Its worth for itself to discuss that.
Comment #46
Anonymous (not verified) commented@dawehner good shout: added discussion issue for using symfony/expression-language
Comment #47
jp.stacey commentedFor reference, I've just closed documentation issue #2427033: Access checking on routes page needs editing: it's an old issue (Feb 15) and the remaining item on it is duplicated by the task here to update the docs located at https://www.drupal.org/node/2122195
Comment #49
joachim commentedIt does seem strange having an empty interface, but at the same time, there is an expectation on an access service -- if I don't return the right thing, I get this:
How is that requirement documented?
Also, the draft change record doesn't say what code should do instead of implementing the deprecated interface.
Comment #51
gbirch commentedN.B. for anyone who ends up here. At least in Drupal 8.3.7, failure to implement AccessInterface in your access check class causes an exception to be thrown in \Drupal\Core\Access\CheckProvider->loadCheck(). So if you want your custom access check to work, ignore the deprecation warning for now.
Comment #53
alexpottSo here's a use case for the having an interface. If you want to decorate an access check to implement additional functionality but play nice with others that want to do the same.
I think we should file a Drupal 9.x issue to add the access method to the interface to do #34 and require that access methods implement default arguments.
This issue could become become something that documents the change on AccessInterface because there is nothing to stop implementations already adding defaults in Drupal 8.x in order to be 9.x compatible.
Comment #55
imclean commentedHere's an example which works fine. There's no need to implement an interface, just make sure the return value is correct.
This prevents anonymous users resetting their password.
In
alterRoutes():Comment #56
imclean commented#55 was in response to joachim #49 and doesn't take into account #53.
Comment #57
joachim commentedIt seems to me that now PHP support return types, we should keep this interface, and once Drupal 8 drops support for PHP 5, we can add a return type to the method.
Comment #58
guardian87 commentedI have updated the docs to reflect the fact that the interface is still required currently here:
https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes
Comment #59
andypostComment #62
mjpa commentedEnded up here because I was checking the AccessInterface method signature as I thought it was a bool return type but obviously not.
Figured I'd not bother with the interface because it's not needed according to this issue... but then we end up with:
Drupal\Core\Access\AccessException: All access checks must implement AccessInterface. in Drupal\Core\Access\CheckProvider->loadCheck() (line 103 of core/lib/Drupal/Core/Access/CheckProvider.php).Soooo looks like we have to use it but it's encourages not to?
Comment #64
jonathan1055 commentedCan we update the issue summary here? Nothing has been committed in 8.x,
AccessInterfacestill exists in 9.2 and is not deprecated.Also we have contradictory advice above - in #55 imclean has an example that does not use the interface and works fine. I also have tested this in contrib, from 8.8 up to 9.2 and the access check works without an interface.
But then we have mjpa in #62 who gets the
Drupal\Core\Access\AccessException: All access checks must implement AccessInterface. in Drupal\Core\Access\CheckProvider->loadCheck()exception.So I'm confused whether to use (a)
AccessInterface, (b)AccessCheckInterface, or (c) neither of them.Comment #66
andypostComment #70
bhanu951 commentedBumping the issue as Drupal 10 Currently doesn't support versions lower than PHP 8, should we consider adding return type as suggested in #57 or should we deprecate Interface ?
Comment #71
andypostI think #53 is a way to go
Comment #73
liam morlandWe could create more than one interface. For example:
Taking this a step further, in a routing file, if the class defined in
_formimplements this interface, then its::access()method could be used automatically instead of having to be defined in_custom_access.Comment #74
liam morlandI updated the summary.
If we are not going to deprecate
AccessInterfacethen the@todoin its code should be replaced with an explanation of when the interface should be used.Comment #75
liam morlandBefore we get into patches, we need to discuss what to do.
Comment #76
tr commentedStill a problem ...
Comment #77
acbramley commentedTrying to figure out what the way forward here is.
#71 points to #53
But #53 points to #34 which is going to be quite disruptive as per #35
People would then also need to add defence when parameters aren't defined otherwise static analysis tooling such as phpstan will complain on higher levels.
I like the idea in #73, funnily enough core's
CsrfAccessCheckalready aliasesAccessInterfaceasRoutingAccessInterface.Comment #79
xjmCrediting for triage from #11.
Comment #80
avpadernoComment #82
mxr576Time flies... if I am not mistaken the Drupal 12.0.0 train has been already missed.
Comment #83
andypostI bet is not, still makes sense to deprecate even in 12.0