Problem/Motivation

Follow-up from #2222719: Use parameter matching via reflection for access checks instead of pulling variables from request attributes.

Drupal\Core\Routing\Access\AccessInterface is empty. Access check methods can have different method parameters for different access checkers, so it is not possible to express that with a single interface. Therefore this interface is pointless and should be deprecated.

Proposed resolution

  • Deprecate AccessInterface for removal before Drupal 13.0.0 release.
  • Leave AccessInterface in core until shortly before Drupal 13, for BC.

Remaining tasks

User interface changes

None.

API changes

We'd be marking part of an API as deprecated, but it's not tagged @api, and the plan is to leave the interface itself in core.

So while we're discouraging the use of an API, we're also not breaking or changing it.

Data model changes

None.

Comments

effulgentsia’s picture

Issue summary: View changes
jessebeach’s picture

Status: Postponed » Active

Unpostponed!

cordoval’s picture

StatusFileSize
new18.76 KB

I worked on this, i am a newbie, hope i can make it into core!

cordoval’s picture

Assigned: Unassigned » cordoval
Status: Active » Needs review

thanks yesCT

cordoval’s picture

Assigned: cordoval » Unassigned

ok switched

guys please i will be begging if you could for once move to github for real, stop being like that ^_^

cordoval’s picture

I implemented the approach No. 1 of the description.

tim.plunkett’s picture

We haven't discussed this yet. I personally think #3 from the OP is preferable.

andyceo’s picture

Issue tags: +revisit before release candidate

Anybody live here? :)

mile23’s picture

Status: Needs review » Needs work
Issue tags: +@todo clean-up, +@deprecated

I vote for #1.

That means #4 needs a reroll.

xjm’s picture

Title: Deprecate, remove, or leave be Drupal\Core\Routing\Access\AccessInterface? » Deprecate or leave be Drupal\Core\Routing\Access\AccessInterface?
Issue summary: View changes
Issue tags: -revisit before release candidate

@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.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.52 KB

Though we still should remove the usages.

Status: Needs review » Needs work

The last submitted patch, 12: 2266817-12.patch, failed testing.

neclimdul’s picture

As 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?

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Anonymous’s picture

Same 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?

andypost’s picture

Version: 8.1.x-dev » 8.2.x-dev

+1 to #2 because it's confusing and hard to explain newcomers

andypost’s picture

Also 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

klausi’s picture

Title: Deprecate or leave be Drupal\Core\Routing\Access\AccessInterface? » Deprecate empty AcessInterface and remove usages
Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new34.13 KB
new32.8 KB

Updated 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.

mile23’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/Routing/Access/AccessInterface.php
@@ -4,10 +4,9 @@
 /**
  * An access check service determines access rules for particular routes.
+ *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 9.0.
  */
 interface AccessInterface {

Should 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

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new34.24 KB
new669 bytes

Sure, 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."

mile23’s picture

Status: Needs review » Reviewed & tested by the community

Patch 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.

catch’s picture

Status: Reviewed & tested by the community » Needs review

There'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?

klausi’s picture

We 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

catch’s picture

Title: Deprecate empty AcessInterface and remove usages » Deprecate empty AccessInterface and remove usages
Issue tags: +Needs change record

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.

Yeah 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.

klausi’s picture

Change 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.

catch’s picture

Very 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?

klausi’s picture

I 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.

mile23’s picture

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

Patch 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.

xjm’s picture

Status: Reviewed & tested by the community » Needs work

Nice, 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:

Interfaces within non-experimental, non-test modules not tagged with either @api or @internal
Interfaces that are not tagged with either @api or @internal can be safely used as type hints. No methods will be changed or removed from these interface in a breaking way.
However, we reserve the ability to add methods to these interfaces in minor releases to support new features. When implementing the interface, module authors are encouraged to either:
Where there is a 1-1 relationship between a class and an interface, inherit from the class. Where a base class or trait is provided, use those. This way your class should inherit new methods from the class or trait when they're added.
Where the interface is implemented directly for other reasons, be prepared to add support for new methods in minor releases

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:

In general, only interfaces can be relied on as the public API.
With the exception of base classes, developers should generally assume that implementations of classes will change, and that they should not inherit from classes in the system, or be prepared to update code for changes if they do. See also more specific notes below

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.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Anonymous’s picture

I'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:

  • Route access checks are defined as services within the dependency injection container, and are marked as so using the tag named access_check, and the applies_to attribute.
  • When a route is accessed, access check services for that route are created and should all conform to the AccessInterface, which originally defined the method access(Route $route, AccountInterface $account): AccessResultInterface to check if a user is allowed access to the route.
  • All access checks are evaluated => end result.

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 checks are all loaded by manipulating the definition for the access_manager.check_provider service at runtime in Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass::process(). Incidentally, this fails silently if the access_manager service isn't defined, when it seems like we should be throwing a runtime exception?
  • Here, we also decide which method we are going to use on this access check service, which is hard-coded default as $method = 'access';, and allows the definition to choose the method anyway?
  • I can see that where this interface is being used is Drupal\Core\Access\AccessManager::performCheck().
  • We have some magical Drupal\Component\Utility\ArgumentsResolver which is looking at what the particular service's access() method is type hinting, and trying to work out what dependencies to pass into it. As a result of this, several variations of the AccessInterface::access() have emerged in the codebase, most of which just ask for RouteMatchInterface as 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 ArgumentResolver to 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 the VoteInterface for 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\RouteMatch object anyway - if you want to provide a more accessible object, and the RouteMatch is more relevant, then just change your interface to access(RouteMatch $routeMatch, AccountInterface $account): AccessResultInterface?

So this whole system can be simplified, and optimised in a self-documenting manner for developers using it:

  • Restore the original interface method access(Route $route, AccountInterface $account): AccessResultInterface, or amend it.
  • Remove the need for RegisterAccessChecksPass and Drupal\Core\Access\CheckProvider to 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.
  • Simplify Drupal\Core\Access\AccessManager by removing the ArgumentResolver magic, and just use the interface method.
  • Refactor core access_check services that require additional dependencies to simply have them injected when they are constructed, and request that developers update their code accordingly.

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.

tim.plunkett’s picture

Everything 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->attributes directly, 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.

fgm’s picture

Something 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 TestAccessCheckInterface actually includes this access() method, with no parameters

So 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/restoring access() in the AccessInterface and 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.

tim.plunkett’s picture

Doing what is proposed in #34 results in a BC break:

class NodePreviewAccessCheck implements AccessInterface {
  public function access(AccountInterface $account, NodeInterface $node_preview) {
    // snip
  }
}

interface AccessInterface {
  public function access();
}

Results in

Fatal error: Declaration of NodePreviewAccessCheck::access() must be compatible with AccessInterface::access()  
fgm’s picture

Indeed in this case all concrete implementations must have all optional parameters, to avoid this problem.

tim.plunkett’s picture

Yes, but those implementations exist in the wild, and changing it is a BC break.

fgm’s picture

I 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..

tim.plunkett’s picture

If 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.

mile23’s picture

Version: 8.3.x-dev » 8.2.x-dev
Issue summary: View changes
Issue tags: +Needs documentation
StatusFileSize
new779 bytes

Deprecation 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:

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).

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.

Anonymous’s picture

Hi 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.

/**
 * An access check service determines access rules for particular routes.
 */
interface AccessInterface {

  // @todo Remove this interface since it no longer defines any methods?
  // @see https://www.drupal.org/node/2266817.

}

Now, we could still use the interface to document the fact that this interface expects an access method, and will use reflection. For example:

/**
 * An access check service determines access rules for particular routes.
 *
 * @method AccessResultInterface access(AccountInterface $account, RouteMatchInterface $route)
 *   Determine access to a route for a user.
 *
 *   The access check system will use reflection on your access method, and
 *   will try to pass the parameters using the parameter names and type-hints
 *   from the route attributes.
 */
interface AccessInterface {}

@method works 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->attributes directly 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:

ArgumentsResolver is directly mimicking Symfony\Component\HttpKernel\Controller\ControllerResolver

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):

    /**
     * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"})
     */
    public function listAction($page = 1)

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:

  access_check.entity:
    class: Drupal\Core\Entity\EntityAccessCheck
    tags:
      - { name: access_check, applies_to: _entity_access }

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 in ArgumentsResolver::handleUnresolvedArgument()?

catch’s picture

fwiw #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.

Anonymous’s picture

Hi 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:

Symfony Full Stack uses a separate Security component for controlling route access that is far too complex for us to integrate in Drupal 8. We're not going there right now.

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 returned AccessResult::forbidden(). At the moment, a voter with no opinion has to return AccessResult::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:

abstract class AccessResult ... {
  ...
  public function andIf(AccessResultInterface $other) {
    ...
    $merge_other = FALSE;
    if ($this->isForbidden() || $other->isForbidden()) {
      $result = static::forbidden();
      if (!$this->isForbidden()) {
        $merge_other = TRUE;
      }
    }
    elseif ($this->isAllowed() && $other->isAllowed()) {
      $result = static::allowed();
      $merge_other = TRUE;
    }
    else {
      $result = static::neutral();
      if (!$this->isNeutral()) {
        $merge_other = TRUE;
      }
    }
    ...
  }

Where AccessResult::allowed()->andIf(AccessResult::neutral()) === AccessResult::neutral() - which ultimately means that the final check in the stack can deny access by returning AccessResult::neutral(). It doesn't appear that this is a logic error however, as the test AccessResultTest::testAndIf() clearly expects ALLOWED && 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:

  access_check:
    class: Drupal\Core\Access\Check
    arguments:
      - "@=service('route').attributes.get('node')"
      - "@=service('current_route_match').getAttribute('user')"
      - '@current_user'
    tags:
      - { name: access_check, applies_to: _some_req }

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.

catch’s picture

Regarding request attribute dependencies, is there a reason why the Symfony expression language component wasn't considered?

Just quickly, that was announced in November 2013, the access system was added in November 2012. So one reason was not having a time machine ;)

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.

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.

dawehner’s picture

@GroovyCarrot
If you want to discuss about the expression language, please add it as its own issue. Its worth for itself to discuss that.

Anonymous’s picture

@dawehner good shout: added discussion issue for using symfony/expression-language

jp.stacey’s picture

For 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

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

It 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:

Drupal\Core\Access\AccessException: Access error in mymodule.my_access_service. Access services must return an object that implements AccessResultInterface

How is that requirement documented?

Also, the draft change record doesn't say what code should do instead of implementing the deprecated interface.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

gbirch’s picture

N.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.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexpott’s picture

So 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.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

imclean’s picture

Here'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.

namespace Drupal\my_module\Access;

use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;

class ResetPasswordAccessCheck {
  
  /**
   * Custom access check for password reset.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   */
  public function access(AccountInterface $account) {
    
    return $account->isAnonymous() ? AccessResult::forbidden() : AccessResult::allowed();
    
  }
  
}

In alterRoutes():

    if ($route = $collection->get('user.pass')) {
      $route->setRequirement('_custom_access', '\Drupal\my_module\Access\ResetPasswordAccessCheck::access');      
    }
imclean’s picture

#55 was in response to joachim #49 and doesn't take into account #53.

joachim’s picture

It 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.

guardian87’s picture

I 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

andypost’s picture

Version: 8.6.x-dev » 8.8.x-dev

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

mjpa’s picture

Issue tags: -

Ended 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?

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

jonathan1055’s picture

Can we update the issue summary here? Nothing has been committed in 8.x, AccessInterface still 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.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

andypost’s picture

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bhanu951’s picture

Bumping 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 ?

andypost’s picture

I think #53 is a way to go

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

liam morland’s picture

We could create more than one interface. For example:

/**
 * Access rules for a route.
 */
interface RouteAccessInterface {

  public function access(AccountInterface $account, RouteMatchInterface $route): AccessResultInterface;

}

Taking this a step further, in a routing file, if the class defined in _form implements this interface, then its ::access() method could be used automatically instead of having to be defined in _custom_access.

liam morland’s picture

Issue summary: View changes

I updated the summary.

If we are not going to deprecate AccessInterface then the @todo in its code should be replaced with an explanation of when the interface should be used.

liam morland’s picture

Status: Needs work » Active
Issue tags: -Needs issue summary update

Before we get into patches, we need to discuss what to do.

tr’s picture

Still a problem ...

acbramley’s picture

Trying 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 CsrfAccessCheck already aliases AccessInterface as RoutingAccessInterface.

xjm credited webchick.

xjm’s picture

Crediting for triage from #11.

avpaderno’s picture

Issue summary: View changes

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

mxr576’s picture

Issue summary: View changes

Time flies... if I am not mistaken the Drupal 12.0.0 train has been already missed.

andypost’s picture

I bet is not, still makes sense to deprecate even in 12.0