autoban-8.x-1.x-dev/src/Controller/AutobanController.php

src/Controller/AutobanController.php
<?php

namespace Drupal\autoban\Controller;

use Drupal\Component\Utility\Html;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Url;
use Drupal\autoban\AutobanUtils;
use Drupal\autoban\Entity\Autoban;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Provides an Autoban functional.
 */
class AutobanController extends ControllerBase implements ContainerInjectionInterface {

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * Constructs a GroupAjaxResponseSubscriber object.
   *
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack.
   */
  public function __construct(RequestStack $requestStack) {
    $this->requestStack = $requestStack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('request_stack')
    );
  }

  /**
   * Operators per user type for a query condition based on the user field.
   */
  const OPERATOR = [TRUE => '>', FALSE => '='];

  /**
   * Retrieve IP addresses for autoban rule.
   *
   * @param string $rule
   *   Autoban rule ID.
   * @param array $params
   *   Params (type, message) for special query.
   *
   * @return array
   *   IP addresses as query result.
   */
  public function getBannedIp($rule, array $params = []) {
    $this->canIpBan('111.111.111.111');

    $query_mode = $this->config('autoban.settings')->get('autoban_query_mode');
    $debug_mode = $this->config('autoban.settings')->get('autoban_debug') ?? FALSE;
    $use_wildcards = $this->config('autoban.settings')->get('autoban_use_wildcards') ?: FALSE;
    $regexp_query_mode = $query_mode == 'regexp';
    $from_analyze = AutobanUtils::isFromAnalyze($rule) && !empty($params);

    if ($from_analyze) {
      $entity = NULL;
      $message = Html::decodeEntities(trim($params['message']));
      $type = trim($params['type']);
      $threshold = 1;
      $window = NULL;
      $referer = NULL;
      $user_type = AutobanUtils::AUTOBAN_USER_ANY;
    }
    else {
      $entity = Autoban::load($rule);
      $message = trim($entity->message);
      $type = trim($entity->type);
      $threshold = (int) $entity->threshold;
      $window = !empty($entity->window) ? trim($entity->window) : NULL;
      $referer = !empty($entity->referer) ? trim($entity->referer) : NULL;
      $user_type = (int) $entity->user_type;
    }

    $connection = Database::getConnection();
    $query = $connection->select('watchdog', 'log');
    $query->fields('log', ['hostname']);

    $group = $query->orConditionGroup();

    // Checking for multiple messages divided by separator.
    $message_items = explode('|', $message);
    if (count($message_items) > 1) {
      foreach ($message_items as $message_item) {
        if ($from_analyze) {
          $group->condition('log.message', trim($message_item))
            ->condition('log.variables', trim($message_item));
        }
        else {
          if ($regexp_query_mode) {
            $group->condition('log.message', trim($message_item), 'REGEXP')
              ->condition('log.variables', trim($message_item), 'REGEXP');
          }
          else {
            if (!$use_wildcards) {
              $message_item = str_replace('%', '', $message_item);
              $group->condition('log.message', '%' . $query->escapeLike(trim($message_item)) . '%', 'LIKE')
                ->condition('log.variables', '%' . $query->escapeLike(trim($message_item)) . '%', 'LIKE');
            }
            else {
              $message_item = trim($message_item);
              if (substr($message_item, 0, 1) == '%') {
                $message_item = '%' . ltrim($message_item, '%');
              }
              if (substr($message_item, -1) == '%') {
                $message_item = rtrim($message_item, '%') . '%';
              }
              $group->condition('log.message', $message_item, 'LIKE')
                ->condition('log.variables', $message_item, 'LIKE');
            }
          }
        }
      }
    }
    else {
      if ($from_analyze) {
        $group->condition('log.message', $message)
          ->condition('log.variables', $message);
      }
      else {
        if ($regexp_query_mode) {
          $group->condition('log.message', $message, 'REGEXP')
            ->condition('log.variables', $message, 'REGEXP');
        }
        else {
          if (!$use_wildcards) {
            $message = str_replace('%', '', $message);
            $group->condition('log.message', '%' . $query->escapeLike($message) . '%', 'LIKE')
              ->condition('log.variables', '%' . $query->escapeLike($message) . '%', 'LIKE');
          }
          else {
            if (substr($message, 0, 1) == '%') {
              $message = '%' . ltrim($message, '%');
            }
            if (substr($message, -1) == '%') {
              $message = rtrim($message, '%') . '%';
            }
            $group->condition('log.message', $message, 'LIKE')
              ->condition('log.variables', $message, 'LIKE');
          }
        }
      }
    }
    $query->condition('log.type', $type)
      ->condition($group);

    if (!empty($referer)) {
      $query->condition('log.referer', '%' . $query->escapeLike($referer) . '%', 'LIKE');
    }

    if ($user_type !== AutobanUtils::AUTOBAN_RULE_ANY) {
      $authenticated = in_array($user_type, [
        AutobanUtils::AUTOBAN_USER_AUTHENTICATED,
        AutobanUtils::AUTOBAN_USER_AUTHENTICATED_STRICT,
      ]);

      $query->condition('log.uid', 0, self::OPERATOR[$authenticated]);
    }

    if ($window) {
      $date = strtotime($window);
      if ($date) {
        $query->condition('log.timestamp', $date, '>=');
      }
    }

    $query->groupBy('log.hostname');
    $query->addExpression('COUNT(log.hostname)', 'hcount');
    $query->having('COUNT(log.hostname) >= :cnt', [':cnt' => $threshold]);

    if ($debug_mode) {
      $this->messenger()->addStatus((string) $query);

      $status = $this->t('Regexp query mode: %regexp_query_mode Use wildcards: %use_wildcards', [
        '%regexp_query_mode' => $regexp_query_mode ? $this->t('Yes') : $this->t('No'),
        '%use_wildcards' => $use_wildcards ? $this->t('Yes') : $this->t('No'),
      ]);
      $this->messenger()->addStatus($status);
    }

    $result = $query->execute()->fetchAll();

    if ($result && in_array($user_type, [
      AutobanUtils::AUTOBAN_USER_ANONYMOUS_STRICT,
      AutobanUtils::AUTOBAN_USER_AUTHENTICATED_STRICT,
    ])) {
      $hostnames = array_map(function ($item) {
        return $item->hostname;
      }, $result);

      $hostnames = $connection->select('watchdog', 'log')
        ->fields('log', ['hostname'])
        ->condition('hostname', $hostnames, 'IN')
        ->condition('uid', 0, self::OPERATOR[$user_type === AutobanUtils::AUTOBAN_USER_ANONYMOUS_STRICT])
        ->execute()
        ->fetchCol();

      if ($hostnames) {
        $result = array_filter($result, function ($item) use ($hostnames) {
          return !in_array($item->hostname, $hostnames);
        });
      }
    }

    return $result;
  }

  /**
   * Get IP Ban Manager data from provider name.
   *
   * @param string $provider
   *   Ban provider ID.
   *
   * @return array
   *   Ban manager object, ban_name, ban_type.
   */
  public function getBanManagerData($provider) {
    // Retrieve Ban provider data for the current provider.
    $banProvider = $this->getBanProvidersList($provider);
    if ($banProvider) {
      // Get Ban Manager object from AutobanProviderInterface implementation.
      $service = $banProvider['service'];
      if ($service) {
        $connection = Database::getConnection();
        // Return Ban Provider's Ban IP Manager and Ban Type.
        return [
          'ban_manager' => $service->getBanIpManager($connection),
          'ban_name' => $service->getName(),
          'ban_type' => $service->getBanType(),
          'ban_meta' => $service->hasMetadata(),
        ];
      }
    }

    return NULL;
  }

  /**
   * Get IP Ban Manager data from autoban rule.
   *
   * @param string $rule
   *   Autoban rule ID.
   *
   * @return array
   *   Ban manager object and ban_type.
   */
  public function getBanManagerDataRule($rule) {
    $entity = Autoban::load($rule);
    return [
      'provider' => $this->getBanManagerData($entity->provider),
      'entity' => $entity,
    ];
  }

  /**
   * Get Ban providers list.
   *
   * @param string $provider_id
   *   Ban provider ID.
   *
   * @return array
   *   List ban providers or provider's data.
   */
  public function getBanProvidersList($provider_id = NULL) {
    $banProvidersList = [];
    // phpcs:disable
    $container = \Drupal::getContainer();
    // phpcs:enable
    $kernel = $container->get('kernel');

    // Get all services list.
    $services = $kernel->getCachedContainerDefinition()['services'];
    foreach ($services as $service_id => $value) {
      // phpcs:disable
      $service_def = unserialize($value);
      // phpcs:enable
      if (!empty($service_def['properties']) && !empty($service_def['properties']['_serviceId'])) {
        $service_id = $service_def['properties']['_serviceId'];
      }

      $aservices = explode('.', $service_id);
      $service_name = end($aservices);
      if ($service_name === 'ban_provider') {
        $service = $container->get($service_id);
        $id = $service->getId();
        $name = $service->getName();
        $banProvidersList[$id] = ['name' => $name, 'service' => $service];
      }
    }

    if (!empty($provider_id)) {
      return $banProvidersList[$provider_id] ?? NULL;
    }
    else {
      return $banProvidersList;
    }
  }

  /**
   * Direct ban controller.
   *
   * @param string $ips
   *   IP addresses (comma delimited).
   * @param string $provider
   *   Ban provider name.
   */
  public function banIpAction($ips, $provider) {
    $banManagerData = $this->getBanManagerData($provider);
    $banData = [
      'provider' => $banManagerData,
      'entity' => ['id' => 'direct'],
    ];

    $ips_arr = explode(',', $ips);
    foreach ($ips_arr as $ip) {
      $banned = $this->banIp($ip, $banData, TRUE);

      if ($banned) {
        $this->messenger()->addMessage($this->t('IP %ip has been banned (@provider).', [
          '%ip' => $ip,
          '@provider' => $banManagerData['ban_name'],
        ])
        );
      }
      else {
        $this->messenger()->addMessage($this->t('IP %ip has not been banned', ['%ip' => $ip]), 'warning');
      }
    }

    $destination = $this->getDestinationArray();
    if (!empty($destination)) {
      $url = Url::fromUserInput($destination['destination']);
      return new RedirectResponse($url->toString());
    }
  }

  /**
   * Ban address.
   *
   * @param string $ip
   *   IP address.
   * @param array $banData
   *   Ban manager data.
   * @param bool $debug
   *   Show debug message.
   *
   * @return bool
   *   IP banned status.
   */
  public function banIp($ip, array $banData, $debug = FALSE) {
    if (empty($banData)) {
      if ($debug) {
        $this->messenger()->addMessage($this->t('Empty banData.'), 'warning');
      }
      return FALSE;
    }

    $banManagerData = $banData['provider'] ?? NULL;
    if (empty($banManagerData)) {
      if ($debug) {
        $this->messenger()->addMessage($this->t('Empty banManagerData.'), 'warning');
      }
      return FALSE;
    }

    $banManager = $banManagerData['ban_manager'];
    if (!$this->canIpBan($ip)) {
      if ($debug) {
        $this->messenger()->addMessage($this->t('Cannot ban this IP.'), 'warning');
      }
      return FALSE;
    }

    if ($banManager->isBanned($ip)) {
      if ($debug) {
        $this->messenger()->addMessage($this->t('This IP already banned.'), 'warning');
      }
      return FALSE;
    }

    $banType = $banManagerData['ban_type'];

    $hasMeta = $banManagerData['ban_meta'] && method_exists($banManager, 'setMetadata');
    if ($hasMeta && !empty($banData['entity'])) {
      $metadata = (array) $banData['entity'];
      $metadata['reporter'] = 'autoban';
      $banManager->setMetadata($metadata);
    }

    switch ($banType) {
      case 'single':
        $banManager->banIp($ip);
        break;

      case 'range':
        $ip_range = $this->createIpRange($ip);
        if (empty($ip_range)) {
          // If cannot create IP range banned single IP.
          $banManager->banIp($ip);
        }
        else {
          $banManager->banIp($ip_range['ip_start'], $ip_range['ip_end']);
        }
        break;
    }

    return TRUE;
  }

  /**
   * Ban addresses.
   *
   * @param array $ip_list
   *   IP addresses list.
   * @param string $rule
   *   Autoban rule ID.
   *
   * @return int
   *   IP banned count.
   */
  public function banIpList(array $ip_list, $rule) {
    $count = 0;
    if (!empty($ip_list) && $rule) {
      // Retrieve Ban data for current rule.
      $banData = $this->getBanManagerDataRule($rule);
      $banManagerData = $banData['provider'] ?? NULL;
      if ($banManagerData) {
        foreach ($ip_list as $item) {
          $banStatus = $this->banIp($item->hostname, $banData);
          if ($banStatus) {
            $count++;
            $this->messenger()->addMessage($this->t('IP %ip has been banned (@provider).', [
              '%ip' => $item->hostname,
              '@provider' => $banManagerData['ban_name'],
            ])
            );
          }
        }
      }
      else {
        $this->messenger()->addMessage($this->t('No ban manager for rule %rule', ['%rule' => $rule]), 'error');
      }
    }

    return $count;
  }

  /**
   * Check IP address for ban.
   *
   * @param string $ip
   *   IP candidate for ban.
   *
   * @return bool
   *   Can ban.
   */
  public function canIpBan($ip) {
    // You cannot ban your current IP address.
    $current_ip = $this->requestStack->getCurrentRequest()->getClientIp();
    if ($ip == $current_ip) {
      return FALSE;
    }

    // The IP address must not be whitelisted.
    if ($this->whitelistIp($ip)) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Is IP address in subnet?
   *
   * @param string $ip
   *   IP address for match check.
   * @param string $network
   *   IP subnet.
   * @param string $cidr
   *   CIDR.
   *
   * @return bool
   *   IP mathes for subnet.
   */
  private function cidrMatch($ip, $network, $cidr) {
    return ((ip2long($ip) & ~((1 << (32 - $cidr)) - 1)) == ip2long($network));
  }

  /**
   * Is IP address in whitelist?
   *
   * @param string $ip
   *   IP address for check.
   *
   * @return bool
   *   IP address in whitelist.
   */
  private function whitelistIp($ip) {
    $autoban_whitelist = $this->config('autoban.settings')->get('autoban_whitelist');
    if (!empty($autoban_whitelist)) {
      $real_host = gethostbyaddr($ip);
      $autoban_whitelist_arr = explode(PHP_EOL, $autoban_whitelist);
      foreach ($autoban_whitelist_arr as $whitelist_ip) {
        $whitelist_ip = trim($whitelist_ip);
        if (empty($whitelist_ip)) {
          continue;
        }

        // Block comment.
        if (substr($whitelist_ip, 0, 1) == '#') {
          continue;
        }

        // Inline comment.
        $whitelist_ip_arr = explode('#', $whitelist_ip);
        if (count($whitelist_ip_arr) > 1) {
          $whitelist_ip = trim($whitelist_ip_arr[0]);
        }

        $whitelist_ip_arr = explode('/', $whitelist_ip);
        // CIDR match.
        if (count($whitelist_ip_arr) > 1) {
          $in_list = $this->cidrMatch($ip, $whitelist_ip_arr[0], (int) $whitelist_ip_arr[1]);
        }
        else {
          $in_list = ($whitelist_ip == $ip);
        }
        if ($in_list) {
          return TRUE;
        }

        // Check for domain.
        if ($real_host) {
          $real_host_arr = explode($whitelist_ip, $real_host);
          if (count($real_host_arr) == 2 && empty($real_host_arr[1])) {
            return TRUE;
          }
        }
      }
    }
    return FALSE;
  }

  /**
   * Create IP range from single IP.
   *
   * @param string $hostname
   *   IP address for ban.
   *
   * @return array
   *   IP range string for insert to ban table.
   */
  private function createIpRange($hostname) {
    // Make range IP from aaa.bbb.ccc.ddd to aaa.bbb.ccc.0 - aaa.bbb.ccc.255 .
    if (!ip2long($hostname)) {
      // Only IPV4 is available for IP range.
      return NULL;
    }
    $parts = explode('.', $hostname);
    if (count($parts) == 4) {
      $parts[3] = '0';
      $ip_start = implode('.', $parts);
      $parts[3] = '255';
      $ip_end = implode('.', $parts);
      return ['ip_start' => $ip_start, 'ip_end' => $ip_end];
    }
    return NULL;
  }

  /**
   * Get user type names list.
   *
   * @param int $index
   *   User type index (optional).
   *
   * @return string|array
   *   User type name or user type names list.
   */
  public function userTypeList($index = NULL) {
    $user_types = [
      AutobanUtils::AUTOBAN_USER_ANY => $this->t('Any'),
      AutobanUtils::AUTOBAN_USER_ANONYMOUS => $this->t('Anonymous'),
      AutobanUtils::AUTOBAN_USER_AUTHENTICATED => $this->t('Authenticated'),
      AutobanUtils::AUTOBAN_USER_ANONYMOUS_STRICT => $this->t('Anonymous strict'),
      AutobanUtils::AUTOBAN_USER_AUTHENTICATED_STRICT => $this->t('Authenticated strict'),
    ];

    if ($index === NULL) {
      return $user_types;
    }
    else {
      if (!isset($user_types[$index])) {
        $index = AutobanUtils::AUTOBAN_USER_ANY;
      }
      return $user_types[$index];
    }
  }

  /**
   * Get rule type names list.
   *
   * @param int $index
   *   Rule type index (optional).
   *
   * @return string|array
   *   User rule name or rule type names list.
   */
  public function ruleTypeList($index = NULL) {
    $rule_types = [
      AutobanUtils::AUTOBAN_RULE_ANY => $this->t('Any'),
      AutobanUtils::AUTOBAN_RULE_MANUAL => $this->t('Manual'),
      AutobanUtils::AUTOBAN_RULE_AUTO => $this->t('Automatic'),
    ];

    if ($index === NULL) {
      return $rule_types;
    }
    else {
      if (!isset($rule_types[$index])) {
        $index = AutobanUtils::AUTOBAN_RULE_ANY;
      }
      return $rule_types[$index];
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc