Score:2

Return AccessDenied for custom page

mw flag

I've setup a custom page in my system which is expecting 2 parameters that will be used to generate part of the content on that page, I'm able to retrieve the information from my page, but I'm wondering what is the best method of returning an access denied when my criteria fail

I found this page which shows how you can setup a custom access controller, but this example is passing the user entity, when what I'm more interested in is the 'slugs' of the URL they've hit

routing.yml

my_module.user_subscribe:
  path: /my-module/subscribe/{entity_type}/{entity_id}
  defaults:
    _controller: \Drupal\my-module\Controller\SubscriptionPages::subscribe
    _title: Email Subscription Sucessful
    type: node
    entity: 1
  requirements:
    _permission: 'access content'
    _entity_check: 'TRUE'

Services.yml

services:
  my_module.entity_check:
    class: Drupal\my-module\Access\EntityCheck
    tags:
      - { name: access_check, applies_to: _entity_check }

Then I have my src/Controller/SubscriptionPages file, and one for src/Access/EntityCheck etc, all that part seems to be working, I can manually enforce the code in my EntityCheck to return an access denied and it does, but what I want to know is how can I pull the {entity_type} and {entity_id} from the URL they've hit so I can check they aren't trying to access something they shouldn't

eg

mywebsite.com/my-module/subscribe/node/15 - would be fine

mywebsite.com/my-module/subscribe/foo/bar - would return access denied, because I want to run the check inside my EntityCheck code

Inside the link above it has arguments: ['@current_user'] inside the services.yml file, is there an equivalent one for current URL, or ones where I can more specifically retrieve each of the items individually?

Score:3
de flag

In your route, instead of _permission, you can use a custom access callback:

_custom_access: Drupal\[MODULE]\Controller::accessCallback()

Then you can return an AccessResult from the access callback:

public function accessCallback($entity_type, $entity_id) {
  if (!in_array($entity_type, $allowed_types)) {
    return \Drupal\Core\Access\AccessResult::forbidden();
  }

  ...

  return \Drupal\Core\Access\AAccessResult::neutral();
}

Or, you can use a _permission on the route, and instead throw a Symfony\Component\HttpKernel\Exception\NotFoundHttpException exception from the controller if the values are incorrect:

public function someRouteCallback($entity_type, $entity_id) {
  if (!$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id)) {
    throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
  }
}
Andrew Morris avatar
mw flag
Ah perfect, even better, thank you
4uk4 avatar
cn flag
Be aware that access callbacks could be called for different reasons and they should return the access result, not act on it. You can throw the exception in the controller itself, this would be no problem, but it's not ideal. This is to have an access callback returning the access result, then any code checking the route access works as it should.
Jaypan avatar
de flag
Whoops. Thanks, I was mixing things. I've updated the post accordingly. Andrew Morris you should review my updates and change your code accordingly.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.