Commit 98ea42f0 authored by Olivier Gilbert's avatar Olivier Gilbert
Browse files

Issue #3252598 by OlivierG: use regex, path, path with wildcards '%' or route...

Issue #3252598 by OlivierG: use regex, path, path with wildcards '%' or route name in the route field
parent 463de803
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ services:
      - '@current_route_match'
      - '@entity_type.manager'
      - '@router.route_provider'
      - '@messenger'

  restrict_route_by_ip.route_subscriber:
    class: Drupal\restrict_route_by_ip\Routing\RouteSubscriber
+8 −4
Original line number Diff line number Diff line
@@ -33,14 +33,18 @@ class RouteSubscriber extends RouteSubscriberBase {
   */
  protected function alterRoutes(RouteCollection $collection) {
    $routes = $this->restrictRouteService->getAllRestrictedRoutes();
    $route_names = array_column($routes, 'route_names');
    $route_names = array_reduce($route_names, 'array_merge', array());

    // Restrict asset on all routes from configurations.
    foreach($routes as $route_name) {
    if (!empty($route_names)) {
      foreach($route_names as $route_name) {
        if ($route = $collection->get($route_name)) {
          $route->setRequirement('_custom_access', 'restrict_route_by_ip.services_access_checker::access');
          $route->setOption('no_cache', TRUE);
        }
      }
    }
  }

}
+16 −1
Original line number Diff line number Diff line
@@ -37,8 +37,23 @@ interface RestrictIpInterface {
   * Get all enabled restricted routes.
   *
   * @return array
   *    List of all routes.
   *    List of all routes. (route example :
   *    [
   *      'restricted_route_id' => $entity->id(),
   *      'route_names' => $this->getRouteNames($path_or_route_name)
   *    ]
   */
  public function getAllRestrictedRoutes(): array;

  /**
   * Get a restricted route by route name.
   *
   * @param string $route_name
   *    Route name to search a restricted route.
   *
   * @return string
   *    The restricted route id or NULLa
   */
  public function getRestrictedRouteId(string $route_name);

}
+105 −17
Original line number Diff line number Diff line
@@ -5,10 +5,12 @@ namespace Drupal\restrict_route_by_ip\Service;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\PageCache\ResponsePolicy\KillSwitch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\RouteProvider;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\restrict_route_by_ip\Entity\RestrictRouteByIp;
use Symfony\Component\HttpFoundation\RequestStack;

@@ -66,6 +68,20 @@ class RestrictIpService implements RestrictIpInterface {
   */
  protected $routeProvider;

  /**
   * Drupal Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * All definied routes.
   *
   * @var array
   */
  protected $allRoutes;

  /**
   * Constructor.
   *
@@ -83,6 +99,8 @@ class RestrictIpService implements RestrictIpInterface {
   *   Drupal EntityTypeManager service.
   * @param \Drupal\Core\Routing\RouteProvider $route_provider
   *   Drupal RouteProvider service.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   Drupal RouteProvider service.
   */
  public function __construct(
    ConfigFactory $config,
@@ -91,7 +109,8 @@ class RestrictIpService implements RestrictIpInterface {
    LoggerChannelFactoryInterface $logger_factory,
    RouteMatchInterface $current_route_match,
    EntityTypeManagerInterface $entity_type_manager,
    RouteProvider $route_provider) {
    RouteProvider $route_provider,
    MessengerInterface $messenger) {
    $this->configManager = $config;
    $this->currentRequest = $request_stack_service->getCurrentRequest();
    $this->pageCacheKillSwitch = $page_cache_kill_switch;
@@ -99,8 +118,11 @@ class RestrictIpService implements RestrictIpInterface {
    $this->routeMatch = $current_route_match;
    $this->restrictRouteStorage = $entity_type_manager->getStorage('restrict_route');
    $this->routeProvider = $route_provider;
    $this->messenger = $messenger;
  }

  use StringTranslationTrait;

  /**
   * {@inheritdoc}
   */
@@ -144,10 +166,7 @@ class RestrictIpService implements RestrictIpInterface {
   */
  public function userIpIsRestricted(AccountInterface $account): bool {
    $current_route_name = $this->routeMatch->getRouteName();
    $routes = $this->getAllRestrictedRoutes();

    // Searching for restricted route name.
    $restricted_route_id = array_search($current_route_name, $routes);
    $restricted_route_id = $this->getRestrictedRouteId($current_route_name);
    $restricted = FALSE;
    if ($restricted_route_id) {
      // Load the current restricted route entity.
@@ -177,6 +196,20 @@ class RestrictIpService implements RestrictIpInterface {
    return $restricted;
  }

  /**
   * {@inheritdoc}
   */
  public function getRestrictedRouteId(string $route_name) {
    $restricted_route_id = NULL;
    $all_restricted_routes = $this->getAllRestrictedRoutes();
    foreach($all_restricted_routes as $route) {
      if (array_search($route_name, $route['route_names']) !== FALSE) {
        $restricted_route_id = $route['restricted_route_id'];
      }
    }
    return $restricted_route_id;
  }

  /**
   * {@inheritdoc}
   */
@@ -189,8 +222,11 @@ class RestrictIpService implements RestrictIpInterface {

    /** @var \Drupal\restrict_route_by_ip\Entity\RestrictRouteInterface $entity */
    foreach($entities as $entity) {
      $route = $entity->getRoute();
      $routes[$entity->id()] = $this->getRouteName($route);
      $restricted_route_name = $entity->getRoute();
      $routes[] = [
        'restricted_route_id' => $entity->id(),
        'route_names' => $this->getRouteNames($restricted_route_name)
      ];
    }
    return $routes;
  }
@@ -198,22 +234,74 @@ class RestrictIpService implements RestrictIpInterface {
  /**
   * Get the route name if route is a path.
   *
   * @param string $route
   *    A route name or a path.
   * @param string $restricted_route_name
   *    A route name or a path or a regular expression.
   *
   * @return string
   *    A route name.
   */
  protected function getRouteName(string $route): string {
    $route_name = $route;
    if (strpos($route, '/') !== FALSE) {
      $route_collection = $this->routeProvider->getRoutesByPattern($route);
      $routes = $route_collection->all();
      if (!empty($routes) && count($routes) > 0) {
        $route_name = key($routes);
  protected function getRouteNames(string $restricted_route_name): array {
    $route_names = [];
    $regex = '';

    // If the restricted route is a regex.
    if (substr($restricted_route_name, 0, 1) === '#'
      && substr($restricted_route_name, -1, 1) === '#') {
      $regex = $restricted_route_name;
    }

    // A path is defined, so check path by regex.
    if (empty($regex) && strpos($restricted_route_name, '/') !== FALSE) {
      $regex = $this->getRegularExpressionFromPath($restricted_route_name);
    }

    // Use regex to define impacted route names.
    if (!empty($regex)) {
      if (empty($this->allRoutes)) {
        $this->allRoutes = $this->routeProvider->getAllRoutes();
      }

      foreach($this->allRoutes as $route_name => $route) {
        $route_path = $route->getPath();
        $route_path = preg_replace('#(\{[_\-0-9a-zA-Z]+\})#', 'rrbip_replaced', $route_path);

        $match = [];
        $check_expression = @preg_match($regex, $route_path, $match);
        if ($check_expression === FALSE) {
          $this->messenger->addWarning($this->t('Invalid regular expression: @regex', [
            '@regex' => $regex
          ]));
          break;
        }
        if ($check_expression) {
          $route_names[] = $route_name;
        }
      }
    }
    else {
      // if it's not a regex or a path, set the route name directly in array.
      $route_names[] = $restricted_route_name;
    }
    return $route_names;
  }

  /**
   * Use a path to define a regex applicable on routes.
   *
   * @param string $path
   *    A path.
   *
   * @return string
   *    A regex
   */
  protected function getRegularExpressionFromPath(string $path): string {
    $regex = $path;

    // If there is a Wildcard "%" in path, replace by ".+"
    if (strpos($regex, '%') !== FALSE) {
      $regex = str_replace('%', '.+', $regex);
    }
    return $route_name;
    return '#'.$regex.'#';
  }

}