Scroll
Restrict access to Taxonomy terms vocabulary using Event Subscriber
Sometimes you need fixed, permanent Categories on the site, which should be updated accedentially. In this case you can utilize custom code with Event Subscribe.
Let's add a new Event Subscriber class in custom module.
drupalbook_custom.services.yml
services:
drupalbook_custom.tag_redirect_subscriber:
class: Drupal\drupalbook_custom\EventSubscriber\TagRedirectSubscriber
arguments:
- '@entity_type.manager'
- '@current_user'
tags:
- { name: event_subscriber }
And include our Event Subscriber in drupalbook_custom/src/EventSubscriber/TagRedirectSubscriber:
<?php
namespace Drupal\drupalbook_custom\EventSubscriber;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Redirects non-administrators from Tag vocabulary admin pages.
*
* A Request-level subscriber runs early, allowing us to short-circuit the
* request and return a redirect response before the matched controller
* executes.
*/
class TagRedirectSubscriber implements EventSubscriberInterface {
/**
* The entity-type manager service.
*
* Kept as an example dependency; not strictly required for the current
* logic but useful if future enhancements require entity loading.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* The current user proxy service.
*
* Used for quick role checks in order to bypass the redirect for
* administrators.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected AccountProxyInterface $currentUser;
/**
* Constructs the subscriber.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity-type manager.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* The user currently making the request.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
AccountProxyInterface $current_user,
) {
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// Priority 32 ensures route parameters are available but the controller
// has not yet executed.
return [
KernelEvents::REQUEST => ['onKernelRequest', 32],
];
}
/**
* Performs the redirect when a non-administrator accesses Tag admin routes.
*
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
* The kernel event carrying the request.
*/
public function onKernelRequest(RequestEvent $event): void {
// Only act on the main (master) request.
if (!$event->isMainRequest()) {
return;
}
// Allow administrators through without redirection.
if ($this->currentUser->hasRole('administrator')) {
return;
}
$request = $event->getRequest();
$route_name = $request->attributes->get('_route');
// Destination for all blocked attempts.
$redirect_to = 'https://drupalbook.org/admin/structure'
. '/taxonomy/manage/tag/overview';
switch ($route_name) {
case 'entity.taxonomy_vocabulary.overview_form':
case 'entity.taxonomy_vocabulary.overview_terms':
case 'entity.taxonomy_term.add_form':
// Confirm we are dealing with the "tag" vocabulary.
$vocabulary = $request->attributes->get('taxonomy_vocabulary');
if (!empty($vocabulary) && $vocabulary->id() === 'tag') {
$event->setResponse(new TrustedRedirectResponse($redirect_to));
}
return;
case 'entity.taxonomy_term.edit_form':
case 'entity.taxonomy_term.delete_form':
/** @var \Drupal\taxonomy\Entity\Term|null $term */
$term = $request->attributes->get('taxonomy_term');
// bundle() returns the vocabulary machine name.
if ($term && $term->bundle() === 'tag') {
$event->setResponse(new TrustedRedirectResponse($redirect_to));
}
return;
default:
return;
}
}
}
The TagRedirectSubscriber
class is a custom Event Subscriber for Drupal, designed to restrict access to administration pages of a specific taxonomy vocabulary (in this case, "tag") to non-administrators. Here’s a breakdown of its structure and the key valuable aspects found in the code:
1. Purpose and Use Case
- Goal: Prevent accidental or unauthorized updates to the "tag" vocabulary by redirecting non-admin users away from its admin routes.
- Benefit: Provides a layer of UI/UX-based access control for critical taxonomy vocabularies, enforcing stability for fixed categories.
2. Class Structure and Dependencies
- The class implements
EventSubscriberInterface
, making it compatible with Symfony’s event system used by Drupal. - Dependencies injected via constructor:
EntityTypeManagerInterface
: Included for potential future entity operations. Not required for current logic, but allows for easy extensibility.AccountProxyInterface
: Used to retrieve and check the current user's roles efficiently.
3. Subscribed Events
- The class subscribes to the
KernelEvents::REQUEST
event with a priority of 32.
This priority ensures:- Route parameters are available (routing is resolved).
- The controller for the route has NOT been executed yet, allowing the subscriber to intercept and short-circuit the request with a redirect if needed.
4. Redirection Logic
- The
onKernelRequest()
method performs all access checks and redirect logic:- Acts only on main requests: Avoids duplicate handling on sub-requests.
- Allows administrators: If the user has the
administrator
role, access is always permitted. - Checks route names: Only certain routes related to the taxonomy vocabulary or its terms are considered.
- Redirects non-admins:
- For overview, add, or list routes (
entity.taxonomy_vocabulary.overview_form
,entity.taxonomy_vocabulary.overview_terms
,entity.taxonomy_term.add_form
), it checks if the vocabulary istag
. - For edit and delete routes (
entity.taxonomy_term.edit_form
,entity.taxonomy_term.delete_form
), it checks if the term’sbundle()
(vocabulary machine name) istag
.
- For overview, add, or list routes (
- Uses a trusted redirect: If conditions match, the user is redirected to a safe admin overview page for the "tag" vocabulary.
- Extensibility: The logic is easy to extend for additional vocabularies or roles by adjusting conditions.
5. Security and Best Practices
- Early interception: By running on the request event, the subscriber can enforce access before any sensitive data is processed or displayed.
- Role-based bypass: Efficiently checks user roles to avoid blocking site builders or admins.
- Clear separation of concerns: Keeps routing logic, user checks, and redirection cleanly separated for maintainability.
6. Potential Enhancements
- Since
EntityTypeManagerInterface
is injected, you can easily add entity-based checks in the future (e.g., permissions based on specific term properties or related content). - You could generalize the class to handle multiple vocabularies or to provide customizable redirects via config.
7. Key Takeaways
- This Event Subscriber demonstrates a practical approach for access control in Drupal, leveraging Symfony’s event-driven architecture for early, efficient request handling.
- The approach is ideal for protecting taxonomy vocabularies that should be managed only by trusted users, reducing the risk of accidental changes.