acquia_search_solr-8.x-1.0-beta9/src/EventSubscriber/AcquiaSearchSolrSubscriber.php

src/EventSubscriber/AcquiaSearchSolrSubscriber.php
<?php

namespace Drupal\acquia_search_solr\EventSubscriber;

use Drupal\acquia_search_solr\AcquiaCryptConnector;
use Drupal\acquia_search_solr\Helper\Flood;
use Drupal\acquia_search_solr\Helper\Runtime;
use Drupal\acquia_search_solr\Helper\Storage;
use Drupal\Component\Utility\Crypt;
use Drupal\search_api_solr\Solarium\EventDispatcher\EventProxy;
use Solarium\Core\Client\Adapter\AdapterHelper;
use Solarium\Core\Client\Client;
use Solarium\Core\Client\Response;
use Solarium\Core\Event\Events;
use Solarium\Core\Plugin\AbstractPlugin;
use Solarium\Exception\HttpException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Class AcquiaSearchSolrSubscriber.
 *
 * Extends Solarium plugin for the Acquia Search module: authenticate, etc.
 *
 * @package Drupal\acquia_search_solr\EventSubscriber
 */
class AcquiaSearchSolrSubscriber extends AbstractPlugin implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   *
   * @var \Solarium\Client
   */
  protected $client;

  /**
   * Array of derived keys, keyed by environment id.
   *
   * @var array
   */
  protected $derivedKey = [];

  /**
   * Nonce.
   *
   * @var string
   */
  protected $nonce = '';

  /**
   * URI.
   *
   * @var string
   */
  protected $uri = '';

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      Events::PRE_EXECUTE_REQUEST => 'preExecuteRequest',
      Events::POST_EXECUTE_REQUEST => 'postExecuteRequest',
    ];
  }

  /**
   * Build Acquia Search Solr Authenticator.
   *
   * @param \Drupal\search_api_solr\Solarium\EventDispatcher\EventProxy $event
   *   PreExecuteRequest event.
   */
  public function preExecuteRequest(EventProxy $event) {

    /** @var \Solarium\Core\Client\Request $request */
    $request = $event->getRequest();

    if (!($this->client instanceof Client)) {
      return;
    }

    // Run Flood control checks.
    if (!Flood::isAllowed($request->getHandler())) {
      // If request should be blocked, show an error message.
      $message = 'The Acquia Search flood control mechanism has blocked a Solr query due to API usage limits. You should retry in a few seconds. Contact the site administrator if this message persists.';
      \Drupal::messenger()->addError($message);

      // Build a static response which avoids a network request to Solr.
      $response = new Response($message, ['HTTP/1.1 429 Too Many Requests']);
      $event->setResponse($response);
      $event->stopPropagation();
      return;
    }
    $request->addParam('request_id', uniqid(), TRUE);
    if ($request->getFileUpload()) {
      $helper = new AdapterHelper();
      $body = $helper->buildUploadBodyFromRequest($request);
      $request->setRawData($body);
    }

    // If we're hosted on Acquia, and have an Acquia request ID,
    // append it to the request so that we map Solr queries to Acquia search
    // requests.
    if (isset($_ENV['HTTP_X_REQUEST_ID'])) {
      $xid = empty($_ENV['HTTP_X_REQUEST_ID']) ? '-' : $_ENV['HTTP_X_REQUEST_ID'];
      $request->addParam('x-request-id', $xid, TRUE);
    }
    $endpoint = $this->client->getEndpoint();
    $this->uri = AdapterHelper::buildUri($request, $endpoint);

    $this->nonce = Crypt::randomBytesBase64(24);
    $raw_post_data = $request->getRawData();
    // We don't have any raw POST data for pings only.
    if (!$raw_post_data) {
      $parsed_url = parse_url($this->uri);
      $path = $parsed_url['path'] ?? '/';
      $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
      $raw_post_data = $path . $query;
    }

    $cookie = $this->calculateAuthCookie($raw_post_data, $this->nonce);
    $request->addHeader('Cookie: ' . $cookie);
    $request->addHeader('User-Agent: ' . 'acquia_search_solr/' . Storage::getVersion());

  }

  /**
   * Validate response.
   *
   * @param \Drupal\search_api_solr\Solarium\EventDispatcher\EventProxy $event
   *   postExecuteRequest event.
   *
   * @throws \Solarium\Exception\HttpException
   */
  public function postExecuteRequest(EventProxy $event) {
    if (!($this->client instanceof Client)) {
      return;
    }

    $response = $event->getResponse();

    if ($response->getStatusCode() != 200) {
      throw new HttpException(
        $response->getStatusMessage(),
        $response->getStatusCode(),
        $response->getBody()
      );
    }

    if ($event->getRequest()->getHandler() == 'admin/ping') {
      return;
    }

    $this->authenticateResponse($event->getResponse(), $this->nonce, $this->uri);

  }

  /**
   * Validate the hmac for the response body.
   *
   * @param \Solarium\Core\Client\Response $response
   *   Solarium Response.
   * @param string $nonce
   *   Nonce.
   * @param string $url
   *   Url.
   *
   * @return \Solarium\Core\Client\Response
   *   Solarium Response.
   *
   * @throws \Solarium\Exception\HttpException
   */
  protected function authenticateResponse(Response $response, $nonce, $url) {

    $hmac = $this->extractHmac($response->getHeaders());
    if (!$this->validateResponse($hmac, $nonce, $response->getBody())) {
      throw new HttpException('Authentication of search content failed url: ' . $url);
    }

    return $response;

  }

  /**
   * Look in the headers and get the hmac_digest out.
   *
   * @param array $headers
   *   Headers array.
   *
   * @return string
   *   Hmac_digest or empty string.
   */
  public function extractHmac(array $headers): string {

    $reg = [];

    if (is_array($headers)) {
      foreach ($headers as $value) {
        if (stristr($value, 'pragma') && preg_match("/hmac_digest=([^;]+);/i", $value, $reg)) {
          return trim($reg[1]);
        }
      }
    }

    return '';

  }

  /**
   * Validate the authenticity of returned data using a nonce and HMAC-SHA1.
   *
   * @param string $hmac
   *   HMAC.
   * @param string $nonce
   *   Nonce.
   * @param string $string
   *   Data string.
   * @param string $derived_key
   *   Derived key.
   * @param string $env_id
   *   Environment Id.
   *
   * @return bool
   *   TRUE if response is valid.
   */
  public function validateResponse($hmac, $nonce, $string, $derived_key = NULL, $env_id = NULL) {

    if (empty($derived_key)) {
      $derived_key = $this->getDerivedKey($env_id);
    }

    return $hmac == hash_hmac('sha1', $nonce . $string, $derived_key);

  }

  /**
   * Get the derived key.
   *
   * Get the derived key for the solr hmac using the information shared with
   * acquia.com.
   *
   * @param string $env_id
   *   Environment Id.
   *
   * @return string|null
   *   Derived Key.
   */
  public function getDerivedKey($env_id = NULL): ?string {

    if (empty($env_id)) {
      $env_id = $this->client->getEndpoint()->getKey();
    }

    // Get derived key for Acquia Search V3.
    $search_v3_index = $this->getSearchIndexKeys();
    if ($search_v3_index) {
      $this->derivedKey[$env_id] = AcquiaCryptConnector::createDerivedKey($search_v3_index['product_policies']['salt'], $search_v3_index['key'], $search_v3_index['secret_key']);
      return $this->derivedKey[$env_id];
    }

    return NULL;

  }

  /**
   * Creates an authenticator based on a data string and HMAC-SHA1.
   *
   * @param string $string
   *   Data string.
   * @param string $nonce
   *   Nonce.
   * @param string $derived_key
   *   Derived key.
   * @param string $env_id
   *   Environment Id.
   *
   * @return string
   *   Auth cookie string.
   */
  public function calculateAuthCookie($string, $nonce, $derived_key = NULL, $env_id = NULL) {

    if (empty($derived_key)) {
      $derived_key = $this->getDerivedKey($env_id);
    }

    if (empty($derived_key)) {
      // Expired or invalid subscription - don't continue.
      return '';
    }

    $time = time();

    $hmac = hash_hmac('sha1', $time . $nonce . $string, $derived_key);

    return sprintf('acquia_solr_time=%s; acquia_solr_nonce=%s; acquia_solr_hmac=%s;', $time, $nonce, $hmac);

  }

  /**
   * Fetches the Acquia Search v3 index keys.
   *
   * @return array|null
   *   Search v3 index keys.
   */
  public function getSearchIndexKeys(): ?array {

    $core_service = Runtime::getPreferredSearchCoreService();

    // Preferred core isn't available - you have to configure it using settings
    // described in the README.txt.
    if (!$core_service->isPreferredCoreAvailable()) {
      return NULL;
    }

    return $core_service->getPreferredCore()['data'];

  }

}

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

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