coveo-1.0.0-alpha1/modules/coveo_secured_search/src/Entity/CoveoCustomSecurityProvider.php
modules/coveo_secured_search/src/Entity/CoveoCustomSecurityProvider.php
<?php
declare(strict_types=1);
namespace Drupal\coveo_secured_search\Entity;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\coveo\Entity\CoveoOrganizationInterface;
use Drupal\coveo\Entity\CoveoSearchComponent;
use Drupal\coveo_secured_search\Event\CoveoSecurityProviderAlter;
use Drupal\coveo_secured_search\Plugin\CoveoCustomSecurityProviderManagerInterface;
use Drupal\coveo_secured_search\Plugin\CoveoCustomSecurityProviderPluginInterface;
use NecLimDul\Coveo\SecurityCache\Model\SecurityProviderModel;
use Neclimdul\OpenapiPhp\Helper\Logging\Error as ApiError;
use Psr\Log\LogLevel;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* Defines Coveo Search configuration entity.
*
* Defines configuration for exposing searches through user key generation.
*
* @ConfigEntityType(
* id = "coveo_custom_security_provider",
* label = @Translation("Coveo Security Provider"),
* label_collection = @Translation("Coveo Security Providers"),
* label_singular = @Translation("Coveo security provider"),
* label_plural = @Translation("Coveo security providers"),
* label_count = @PluralTranslation(
* singular = "@count Coveo security provider",
* plural = "@count Coveo security providers",
* ),
* handlers = {
* "form" = {
* "add" = "Drupal\coveo_secured_search\Form\SecurityProviders\CustomSecurityProviderForm",
* "edit" = "Drupal\coveo_secured_search\Form\SecurityProviders\CustomSecurityProviderForm",
* "delete" = "Drupal\coveo_secured_search\Form\SecurityProviders\CustomSecurityProviderDeleteForm",
* },
* "list_builder" = "Drupal\coveo_secured_search\CustomSecurityProviderListBuilder",
* "storage" = "Drupal\coveo_secured_search\SecurityProviderStorage",
* },
* admin_permission = "administer coveo search",
* config_prefix = "custom_security_provider",
* entity_keys = {
* "id" = "name",
* "label" = "label",
* "coveo_name" = "coveo_name",
* "description" = "description",
* "security_provider" = "security_provider",
* },
* links = {
* "edit-form" = "/admin/config/search/coveo/security_providers/manage/{coveo_custom_security_provider}",
* "delete-form" = "/admin/config/search/coveo/security_providers/manage/{coveo_custom_security_provider}/delete",
* "collection" = "/admin/config/search/coveo/security_providers",
* },
* config_export = {
* "name",
* "label",
* "coveo_name",
* "description",
* "security_provider",
* "push_sources",
* "organization_name",
* }
* )
*
* @phpstan-type SyncOperation callable(string, string, \Drupal\coveo_search_api\Plugin\search_api\backend\SearchApiCoveoBackend[]): void
*/
class CoveoCustomSecurityProvider extends ConfigEntityBase implements CoveoCustomSecurityProviderInterface {
use LoggerChannelTrait, MessengerTrait;
/**
* The name of the provider.
*/
protected string $name;
/**
* The provider label.
*/
protected string $label;
/**
* A description of the provider.
*/
protected ?string $description;
/**
* Coveo security provider plugin type.
*/
protected ?string $security_provider;
/**
* Coveo security provider.
*/
protected ?string $organization_name;
/**
* Coveo push sources.
*/
protected ?array $push_sources;
/**
* Custom Security Provider plugin manager.
*/
private CoveoCustomSecurityProviderManagerInterface $manager;
/**
* Event dispatcher.
*/
private EventDispatcherInterface $eventDispatcher;
public function __construct(array $values, $entity_type) {
parent::__construct($values, $entity_type);
$this->manager = \Drupal::service('plugin.manager.coveo_custom_security_provider');
$this->eventDispatcher = \Drupal::service('event_dispatcher');
}
/**
* {@inheritdoc}
*/
#[\Override]
public function id(): string|null {
return $this->name ?? NULL;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getDescription(): string|null {
return $this->description ?? NULL;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getSecurityProviderPluginId(): string|null {
return $this->security_provider ?? NULL;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getPushSourceIds(): array {
return $this->push_sources ?? [];
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getOrganizationName(): string|null {
return $this->organization_name ?? NULL;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getCoveoName(): string|null {
return $this->coveo_name ?? NULL;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getSecurityProviderId(): string {
if ($this->getCoveoName()) {
return $this->getCoveoName();
}
// Should this be conditional?
$prefix = $this->getOrganization()?->getPrefix();
return $prefix . '-' . $this->getSecurityProviderBaseName() . '-' . $this->id();
}
/**
* The base name used for identifying this provider in Coveo.
*
* @return string
* Provider base name.
*/
protected function getSecurityProviderBaseName(): string {
return $this->manager->getDefinition($this->getSecurityProviderPluginId())['baseName'];
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getSecurityProvider(): CoveoCustomSecurityProviderPluginInterface {
$instance = $this->manager->createInstance(
$this->getSecurityProviderPluginId(),
[
'provider_id' => $this->getSecurityProviderId(),
]
);
return $instance;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function generateToken(
CoveoSearchComponent $search,
AccountInterface $account,
): string {
$token = FALSE;
// @todo figure out how to avoid unnecessary token generation
// Sessions are broken and can't time out for refresh broken tokens so
// disabled until I can find a fix.
// $session = $request->getSession();
// $session->get('coveo_token', FALSE);
// $session->remove('coveo_token');
/* @phpstan-ignore-next-line */
if (!$token) {
try {
return $this->getSecurityProvider()
->generateToken($search, $account);
}
catch (PluginException $e) {
// dpm($e->getMessage());
// @todo log plugin failure.
// Call through and return false as a failure.
}
}
return 'failure';
}
/**
* {@inheritDoc}
*/
#[\Override]
public function getOrganization(): CoveoOrganizationInterface|null {
$id = $this->getOrganizationName();
if ($id) {
return $this->entityTypeManager()
->getStorage('coveo_organization')
->load($id);
}
return NULL;
}
/**
* {@inheritdoc}
*/
#[\Override]
public function calculateDependencies() {
parent::calculateDependencies();
$organization = $this->getOrganization();
$this->addDependency('config', $organization->getConfigDependencyName());
return $this;
}
/**
* {@inheritDoc}
*/
#[\Override]
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
\Drupal::service('plugin.manager.coveo_security_provider')->clearCachedDefinitions();
$organization = $this->getOrganization();
if ($organization && !$organization->isReadOnly()) {
$this->writeToCoveo();
}
}
/**
* {@inheritDoc}
*/
#[\Override]
public static function postDelete(EntityStorageInterface $storage, array $entities) {
\Drupal::service('plugin.manager.coveo_security_provider')->clearCachedDefinitions();
foreach ($entities as $entity) {
assert($entity instanceof static);
$organization = $entity->getOrganization();
if ($organization !== NULL && !$organization->isReadOnly()) {
$entity->deleteFromCoveo();
}
}
}
/**
* Sync this custom provider to Coveo.
*
* @see https://docs.coveo.com/en/85/index-content/create-or-update-a-security-identity-provider-for-a-secured-push-source
* @see https://platform.cloud.coveo.com/docs?urls.primaryName=SecurityCache#/Security%20Providers/rest_organizations_paramId_securityproviders_paramId_put
*/
private function writeToCoveo(): void {
$provider_id = $this->getSecurityProviderId();
$base_name = $this->getSecurityProviderBaseName();
// Create SecurityProviderModel based on this entities' config.
// @todo it probably makes sense for the plugin to generate this so it
// can do any quirky customization of the provider in Coveo it needs to.
// @todo should this cascade to email provider by default?
// Generate a provider model.
$params = new SecurityProviderModel();
$params->setId($provider_id);
$params->setDisplayName($base_name . ' - ' . $this->label());
$params->setType(SecurityProviderModel::TYPE_EXPANDED);
$params->setNodeRequired(FALSE);
// Trigger alter event.
$this->eventDispatcher->dispatch(new CoveoSecurityProviderAlter(
$this,
$params
));
// Get a SecurityProviderApi instance.
// Call API.
$response = $this->getOrganization()?->getSecurityProviderApi()
->createOrUpdateSecurityProvider(
$this->getOrganization()->getOrganizationId(),
$provider_id,
$params,
);
if ($response === NULL) {
$this->messenger()
->addError('Error creating request to sync Coveo provider.');
}
elseif (!$response->isSuccess()) {
$this->messenger()->addError('Error syncing provider to Coveo. Check logs for more information.');
ApiError::logError(
$this->getLogger('coveo'),
$response,
);
}
}
/**
* Sync this custom provider to Coveo.
*
* @see https://docs.coveo.com/en/85/index-content/create-or-update-a-security-identity-provider-for-a-secured-push-source
* @see https://platform.cloud.coveo.com/docs?urls.primaryName=SecurityCache#/Security%20Providers/rest_organizations_paramId_securityproviders_paramId_delete
*/
private function deleteFromCoveo(): void {
$provider_id = $this->getSecurityProviderId();
// @todo this doesn't seem to actually do anything. Coveo is aware and
// should be fixing it.
// Get a SecurityProviderApi instance.
$response = $this->getOrganization()?->getSecurityProviderApi()
->removeSecurityProvider(
$this->getOrganization()->getOrganizationId(),
$provider_id,
);
if ($response === NULL) {
$this->messenger()
->addError('Error creating request to delete Coveo provider.');
}
elseif ($response->getResponse()->getStatusCode() == 412) {
$this->messenger()->addWarning('Known delete failure. Coveo can\'t actually delete a provider with its delete method so you will have to manually clean up your push source securityProviderReferences.');
ApiError::logError(
$this->getLogger('coveo'),
$response,
LogLevel::DEBUG,
'Known delete failure. ' . $response->getResponse()->getBody(),
);
}
elseif (!$response->isSuccess()) {
$this->messenger()->addError('Error syncing provider to Coveo. Check logs for more information.');
ApiError::logError(
$this->getLogger('coveo'),
$response,
);
}
}
}
