username-1.0.x-dev/src/Routing/UsernameRouteSubscriber.php
src/Routing/UsernameRouteSubscriber.php
<?php
namespace Drupal\username\Routing;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteProviderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* Modifies user-related routes to respond with 404 rather than 403.
*
* @package Drupal\username
*/
class UsernameRouteSubscriber implements EventSubscriberInterface {
/**
* Cache CID with user route IDS.
*/
const ROUTE_CID = 'username_user_route_ids';
/**
* Route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* A cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $prevention;
/**
* {@inheritdoc}
*/
public function __construct(RouteProviderInterface $routeProvider, EntityTypeManagerInterface $entityTypeManager, CacheBackendInterface $cache, ConfigFactoryInterface $config_factory) {
$this->routeProvider = $routeProvider;
$this->entityTypeManager = $entityTypeManager;
$this->cache = $cache;
$this->prevention = $config_factory->get('username.settings')->get('prevention');
}
/**
* {@inheritdoc}
*/
public function onException(ExceptionEvent $event) {
if ($this->prevention) {
$routeMatch = RouteMatch::createFromRequest($event->getRequest());
if ($event->getThrowable() instanceof AccessDeniedHttpException && in_array($routeMatch->getRouteName(), $this->getUserRoutes())) {
$event->setThrowable(new NotFoundHttpException());
}
}
}
/**
* Get an array of user route IDs.
*
* @return array
* An array of user route IDs.
*/
protected function getUserRoutes(): array {
if ($this->prevention) {
$userRouteIds = $this->cache->get(static::ROUTE_CID);
if ($userRouteIds !== FALSE) {
return $userRouteIds->data;
}
$userLinkTemplates = $this->entityTypeManager
->getDefinition('user')
->getLinkTemplates();
$routes = new RouteCollection();
foreach ($userLinkTemplates as $path) {
$routes->addCollection($this->routeProvider->getRoutesByPattern($path));
}
$userRouteIds = array_keys(array_filter(iterator_to_array($routes), function (Route $route): bool {
$parameters = $route->getOption('parameters') ?? [];
if (is_array($parameters)) {
foreach ($parameters as $parameter) {
// Check if the route has user entity parameters.
if ($parameter['type'] ?? NULL === 'entity:user') {
return TRUE;
}
}
}
return strpos($route->getPath(), '{user}') !== FALSE;
}));
// Add specific routes to the user route IDs list.
$userRouteIds[] = 'user.cancel_confirm';
$userRouteIds[] = 'shortcut.set_switch';
$this->cache->set(static::ROUTE_CID, $userRouteIds, Cache::PERMANENT, ['routes']);
return $userRouteIds;
}
return [];
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION] = 'onException';
return $events;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('system.entity_autocomplete')) {
$route->setDefault('_controller', '\Drupal\username\Controller\UsernameAutocompleteController::handleAutocomplete');
}
}
}
