diff --git a/islandora.services.yml b/islandora.services.yml index b08e06994..749f578c5 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -59,3 +59,8 @@ services: islandora.gemini.lookup: class: Drupal\islandora\GeminiLookup arguments: ['@islandora.gemini.client', '@jwt.authentication.jwt', '@islandora.media_source_service', '@http_client', '@logger.channel.islandora'] + islandora.stomp.auth_header_listener: + class: Drupal\islandora\EventSubscriber\StompHeaderEventSubscriber + arguments: ['@jwt.authentication.jwt'] + tags: + - { name: event_subscriber } diff --git a/src/Event/StompHeaderEvent.php b/src/Event/StompHeaderEvent.php new file mode 100644 index 000000000..d6d93c22c --- /dev/null +++ b/src/Event/StompHeaderEvent.php @@ -0,0 +1,97 @@ +entity = $entity; + $this->user = $user; + $this->data = $data; + $this->configuration = $configuration; + $this->headers = new ParameterBag(); + } + + /** + * {@inheritdoc} + */ + public function getEntity() { + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function getUser() { + return $this->user; + } + + /** + * {@inheritdoc} + */ + public function getData() { + return $this->data; + } + + /** + * {@inheritdoc} + */ + public function getHeaders() { + return $this->headers; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + +} diff --git a/src/Event/StompHeaderEventException.php b/src/Event/StompHeaderEventException.php new file mode 100644 index 000000000..c9ee3878e --- /dev/null +++ b/src/Event/StompHeaderEventException.php @@ -0,0 +1,8 @@ +account = $account; $this->entityTypeManager = $entity_type_manager; $this->eventGenerator = $event_generator; $this->stomp = $stomp; - $this->auth = $auth; + $this->eventDispatcher = $event_dispatcher; } /** @@ -105,7 +107,7 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity_type.manager'), $container->get('islandora.eventgenerator'), $container->get('islandora.stomp'), - $container->get('jwt.authentication.jwt') + $container->get('event_dispatcher') ); } @@ -113,29 +115,26 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ public function execute($entity = NULL) { - - // Include a token for later authentication in the message. - $token = $this->auth->generateToken(); - if (empty($token)) { - // JWT isn't properly configured. Log and notify user. - \Drupal::logger('islandora')->error( - t('Error getting JWT token for message. Check JWT Configuration.') - ); - drupal_set_message( - t('Error getting JWT token for message. Check JWT Configuration.'), 'error' - ); - return; - } - // Generate event as stomp message. try { $user = $this->entityTypeManager->getStorage('user')->load($this->account->id()); $data = $this->generateData($entity); + + $event = $this->eventDispatcher->dispatch( + StompHeaderEvent::EVENT_NAME, + new StompHeaderEvent($entity, $user, $data, $this->getConfiguration()) + ); + $message = new Message( $this->eventGenerator->generateEvent($entity, $user, $data), - ['Authorization' => "Bearer $token"] + $event->getHeaders()->all() ); } + catch (StompHeaderEventException $e) { + \Drupal::logger('islandora')->error($e->getMessage()); + drupal_set_message($e->getMessage(), 'error'); + return; + } catch (\RuntimeException $e) { // Notify the user the event couldn't be generated and abort. \Drupal::logger('islandora')->error( diff --git a/src/EventSubscriber/JwtEventSubscriber.php b/src/EventSubscriber/JwtEventSubscriber.php index 4ea049c4c..93c057a30 100644 --- a/src/EventSubscriber/JwtEventSubscriber.php +++ b/src/EventSubscriber/JwtEventSubscriber.php @@ -19,6 +19,8 @@ */ class JwtEventSubscriber implements EventSubscriberInterface { + const AUDIENCE = 'islandora'; + /** * User storage to load users. * @@ -100,6 +102,7 @@ public function setIslandoraClaims(JwtAuthGenerateEvent $event) { $event->addClaim('sub', $this->currentUser->getAccountName()); $event->addClaim('roles', $this->currentUser->getRoles(FALSE)); + $event->addClaim('aud', [static::AUDIENCE]); } /** @@ -111,6 +114,18 @@ public function setIslandoraClaims(JwtAuthGenerateEvent $event) { public function validate(JwtAuthValidateEvent $event) { $token = $event->getToken(); + $aud = $token->getClaim('aud'); + + if (!$aud) { + // Deprecation cycle: Avoid invalidating if there's no "aud" claim, to + // allow tokens in flight before the introduction of this claim to remain + // valid. + } + elseif (!in_array(static::AUDIENCE, $aud, TRUE)) { + $event->invalidate('Missing audience entry.'); + return; + } + $uid = $token->getClaim('webid'); $name = $token->getClaim('sub'); $roles = $token->getClaim('roles'); diff --git a/src/EventSubscriber/StompHeaderEventSubscriber.php b/src/EventSubscriber/StompHeaderEventSubscriber.php new file mode 100644 index 000000000..47792efd2 --- /dev/null +++ b/src/EventSubscriber/StompHeaderEventSubscriber.php @@ -0,0 +1,63 @@ +auth = $auth; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + StompHeaderEventInterface::EVENT_NAME => ['baseAuth', -100], + ]; + } + + /** + * Event callback; generate and add base authorization header if none is set. + */ + public function baseAuth(StompHeaderEventInterface $stomp_event) { + $headers = $stomp_event->getHeaders(); + if (!$headers->has('Authorization')) { + $token = $this->auth->generateToken(); + if (empty($token)) { + // JWT does not seem to be properly configured. + // phpcs:ignore DrupalPractice.General.ExceptionT.ExceptionT + throw new StompHeaderEventException($this->t('Error getting JWT token for message. Check JWT Configuration.')); + } + else { + $headers->set('Authorization', "Bearer $token"); + } + } + + } + +} diff --git a/src/Plugin/Action/AbstractGenerateDerivative.php b/src/Plugin/Action/AbstractGenerateDerivative.php index fa41f7ff5..5c960c954 100644 --- a/src/Plugin/Action/AbstractGenerateDerivative.php +++ b/src/Plugin/Action/AbstractGenerateDerivative.php @@ -2,18 +2,14 @@ namespace Drupal\islandora\Plugin\Action; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Url; -use Drupal\islandora\IslandoraUtils; use Drupal\islandora\EventGenerator\EmitEvent; -use Drupal\islandora\EventGenerator\EventGeneratorInterface; +use Drupal\islandora\IslandoraUtils; use Drupal\islandora\MediaSource\MediaSourceService; -use Drupal\jwt\Authentication\Provider\JwtAuth; use Drupal\token\TokenInterface; -use Stomp\StatefulStomp; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -42,77 +38,17 @@ class AbstractGenerateDerivative extends EmitEvent { */ protected $token; - /** - * Constructs a EmitEvent action. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\Session\AccountInterface $account - * Current user. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * Entity type manager. - * @param \Drupal\islandora\EventGenerator\EventGeneratorInterface $event_generator - * EventGenerator service to serialize AS2 events. - * @param \Stomp\StatefulStomp $stomp - * Stomp client. - * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth - * JWT Auth client. - * @param \Drupal\islandora\IslandoraUtils $utils - * Islandora utility functions. - * @param \Drupal\islandora\MediaSource\MediaSourceService $media_source - * Media source service. - * @param \Drupal\token\TokenInterface $token - * Token service. - */ - public function __construct( - array $configuration, - $plugin_id, - $plugin_definition, - AccountInterface $account, - EntityTypeManagerInterface $entity_type_manager, - EventGeneratorInterface $event_generator, - StatefulStomp $stomp, - JwtAuth $auth, - IslandoraUtils $utils, - MediaSourceService $media_source, - TokenInterface $token - ) { - parent::__construct( - $configuration, - $plugin_id, - $plugin_definition, - $account, - $entity_type_manager, - $event_generator, - $stomp, - $auth - ); - $this->utils = $utils; - $this->mediaSource = $media_source; - $this->token = $token; - } - /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('current_user'), - $container->get('entity_type.manager'), - $container->get('islandora.eventgenerator'), - $container->get('islandora.stomp'), - $container->get('jwt.authentication.jwt'), - $container->get('islandora.utils'), - $container->get('islandora.media_source_service'), - $container->get('token') - ); + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + + $instance->setUtilsService($container->get('islandora.utils')); + $instance->setMediaSourceService($container->get('islandora.media_source_service')); + $instance->setTokenService($container->get('token')); + + return $instance; } /** @@ -334,4 +270,25 @@ protected function getEntityById($entity_id) { return ''; } + /** + * Setter for the Islanodra utils service. + */ + public function setUtilsService(IslandoraUtils $utils) { + $this->utils = $utils; + } + + /** + * Setter for the media source service. + */ + public function setMediaSourceService(MediaSourceService $media_source) { + $this->mediaSource = $media_source; + } + + /** + * Setter for the token service. + */ + public function setTokenService(TokenInterface $token) { + $this->token = $token; + } + } diff --git a/src/Plugin/Action/EmitFileEvent.php b/src/Plugin/Action/EmitFileEvent.php index f8f4be2ee..0c1f70205 100644 --- a/src/Plugin/Action/EmitFileEvent.php +++ b/src/Plugin/Action/EmitFileEvent.php @@ -2,15 +2,11 @@ namespace Drupal\islandora\Plugin\Action; +use Drupal\islandora\EventGenerator\EmitEvent; + use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileSystemInterface; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; -use Drupal\jwt\Authentication\Provider\JwtAuth; -use Drupal\islandora\EventGenerator\EmitEvent; -use Drupal\islandora\EventGenerator\EventGeneratorInterface; -use Stomp\StatefulStomp; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -32,48 +28,9 @@ class EmitFileEvent extends EmitEvent { protected $fileSystem; /** - * Constructs a EmitEvent action. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\Session\AccountInterface $account - * Current user. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * Entity type manager. - * @param \Drupal\islandora\EventGenerator\EventGeneratorInterface $event_generator - * EventGenerator service to serialize AS2 events. - * @param \Stomp\StatefulStomp $stomp - * Stomp client. - * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth - * JWT Auth client. - * @param \Drupal\Core\File\FileSystemInterface $file_system - * File system service. + * Setter for the file system service. */ - public function __construct( - array $configuration, - $plugin_id, - $plugin_definition, - AccountInterface $account, - EntityTypeManagerInterface $entity_type_manager, - EventGeneratorInterface $event_generator, - StatefulStomp $stomp, - JwtAuth $auth, - FileSystemInterface $file_system - ) { - parent::__construct( - $configuration, - $plugin_id, - $plugin_definition, - $account, - $entity_type_manager, - $event_generator, - $stomp, - $auth - ); + public function setFileSystemService(FileSystemInterface $file_system) { $this->fileSystem = $file_system; } @@ -81,17 +38,11 @@ public function __construct( * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('current_user'), - $container->get('entity_type.manager'), - $container->get('islandora.eventgenerator'), - $container->get('islandora.stomp'), - $container->get('jwt.authentication.jwt'), - $container->get('file_system') - ); + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + + $instance->setFileSystemService($container->get('file_system')); + + return $instance; } /** diff --git a/src/Plugin/Action/EmitMediaEvent.php b/src/Plugin/Action/EmitMediaEvent.php index c5ad550a8..294f9aead 100644 --- a/src/Plugin/Action/EmitMediaEvent.php +++ b/src/Plugin/Action/EmitMediaEvent.php @@ -3,13 +3,8 @@ namespace Drupal\islandora\Plugin\Action; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\jwt\Authentication\Provider\JwtAuth; use Drupal\islandora\EventGenerator\EmitEvent; -use Drupal\islandora\EventGenerator\EventGeneratorInterface; use Drupal\islandora\MediaSource\MediaSourceService; -use Stomp\StatefulStomp; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -30,67 +25,15 @@ class EmitMediaEvent extends EmitEvent { */ protected $mediaSource; - /** - * Constructs a EmitEvent action. - * - * @param array $configuration - * A configuration array containing information about the plugin instance. - * @param string $plugin_id - * The plugin_id for the plugin instance. - * @param mixed $plugin_definition - * The plugin implementation definition. - * @param \Drupal\Core\Session\AccountInterface $account - * Current user. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * Entity type manager. - * @param \Drupal\islandora\EventGenerator\EventGeneratorInterface $event_generator - * EventGenerator service to serialize AS2 events. - * @param \Stomp\StatefulStomp $stomp - * Stomp client. - * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth - * JWT Auth client. - * @param \Drupal\islandora\MediaSource\MediaSourceService $media_source - * Media source service. - */ - public function __construct( - array $configuration, - $plugin_id, - $plugin_definition, - AccountInterface $account, - EntityTypeManagerInterface $entity_type_manager, - EventGeneratorInterface $event_generator, - StatefulStomp $stomp, - JwtAuth $auth, - MediaSourceService $media_source - ) { - parent::__construct( - $configuration, - $plugin_id, - $plugin_definition, - $account, - $entity_type_manager, - $event_generator, - $stomp, - $auth - ); - $this->mediaSource = $media_source; - } - /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('current_user'), - $container->get('entity_type.manager'), - $container->get('islandora.eventgenerator'), - $container->get('islandora.stomp'), - $container->get('jwt.authentication.jwt'), - $container->get('islandora.media_source_service') - ); + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + + $instance->setMediaSourceService($container->get('islandora.media_source_service')); + + return $instance; } /** @@ -102,4 +45,11 @@ protected function generateData(EntityInterface $entity) { return $data; } + /** + * Setter for the media source service. + */ + public function setMediaSourceService(MediaSourceService $media_source) { + $this->mediaSource = $media_source; + } + }