Drupal - Add a local task tab for a content type only

You can use an access check to hide a route.

If the user that will view the tab has the same permission for adding a node of a given type, then simply adding _node_add_access: 'node:MYNODETYPEHERE' would be sufficient. However this is probably not the case.

Instead you can add _custom_access to the route requirements as a method on the controller class.

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _custom_access: '\Drupal\mymodule\Controller\CheckTestController::checkAccess'
    # It is highly-recommended to always use the default entity access check for
    # an Entity as pointed out by @pwolanin below. Not doing so may lead to
    # undesirable results and potential information disclosure or access bypass
    # vulnerability.
    _entity_access: node.view

And then a corresponding method on the controller (see Access checking on routes for more details). Something like this might work for you (untested).

public function checkAccess($node) {
  return AccessResult::allowedif($node->bundle() === 'my_node_type');
}

Update 2019.08.30: Incorporated @pwolanin's answer into this one in case anyone misses and doesn't read its importance.


I'm using Drupal 8.1.2 and both the accepted answer and the answer provided in the edit to the question did not actually work for me and they gave this error:

RuntimeException: Callable "Drupal\mymodule\Controller\CheckTestController::checkAccess" requires a value for the "$node" argument. in Drupal\Component\Utility\ArgumentsResolver->handleUnresolvedArgument() (line 142 of /var/www/html/core/lib/Drupal/Component/Utility/ArgumentsResolver.php).

the problem is that with the entity.node.canonical route, the $node variable seems to not actually link to the node itself, but stays as the ID.

The code that worked for me for the checkAccess method in the controller is:

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Controller\ControllerBase;
use Drupal\node\Entity\Node;

class CheckTestController extends ControllerBase {

    ...

    public function checkAccess($node) {
        $actualNode = Node::load($node);
        return AccessResult::allowedIf($actualNode->bundle() === 'test');
    }
}

Also - the answer above is incomplete since it skips a check on access to the node itself. This is possibly a recipe for an access bypass vulnerability.

I'd think you'd want a route more like:

mymodule.check_test:
  path: '/checktest/{node}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\CheckTestController::content'
  requirements:
    _custom_access: '\Drupal\mymodule\Controller\CheckTestController::checkAcess'
    _entity_access: node.view

Tags:

Routes

8