2

I need to create a permission to allow users to see the content overview page (/admin/content) only during business hours (9 AM to 5 PM).

Drupal core comes with the "Access the Content overview page" permission; but I need "Access the Content overview page during work hours".

I know how to create a permission and how to test that the user has the permission. What I don't understand is how to assign some logic to a permission itself (for example, within business hours).

I can do this in mymodule.permissions.yml.

access content overview within business hours:
  title: 'Access the Content overview page within business hours'
  restrict access: TRUE

I can check the user has the permission with \Drupal::currentUser()->hasPermission('access content overview within business hours').

How can I make the permission return TRUE only during business hours?
How can I check the permission only when the users visits the content overview page?

I can foresee multiple use cases for needing some logic implementation within the permission itself, for example when allowing/restricting access to certain pages depending on whether the environment is development or production, or allowing/restricting access depending on the value of some fields.

- EDIT -

As suggested in the comments, I've started by creating the following three files on my module; but it doesn't work. The CustomAccessChecks::access never gets called.

File: custom_access.services.yml

services:
  custom_access.route_subscriber:
    class: Drupal\custom_access\Routing\AdminRouteSubscriber
    tags:
      - { name: event_subscriber }
  custom_access.access_checker:
    class: Drupal\custom_access\Access\CustomAccessChecks
    tags:
      - { name: access_check }

File: src/Access/CustomAccessChecks.php

<?php

namespace Drupal\custom_access\Access;

use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResultAllowed;
use Symfony\Component\Routing\Route;
use Drupal\Core\Routing\RouteMatchInterface;

class CustomAccessChecks implements AccessInterface {

  /**
   * A custom access check.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   */
  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
    return AccessResultAllowed::allowedIf((int) $account->id() === 1);
  }
}

File: AdminRouteSubscriber.php

<?php

namespace Drupal\custom_access\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

class AdminRouteSubscriber extends RouteSubscriberBase {


  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {

    if ($route = $collection->get('system.admin_content')) {
      $route->setRequirement('_custom_access', '\Drupal\custom_access\Access\CustomAccessChecks::access');
    }
  }
}

As expected, visiting '/admin/content' as the main administrator $account->id() === 1 works, but when visiting as another authenticated user, I still can access the page.

What I'm missing?

3
  • I believe the most voted answer in this question drupal.stackexchange.com/questions/204582/… outlines what you need to do.
    – GiorgosK
    Commented Mar 31, 2019 at 16:23
  • That’s probably a good solution.
    – Kevin
    Commented Mar 31, 2019 at 18:03
  • @GiorgosK and Kevin Thank you so much for the suggestion. I've updated my question above to include some code that I've added to my "custom_access" module. This code is mostly copied from the other answer as a prof of concept. Still not including the logic for the time of the day, though. Commented Mar 31, 2019 at 19:13

1 Answer 1

1

When the access to a route doesn't depend uniquely on a single permission, you need to implement what Access checking on routes describes.

  • Implement a service tagged access_check
  • Associate it with the route

Since you are altering the route defined from another module, you need to also follow Altering existing routes and adding new routes based on dynamic ones.

example.services.yml

services:
  example.route_subscriber:
    class: Drupal\example\Routing\RouteSubscriber
    tags:
      - { name: event_subscriber }

  example.access_checker:
    class: Drupal\example\Access\CustomAccessCheck
    tags:
      - { name: access_check }

RouteSubscriber.php

namespace Drupal\example\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    if ($route = $collection->get('system.admin_content')) {
      $route->setRequirement('_custom_access', 'example.access_checker::access');
    }
  }

CustomAccessCheck.php

namespace Drupal\example\Access;

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

/**
 * Checks access for displaying configuration translation page.
 */
class CustomAccessCheck implements AccessInterface {

  /**
   * A custom access check.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function access(AccountInterface $account) {
    // Check permissions and combine that with any custom access checking needed. Pass forward
    // parameters from the route and/or request as needed.
    return ($account->hasPermission('access content overview within business hours') && $this->checkTimeAccess()) ? AccessResult::allowed() : AccessResult::forbidden();
  }

}

The checkTimeAccess() method I didn't write should check if the user is trying to access the route during the business hours.

I cannot tell what happens with a route using the _permission requirement when it is altered to use also the _custom_access requirement. I didn't find the Drupal core code handling the access to a route, so I cannot say which requirement is used first.
I didn't also find a way to remove requirements from a route when altering it.

3
  • Thank you for your help. I tried your code but it didn't work. Commented Mar 31, 2019 at 21:05
  • Using kint($route) I can see that there is a requirement '_permission' => string(27) "access administration pages". It may be possible that Drupal is going with that requirement, which comes first. It is interesting that on D8 this particular route is overriden by a views, and I can see in the views administration that the path that is set is "/admin/content/node", however; this page really uses "/admin/content". I wonder whether this doesn't work because the views, or because of the requirement. Commented Mar 31, 2019 at 21:18
  • /admin/content/node is the default tab shown on /admin/content; it's not /admin/content/node that overrides /admin/content. As far as I recall, the permission to see /admin/content/node is inherited from /admin/content, so a user who doesn't have the permission to visit /admin/content cannot visit /admin/content/node either.
    – avpaderno
    Commented Apr 3, 2019 at 10:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.