How to limit access to a store page to the store owner?

95 Views Asked by At

I created a custom module to create a /store/ID/tasks page

https://www.drupal.org/project/commerce

How to limit access to this page to the store owner ?

If the current user is owner of store ID 76, he can access this page :

/store/76/tasks

But if he goes to another store, he must have denied access :

/store/89/tasks

https://git.drupalcode.org/sandbox/zenimagine-3076032

task_notify/task_notify.routing.yml

task_notify.store_page.tasks:
  path: '/store/{store}/tasks'
  defaults:
    _controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
    _title: 'Liste des tâches'
  requirements:
    _custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'

task_notify/src/Controller/TaskNotifyStoreController.php

<?php

namespace Drupal\task_notify\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\commerce_store\Entity\StoreInterface;

class TaskNotifyStoreController extends ControllerBase {

  public function Tasks() {
    return [
      '#theme' => 'task_notify_store_template',
    ];
  }

  public function taskAccess(StoreInterface $store, AccountInterface $account = NULL, $return_as_object = FALSE) {
    $result = $store->access('edit', $account, TRUE);
    return $return_as_object ? $result : $result->isAllowed();
  }

}

This page should be accessible only if the current user can edit the store (the site administrator and the store owner).

Access in the module code must have the same conditions as in this view :

https://i.stack.imgur.com/ZfUMo.png

I was inspired by the two files below :

https://git.drupalcode.org/project/commerce_marketplace/-/blob/8.x-1.x/src/Plugin/Action/MarketplaceIncreaseStoreLimitByOne.php

https://git.drupalcode.org/project/commerce_marketplace/-/blob/8.x-1.x/src/Plugin/Action/MarketplaceMarkAsDefault.php

1

There are 1 best solutions below

2
Brian Wagner On

In this case, we can tell Drupal that {store} is an entity and it will load the object. So we don't have to do that in the Controller function.

So your routing file can include "parameters" settings to do that.

task_notify.store_page.tasks:
  path: '/store/{store}/tasks'
  defaults:
    _controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
    _title: 'Liste des tâches'
  requirements:
    _custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'
  options:
    parameters:
      store:
        type: entity:commerce_store

Now your controller function has access to that object.

  public function Tasks(StoreInterface $store) { ...

In my experience, that is NOT true of the access() method (at least when using a type-hinted parameter as we are doing here). You get a string, so you'll have to load the store manually.

  public function taskAccess(string $store, AccountInterface $account) {
    $store = \Drupal\commerce_store\Entity\Store::load($store);
    // Check store owner against current user.
    if ($store->access('edit', $account)) {
      return AccessResult::allowed();
    }
    else {
      return AccessResult::forbidden();
    }

Also we need to define $account in the routing file now, as we are using type-hinted parameters (I think). So add that to the options:.

task_notify.store_page.tasks:
  path: '/store/{store}/tasks'
  defaults:
    _controller: '\Drupal\task_notify\Controller\TaskNotifyStoreController::Tasks'
    _title: 'Liste des tâches'
  requirements:
    _custom_access: '\Drupal\task_notify\Controller\TaskNotifyStoreController::taskAccess'
  options:
    parameters:
      store:
        type: entity:commerce_store
      account: \Drupal\Core\Session\AccountProxy

$account is one of a few special parameters that we can type-hint this way. More info: https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes/advanced-route-access-checking