class SearchSubscriber in Acquia Search 3.x
Same name and namespace in other branches
- 2.x src/EventSubscriber/SearchSubscriber.php \Drupal\acquia_search\EventSubscriber\SearchSubscriber
Class SearchSubscriber.
Extends Solarium plugin for the Acquia Search module: authenticate, etc.
@package Drupal\acquia_search\EventSubscriber
Hierarchy
- class \Drupal\acquia_search\EventSubscriber\SearchSubscriber extends \Solarium\Core\Plugin\AbstractPlugin implements \Symfony\Component\EventDispatcher\EventSubscriberInterface
Expanded class hierarchy of SearchSubscriber
1 string reference to 'SearchSubscriber'
1 service uses SearchSubscriber
File
- src/
EventSubscriber/ SearchSubscriber.php, line 27
Namespace
Drupal\acquia_search\EventSubscriberView source
class SearchSubscriber 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 \Solarium\Core\Event\PreExecuteRequest $event
* PreExecuteRequest event.
*/
public function preExecuteRequest(PreExecuteRequest $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/' . Storage::getVersion());
}
/**
* Validate response.
*
* @param \Solarium\Core\Event\PostExecuteRequest $event
* postExecuteRequest event.
*
* @throws \Solarium\Exception\HttpException
*/
public function postExecuteRequest(PostExecuteRequest $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'];
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
SearchSubscriber:: |
protected | property | ||
SearchSubscriber:: |
protected | property | Array of derived keys, keyed by environment id. | |
SearchSubscriber:: |
protected | property | Nonce. | |
SearchSubscriber:: |
protected | property | URI. | |
SearchSubscriber:: |
protected | function | Validate the hmac for the response body. | |
SearchSubscriber:: |
public | function | Creates an authenticator based on a data string and HMAC-SHA1. | |
SearchSubscriber:: |
public | function | Look in the headers and get the hmac_digest out. | |
SearchSubscriber:: |
public | function | Get the derived key. | |
SearchSubscriber:: |
public | function | Fetches the Acquia Search v3 index keys. | |
SearchSubscriber:: |
public static | function | ||
SearchSubscriber:: |
public | function | Validate response. | |
SearchSubscriber:: |
public | function | Build Acquia Search Solr Authenticator. | |
SearchSubscriber:: |
public | function | Validate the authenticity of returned data using a nonce and HMAC-SHA1. |