4

I would like to add an additional access condition to viewing user pages like user/1.

I looked at the following guidelines, but have not succeeded in coming up with a solution.

https://www.drupal.org/docs/drupal-apis/routing-system/altering-existing-routes-and-adding-new-routes-based-on-dynamic-ones

and

https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes#access-custom

The module code I have come up with this far - I can restrict all access to user profiles fine. But I fail at my attempt to get the account id of the page being requested. Once I have the account I can load the field I need to check.

my_module.services.yml

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

my_module/src/Routing/RouteSubscriber.php

<?php

namespace Drupal\my_module\Routing;

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

/**
 * Listens to the dynamic route events.
 * 
 * We deny access to profiles, if the user has not accepted the terms.
 * 
 * See https://www.drupal.org/node/2187643
 * 
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    $path = \Drupal::service('path.current')->getPath();
    $path_args = explode('/', $path);
    // @todo: the right path does not seem to be accessible here - how do we get the user account id?
    // Always deny access to '/user/{uid}'.
    // Note that the second parameter of setRequirement() is a string.
    if ($route = $collection->get('entity.user.canonical')) {

      //$account = \Drupal\user\Entity\User::load($account_uid);
      // This effectively denies all access to user profiles
      $route->setRequirement('_access', 'FALSE');
    }
  }

}

EDIT: Based on the comments I tried the following, but I am not being restricted to paths like /user/1, so I guess something is still amiss:

My module structure now looks like this:

/my_module
  /src
    /Access
      my_moduleAccessCheck.php
    /Controller
      my_moduleController.php
    my_module.info.yml
    my_module.routing.yml
    my_module.services.yml

my_moduleAccessCheck.php:

<?php

namespace Drupal\my_module\Access;

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

/**
 * Checks access for displaying configuration translation page.
 */
class my_moduleAccessCheck 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.
    // $account->hasPermission('do example things') && $this->someOtherCustomCondition()
    return AccessResult::forbidden();
  }

}

my_moduleController.php:

<?php

namespace Drupal\my_module\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * An example controller.
 */
class my_moduleController extends ControllerBase {

  public function sayHi() {
    dpm('Hi');
  }

}

my_module.routing.yml

my_module:
  path: '/user/{uid}'
  defaults:
    _controller: '\Drupal\my_module\Controller\my_moduleController::sayHi'
    _title: ''
  requirements:
    _my_module_access_check: 'TRUE'

my_module.services.yml

services:
  my_module.access_checker:
    class: Drupal\my_module\Access\my_moduleAccessCheck
    arguments: ['@current_user']
    tags:
      - # Required: Mark that this service is for access checks.
        name: access_check, 
        # Optional: Specify which routes this applies to.
        applies_to: _my_module_access_check
6
  • So you're viewing /user/1 and you want to get the user object with id=1? I.e. You want to get the user object for the profile being viewed, not the user object of the visitor viewing the page?
    – sonfd
    Commented Aug 11, 2020 at 12:37
  • 1
    Instead of _access you need a dynamic access checking service. See drupal.org/docs/8/api/routing-system/access-checking-on-routes/…
    – 4uk4
    Commented Aug 11, 2020 at 12:50
  • @sonfd eventually I will need both the user object for the user profile being viewed and the user role of the currently logged in user. Commented Aug 11, 2020 at 21:28
  • 1
    See the linked doc: "The access method arguments are resolved similar to normal routing." So you can get both via the method arguments: access(AccountInterface $account, UserInterface $user)
    – 4uk4
    Commented Aug 12, 2020 at 11:26
  • @4k4 I tried to implement the suggested method, but still no success - I added my code in the original question. Commented Aug 13, 2020 at 20:54

2 Answers 2

1

I ended up using hook_ENTITY_TYPE_access():

use \Drupal\Core\Entity\EntityInterface;
use \Drupal\Core\Session\AccountInterface;
use \Drupal\Core\Access\AccessResult;
use \Drupal\Core\Access\AccessResultInterface;
/**
 *
 * Implements hook_ENTITY_TYPE_access().
 */
function my_module_user_access(EntityInterface $entity, $operation, AccountInterface $account) {

  // Deny access to user profile for other members if the user hasn't accepted the terms
  if (!$entity->id() === $account->id() 
      || !$entity->hasField('field_profile_visibility')
      || $entity->get('field_profile_visibility')->value === NULL) {
    \Drupal::messenger()->addWarning(t('The profile is not visible because this user has not accepted the terms of this site yet.'));
    return AccessResult::forbidden();
  }
  return AccessResult::neutral();
}
0

The entity.user.canonical route has one parameter, $user. Route parameters can be retrieved from the route object and are upcast to their respective object. So, the user id will be upcast to the user object.

This technique is explained for nodes in the question How do I get the current node ID, but you can do the exact same thing with users.

For example:

$profile_user = \Drupal::routeMatch()->getParameter('user');
if ($profile_user instanceof \Drupal\user\UserInterface) {
  // $profile_user contains the $user object for the currently viewed profile.
}
3
  • The $profile_user is NULL when I retrieve it from within the if clause. Commented Aug 11, 2020 at 19:57
  • Yeah, I didn't read your question thoroughly enough. I was just answering your question "I fail at my attempt to get the account id of the page being requested. Once I have the account I can load the field I need to check." I didn't realize that the context you were in wouldn't support that idea. I think you should be able to use this snippet in a dynamic access checking service like 4k4 mentioned.
    – sonfd
    Commented Aug 11, 2020 at 20:15
  • Thanks I will try that. Commented Aug 11, 2020 at 21:26

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.