Scroll
Ограничение доступа к словарю терминов таксономии с помощью подписчика событий (Event Subscriber)
Иногда на сайте требуются фиксированные, постоянные категории, которые не должны случайно обновляться. В этом случае можно использовать пользовательский код с подписчиком событий (Event Subscriber).
Добавим новый класс подписчика событий в кастомный модуль.
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 }
Теперь добавим наш подписчик событий в 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;
/**
* Перенаправляет неадминистраторов со страниц администрирования словаря "tag".
*
* Подписчик на уровне запроса срабатывает рано, что позволяет прервать
* выполнение и вернуть редирект до выполнения контроллера.
*/
class TagRedirectSubscriber implements EventSubscriberInterface {
/**
* Сервис entity-type manager.
*
* Приведен как пример зависимости; не обязателен для текущей логики,
* но полезен для будущих доработок, если потребуется загрузка сущностей.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* Сервис текущего пользователя.
*
* Используется для быстрого определения роли пользователя,
* чтобы не перенаправлять администраторов.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected AccountProxyInterface $currentUser;
/**
* Конструктор подписчика.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Менеджер сущностей.
* @param \Drupal\Core\Session\AccountProxyInterface $current_user
* Текущий пользователь, выполняющий запрос.
*/
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 {
// Приоритет 32 — параметры маршрута уже доступны,
// но контроллер еще не вызван.
return [
KernelEvents::REQUEST => ['onKernelRequest', 32],
];
}
/**
* Выполняет редирект, если неадминистратор обращается к страницам управления словарем "tag".
*
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
* Событие ядра с информацией о запросе.
*/
public function onKernelRequest(RequestEvent $event): void {
// Работаем только с основным запросом.
if (!$event->isMainRequest()) {
return;
}
// Администраторов не перенаправляем.
if ($this->currentUser->hasRole('administrator')) {
return;
}
$request = $event->getRequest();
$route_name = $request->attributes->get('_route');
// Куда перенаправлять все запрещенные попытки.
$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':
// Убеждаемся, что это словарь "tag".
$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() возвращает машинное имя словаря.
if ($term && $term->bundle() === 'tag') {
$event->setResponse(new TrustedRedirectResponse($redirect_to));
}
return;
default:
return;
}
}
}
Класс TagRedirectSubscriber
— это пользовательский подписчик событий в Drupal, предназначенный для ограничения доступа к административным страницам конкретного словаря таксономии (в данном случае, "tag") для неадминистраторов. Ниже представлен разбор его структуры и ключевых моментов:
1. Назначение и область применения
- Цель: Предотвратить случайные или несанкционированные изменения в словаре "tag", перенаправляя неадминистраторов с его административных маршрутов.
- Преимущество: Добавляет дополнительный уровень UI/UX-контроля доступа для критически важных словарей, поддерживая стабильность фиксированных категорий.
2. Структура класса и зависимости
- Класс реализует
EventSubscriberInterface
, что делает его совместимым с событийной системой Symfony, используемой в Drupal. - Зависимости внедряются через конструктор:
EntityTypeManagerInterface
: Добавлен на будущее, если потребуется работа с сущностями. Не обязателен для текущей логики, но облегчает расширяемость.AccountProxyInterface
: Используется для быстрого получения информации о ролях пользователя.
3. Подписка на события
- Класс подписан на событие
KernelEvents::REQUEST
с приоритетом 32.
Этот приоритет гарантирует:- Параметры маршрута уже доступны (маршрутизация завершена).
- Контроллер еще не выполнен, что позволяет перехватить и прервать выполнение запроса до передачи его контроллеру.
4. Логика перенаправления
- Весь доступ и логика перенаправления реализованы в методе
onKernelRequest()
:- Обработка только основного запроса: Исключает дублирующую обработку на подзапросах.
- Разрешение для администраторов: Если у пользователя есть роль
administrator
, доступ не ограничивается. - Проверка маршрутов: Обрабатываются только определенные маршруты, связанные со словарем таксономии и его терминами.
- Перенаправление неадминистраторов:
- Для overview, add, list (
entity.taxonomy_vocabulary.overview_form
,entity.taxonomy_vocabulary.overview_terms
,entity.taxonomy_term.add_form
) проверяется, что речь о словареtag
. - Для edit и delete (
entity.taxonomy_term.edit_form
,entity.taxonomy_term.delete_form
) проверяется bundle() термина (машинное имя словаря).
- Для overview, add, list (
- Использование Trusted Redirect: При совпадении условий пользователь перенаправляется на страницу обзора словаря "tag".
- Расширяемость: Логика легко дорабатывается для дополнительных словарей или ролей при необходимости.
5. Безопасность и лучшие практики
- Ранний перехват: Благодаря запуску на этапе запроса подписчик может ограничить доступ до обработки или отображения каких-либо данных.
- Проверка ролей: Эффективно определяет роли пользователя, чтобы не ограничивать доступ администраторам и разработчикам сайта.
- Чистое разделение логики: Отдельно реализованы маршрутизация, проверки пользователя и логика перенаправления для удобства поддержки.
6. Возможные улучшения
- Так как
EntityTypeManagerInterface
уже внедрен, в будущем можно реализовать проверки на уровне сущностей (например, доступ к отдельным терминам или связанному контенту). - Класс можно обобщить для поддержки нескольких словарей или сделать перенаправления настраиваемыми через конфигурацию.
7. Основные выводы
- Данный подписчик событий реализует эффективный способ контроля доступа в Drupal, используя событийную архитектуру Symfony для ранней и быстрой обработки запроса.
- Подход идеально подходит для защиты таксономий, которыми должны управлять только доверенные пользователи, снижая риск случайных изменений.