From afcd377f9efad659f0dc81323e14c3fd46c8cfd2 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Wed, 8 Mar 2017 10:41:31 -0400 Subject: [PATCH] Add JWT Token Authentication (#38) * Add JWT Token Authentication This adds JWT token authentication to Islandora. We send these tokens along with broadcast messages, and can use them to authenticate with Fedora as well as call back into Islandora. * Updated the version for JWT in the composer file. * JwtEventSubscriber tests. * Fixing copy/pasta mistake in tests (#2) * Update JWT Structure to Eliminate Nesting No java JWT parsing libraries like when a JWT contains nested structures, even when that is allowed in the standard. This commit updates our code so we put the drupal data at the root level of the JWT claims, instead of in a subclaim. * Coding standards for new sniffs --- .github/PULL_REQUEST_TEMPLATE.md | 17 ++- .gitignore | 1 + composer.json | 3 +- ...lay.fedora_resource.rdf_source.default.yml | 1 - ...ntity_view_mode.fedora_resource.teaser.yml | 1 - ...resource.rdf_source.field_ldp_contains.yml | 1 - config/install/islandora.settings.yml | 1 - ...ng.fedora_resource_type.non_rdf_source.yml | 1 - ...apping.fedora_resource_type.rdf_source.yml | 1 - .../rest.resource.entity.fedora_resource.yml | 4 +- .../rules.reaction.broadcast_create_event.yml | 1 - .../rules.reaction.broadcast_delete_event.yml | 1 - .../rules.reaction.broadcast_update_event.yml | 1 - .../views.view.fedora_entities_reference.yml | 1 - config/schema/fedora_resource_type.schema.yml | 2 +- config/schema/islandora.schema.yml | 1 - islandora.info.yml | 2 +- islandora.links.action.yml | 1 - islandora.permissions.yml | 2 +- islandora.services.yml | 6 + .../FedoraResourceAddController.php | 4 +- src/Entity/FedoraResource.php | 20 --- src/EventSubscriber/JwtEventSubscriber.php | 140 +++++++++++++++++ src/FedoraResourceInterface.php | 10 +- src/Plugin/RulesAction/Broadcaster.php | 41 ++++- src/Plugin/Search/FedoraEntitySearch.php | 6 +- src/StompFactory.php | 2 +- tests/src/Kernel/BroadcasterTest.php | 6 +- tests/src/Kernel/IslandoraKernelTestBase.php | 2 + tests/src/Kernel/JwtEventSubscriberTest.php | 141 ++++++++++++++++++ 30 files changed, 361 insertions(+), 60 deletions(-) create mode 100644 src/EventSubscriber/JwtEventSubscriber.php create mode 100644 tests/src/Kernel/JwtEventSubscriberTest.php diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 637199fb..db09c375 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,20 +1,24 @@ **GitHub Issue**: (link) -* Other Relevant Links (Google Groups discussion, related pull requests, Release pull requests, etc.) +* Other Relevant Links (Google Groups discussion, related pull requests, + Release pull requests, etc.) # What does this Pull Request do? -A brief description of what the intended result of the PR will be and/or what problem it solves. +A brief description of what the intended result of the PR will be and/or what + problem it solves. # What's new? -A in-depth description of the changes made by this PR. Technical details and possible side effects. +A in-depth description of the changes made by this PR. Technical details and + possible side effects. * Changes x feature to such that y * Added x * Removed y * Does this change require documentation to be updated? * Does this change add any new dependencies? -* Does this change require any other modifications to be made to the repository (ie. Regeneration activity, etc.)? +* Does this change require any other modifications to be made to the repository + (ie. Regeneration activity, etc.)? * Could this change impact execution of existing code? # How should this be tested? @@ -26,7 +30,8 @@ A description of what steps someone could take to: * Good testing instructions help get your PR completed faster. # Additional Notes: -Any additional information that you think would be helpful when reviewing this PR. +Any additional information that you think would be helpful when reviewing this + PR. # Interested parties -Tag (@ mention) interested parties or, if unsure, @Islandora-CLAW/committers \ No newline at end of file +Tag (@ mention) interested parties or, if unsure, @Islandora-CLAW/committers diff --git a/.gitignore b/.gitignore index 22d0d82f..3572ca42 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor +/.idea/ diff --git a/composer.json b/composer.json index 19c5ef79..e0dec094 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "drupal/rules": "^3.0@alpha", "drupal/search_api": "^1.0@beta", "islandora/claw-jsonld": "dev-8.x-1.x", - "stomp-php/stomp-php": "4.*" + "stomp-php/stomp-php": "4.*", + "drupal/jwt": "1.0.0-alpha6" }, "require-dev": { "phpunit/phpunit": "^4.8", diff --git a/config/install/core.entity_form_display.fedora_resource.rdf_source.default.yml b/config/install/core.entity_form_display.fedora_resource.rdf_source.default.yml index 802d45ae..5fc2de42 100644 --- a/config/install/core.entity_form_display.fedora_resource.rdf_source.default.yml +++ b/config/install/core.entity_form_display.fedora_resource.rdf_source.default.yml @@ -64,4 +64,3 @@ content: placeholder: '' third_party_settings: { } hidden: { } - diff --git a/config/install/core.entity_view_mode.fedora_resource.teaser.yml b/config/install/core.entity_view_mode.fedora_resource.teaser.yml index d76f3f15..be1fa1a3 100644 --- a/config/install/core.entity_view_mode.fedora_resource.teaser.yml +++ b/config/install/core.entity_view_mode.fedora_resource.teaser.yml @@ -11,4 +11,3 @@ id: fedora_resource.teaser label: Teaser targetEntityType: fedora_resource cache: true - diff --git a/config/install/field.field.fedora_resource.rdf_source.field_ldp_contains.yml b/config/install/field.field.fedora_resource.rdf_source.field_ldp_contains.yml index 9810847c..1e2ece6d 100644 --- a/config/install/field.field.fedora_resource.rdf_source.field_ldp_contains.yml +++ b/config/install/field.field.fedora_resource.rdf_source.field_ldp_contains.yml @@ -28,4 +28,3 @@ settings: display_name: entity_reference_1 arguments: { } field_type: entity_reference - diff --git a/config/install/islandora.settings.yml b/config/install/islandora.settings.yml index 7c052397..60a09ba9 100644 --- a/config/install/islandora.settings.yml +++ b/config/install/islandora.settings.yml @@ -3,4 +3,3 @@ fedora_rest_endpoint: 'http://localhost:8080/fcrepo/rest' _core: default_config_hash: nDZDR2rrpXXQ-D_7BYrdDFAXYOsB5hgH6vCAMV5I3w8 broadcast_queue: islandora-connector-broadcast - diff --git a/config/install/rdf.mapping.fedora_resource_type.non_rdf_source.yml b/config/install/rdf.mapping.fedora_resource_type.non_rdf_source.yml index 0752fc35..521af677 100644 --- a/config/install/rdf.mapping.fedora_resource_type.non_rdf_source.yml +++ b/config/install/rdf.mapping.fedora_resource_type.non_rdf_source.yml @@ -45,4 +45,3 @@ fieldMappings: properties: - 'ldp:contains' mapping_type: rel - diff --git a/config/install/rdf.mapping.fedora_resource_type.rdf_source.yml b/config/install/rdf.mapping.fedora_resource_type.rdf_source.yml index 127b2e3a..071b3787 100644 --- a/config/install/rdf.mapping.fedora_resource_type.rdf_source.yml +++ b/config/install/rdf.mapping.fedora_resource_type.rdf_source.yml @@ -49,4 +49,3 @@ fieldMappings: properties: - 'ldp:contains' mapping_type: rel - diff --git a/config/install/rest.resource.entity.fedora_resource.yml b/config/install/rest.resource.entity.fedora_resource.yml index d4916551..15989cac 100644 --- a/config/install/rest.resource.entity.fedora_resource.yml +++ b/config/install/rest.resource.entity.fedora_resource.yml @@ -21,6 +21,4 @@ configuration: - json - xml supported_auth: - - basic_auth - - cookie - + - jwt_auth diff --git a/config/install/rules.reaction.broadcast_create_event.yml b/config/install/rules.reaction.broadcast_create_event.yml index 95e35c54..e2a097ce 100644 --- a/config/install/rules.reaction.broadcast_create_event.yml +++ b/config/install/rules.reaction.broadcast_create_event.yml @@ -54,4 +54,3 @@ expression: rules_tokens: { } provides_mapping: { } action_id: islandora_broadcast - diff --git a/config/install/rules.reaction.broadcast_delete_event.yml b/config/install/rules.reaction.broadcast_delete_event.yml index cb470615..f3a830ef 100644 --- a/config/install/rules.reaction.broadcast_delete_event.yml +++ b/config/install/rules.reaction.broadcast_delete_event.yml @@ -54,4 +54,3 @@ expression: rules_tokens: { } provides_mapping: { } action_id: islandora_broadcast - diff --git a/config/install/rules.reaction.broadcast_update_event.yml b/config/install/rules.reaction.broadcast_update_event.yml index deedc0cb..8db4f929 100644 --- a/config/install/rules.reaction.broadcast_update_event.yml +++ b/config/install/rules.reaction.broadcast_update_event.yml @@ -54,4 +54,3 @@ expression: rules_tokens: { } provides_mapping: { } action_id: islandora_broadcast - diff --git a/config/install/views.view.fedora_entities_reference.yml b/config/install/views.view.fedora_entities_reference.yml index b3aa6a67..b34cf8e0 100644 --- a/config/install/views.view.fedora_entities_reference.yml +++ b/config/install/views.view.fedora_entities_reference.yml @@ -167,4 +167,3 @@ display: - 'languages:language_interface' - user.permissions tags: { } - diff --git a/config/schema/fedora_resource_type.schema.yml b/config/schema/fedora_resource_type.schema.yml index 62d75a09..cde84179 100644 --- a/config/schema/fedora_resource_type.schema.yml +++ b/config/schema/fedora_resource_type.schema.yml @@ -21,4 +21,4 @@ search.plugin.node_search: label: 'Content ranking' sequence: type: integer - label: 'Influence' \ No newline at end of file + label: 'Influence' diff --git a/config/schema/islandora.schema.yml b/config/schema/islandora.schema.yml index 8756286d..6c88ccbc 100644 --- a/config/schema/islandora.schema.yml +++ b/config/schema/islandora.schema.yml @@ -11,4 +11,3 @@ islandora.settings: broadcast_queue: type: string label: 'Queue that handles distributing messages amongst multiple recipients' - diff --git a/islandora.info.yml b/islandora.info.yml index ed5c110d..20f75325 100644 --- a/islandora.info.yml +++ b/islandora.info.yml @@ -15,4 +15,4 @@ dependencies: - jsonld - rules - search_api -version: '8.x-1.x-dev' + - jwt diff --git a/islandora.links.action.yml b/islandora.links.action.yml index 2666c978..2aa6bf43 100644 --- a/islandora.links.action.yml +++ b/islandora.links.action.yml @@ -8,4 +8,3 @@ entity.fedora_resource_type.add_form: title: 'Add Fedora resource type' appears_on: - entity.fedora_resource_type.collection - diff --git a/islandora.permissions.yml b/islandora.permissions.yml index d5b5fb4e..e1fc0a80 100644 --- a/islandora.permissions.yml +++ b/islandora.permissions.yml @@ -23,4 +23,4 @@ view unpublished fedora resource entities: administer fedora resource type entities: title: 'Administer Fedora Resource Type entities' description: 'Allow to access administration of Fedora Resource Type entities (bundles).' - restrict access: true \ No newline at end of file + restrict access: true diff --git a/islandora.services.yml b/islandora.services.yml index e17fb7c4..d078fe08 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -12,3 +12,9 @@ services: class: Stomp\StatefulStomp factory: ['Drupal\islandora\StompFactory', create] arguments: ['@config.factory'] + islandora.jwt-subscriber: + class: Drupal\islandora\EventSubscriber\JwtEventSubscriber + factory: ['Drupal\islandora\EventSubscriber\JwtEventSubscriber', create] + arguments: ['@entity_type.manager', '@current_user'] + tags: + - { name: event_subscriber } diff --git a/src/Controller/FedoraResourceAddController.php b/src/Controller/FedoraResourceAddController.php index 738424ff..9b33634a 100644 --- a/src/Controller/FedoraResourceAddController.php +++ b/src/Controller/FedoraResourceAddController.php @@ -86,7 +86,7 @@ class FedoraResourceAddController extends ControllerBase { /** * Presents the creation form for fedora_resource entities of given type. * - * @param EntityInterface $fedora_resource_type + * @param \Drupal\Core\Entity\EntityInterface $fedora_resource_type * The custom bundle to add. * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. @@ -104,7 +104,7 @@ class FedoraResourceAddController extends ControllerBase { /** * Provides the page title for this controller. * - * @param EntityInterface $fedora_resource_type + * @param \Drupal\Core\Entity\EntityInterface $fedora_resource_type * The custom bundle/type being added. * * @return string diff --git a/src/Entity/FedoraResource.php b/src/Entity/FedoraResource.php index c2ed215b..f31e16d0 100644 --- a/src/Entity/FedoraResource.php +++ b/src/Entity/FedoraResource.php @@ -69,26 +69,6 @@ class FedoraResource extends ContentEntityBase implements FedoraResourceInterfac use EntityChangedTrait; - /** - * Override this to have a rel ='uuid' for islandora entities. - * - * Stolen (but not inherited from EntityInterface) - * - * @param string $rel - * The link relationship type, for example: canonical or edit-form. - * @param array $options - * See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for - * the available options. - * - * @return \Drupal\Core\Url - * The URL object. - */ - public function toUrl($rel = 'canonical', array $options = []) { - // TODO: I Will override this to have a rel ='uuid' for islandora entities - // TODO: Change the autogenerated stub. - return parent::toUrl($rel, $options); - } - /** * Gets an array of placeholders for Fedora Resource Entity. * diff --git a/src/EventSubscriber/JwtEventSubscriber.php b/src/EventSubscriber/JwtEventSubscriber.php new file mode 100644 index 00000000..d32d4834 --- /dev/null +++ b/src/EventSubscriber/JwtEventSubscriber.php @@ -0,0 +1,140 @@ +userStorage = $userStorage; + $this->currentUser = $user; + } + + /** + * Factory. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityManager + * Entity manager to get user storage. + * @param \Drupal\Core\Session\AccountInterface $user + * The current user. + */ + public static function create( + EntityTypeManagerInterface $entityManager, + AccountInterface $user + ) { + return new static( + $entityManager->getStorage('user'), + $user + ); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[JwtAuthEvents::VALIDATE][] = ['validate']; + $events[JwtAuthEvents::VALID][] = ['loadUser']; + $events[JwtAuthEvents::GENERATE][] = ['setIslandoraClaims']; + + return $events; + } + + /** + * Sets claims for a Islandora consumer on the JWT. + * + * @param \Drupal\jwt\Authentication\Event\JwtAuthGenerateEvent $event + * The event. + */ + public function setIslandoraClaims(JwtAuthGenerateEvent $event) { + global $base_secure_url; + + // Standard claims, validated at JWT validation time. + $event->addClaim('iat', time()); + $event->addClaim('exp', strtotime('+2 hour')); + + // Islandora claims we need to validate. + $event->addClaim('uid', $this->currentUser->id()); + $event->addClaim('name', $this->currentUser->getAccountName()); + $event->addClaim('roles', $this->currentUser->getRoles(FALSE)); + $event->addClaim('url', $base_secure_url); + } + + /** + * Validates that the Islandora data is present in the JWT. + * + * @param \Drupal\jwt\Authentication\Event\JwtAuthValidateEvent $event + * A JwtAuth event. + */ + public function validate(JwtAuthValidateEvent $event) { + $token = $event->getToken(); + + $uid = $token->getClaim('uid'); + $name = $token->getClaim('name'); + $roles = $token->getClaim('roles'); + $url = $token->getClaim('url'); + if ($uid === NULL || $name === NULL || $roles === NULL || $url === NULL) { + $event->invalidate("Expected data missing from payload."); + return; + } + + $user = $this->userStorage->load($uid); + if ($user === NULL) { + $event->invalidate("Specified UID does not exist."); + } + elseif ($user->getAccountName() !== $name) { + $event->invalidate("Account name does not match."); + } + } + + /** + * Load and set a Drupal user to be authentication based on the JWT's uid. + * + * @param \Drupal\jwt\Authentication\Event\JwtAuthValidEvent $event + * A JwtAuth event. + */ + public function loadUser(JwtAuthValidEvent $event) { + $token = $event->getToken(); + $uid = $token->getClaim('uid'); + $user = $this->userStorage->load($uid); + $event->setUser($user); + } + +} diff --git a/src/FedoraResourceInterface.php b/src/FedoraResourceInterface.php index 806504d7..59e871d3 100644 --- a/src/FedoraResourceInterface.php +++ b/src/FedoraResourceInterface.php @@ -86,7 +86,7 @@ interface FedoraResourceInterface extends ContentEntityInterface, EntityChangedI * Does the entity have a parent entity? * * @return bool - * Whether a parent entity was set. + * Whether a parent entity was set. */ public function hasParent(); @@ -94,7 +94,7 @@ interface FedoraResourceInterface extends ContentEntityInterface, EntityChangedI * Gets the id of the parent entity. * * @return int - * The id of the parent Fedora resource entity. + * The id of the parent Fedora resource entity. */ public function getParentId(); @@ -102,7 +102,7 @@ interface FedoraResourceInterface extends ContentEntityInterface, EntityChangedI * Get the parent entity. * * @return \Drupal\islandora\FedoraResourceInterface - * The actual entity of the parent Fedora resource. + * The actual entity of the parent Fedora resource. */ public function getParent(); @@ -110,10 +110,10 @@ interface FedoraResourceInterface extends ContentEntityInterface, EntityChangedI * Get the parent entity. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity - * The parent entity. + * The parent entity. * * @return \Drupal\islandora\FedoraResourceInterface - * The called Fedora resource entity. + * The called Fedora resource entity. */ public function setParent(EntityTypeInterface $entity); diff --git a/src/Plugin/RulesAction/Broadcaster.php b/src/Plugin/RulesAction/Broadcaster.php index 05140f29..09abcd48 100644 --- a/src/Plugin/RulesAction/Broadcaster.php +++ b/src/Plugin/RulesAction/Broadcaster.php @@ -5,6 +5,7 @@ namespace Drupal\islandora\Plugin\RulesAction; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\islandora\Form\IslandoraSettingsForm; use Drupal\rules\Core\RulesActionBase; +use Drupal\jwt\Authentication\Provider\JwtAuth; use Stomp\Exception\StompException; use Stomp\StatefulStomp; use Stomp\Transport\Message; @@ -45,6 +46,13 @@ class Broadcaster extends RulesActionBase implements ContainerFactoryPluginInter */ protected $broadcastQueue; + /** + * The JWT Auth Service. + * + * @var \Drupal\jwt\Authentication\Provider\JwtAuth + */ + protected $auth; + /** * Constructs a BroadcastAction. * @@ -58,12 +66,22 @@ class Broadcaster extends RulesActionBase implements ContainerFactoryPluginInter * Name of queue that will handle distributing the broadcast. * @param \Stomp\StatefulStomp $stomp * Stomp client. + * @param \Drupal\jwt\Authentication\Provider\JwtAuth $auth + * JWT Auth client. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, $broadcast_queue, StatefulStomp $stomp) { + public function __construct( + array $configuration, + $plugin_id, + $plugin_definition, + $broadcast_queue, + StatefulStomp $stomp, + JwtAuth $auth + ) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->broadcastQueue = $broadcast_queue; $this->stomp = $stomp; + $this->auth = $auth; } /** @@ -79,7 +97,8 @@ class Broadcaster extends RulesActionBase implements ContainerFactoryPluginInter $plugin_id, $plugin_definition, $broadcastQueue, - $container->get('islandora.stomp') + $container->get('islandora.stomp'), + $container->get('jwt.authentication.jwt') ); } @@ -95,9 +114,25 @@ class Broadcaster extends RulesActionBase implements ContainerFactoryPluginInter // Transform recipients array into comma searated list. $recipients = array_map('trim', $recipients); $recipients = implode(',', $recipients); + $headers = ['IslandoraBroadcastRecipients' => $recipients]; + + // Include a token for later authentication in the message. + $token = $this->auth->generateToken(); + if ($token !== NULL) { + $headers['Authorization'] = "Bearer $token"; + } + else { + // JWT isn't properly configured. Log and notify user. + \Drupal::logger('islandora')->error( + 'Error getting JWT token for message: @msg', ['@msg' => $e->getMessage()] + ); + drupal_set_message( + t('Error getting JWT token for message. Check JWT Configuration.'), 'error' + ); + } // Transform message from string into a proper message object. - $message = new Message($message, ['IslandoraBroadcastRecipients' => $recipients]); + $message = new Message($message, $headers); // Send the message. try { diff --git a/src/Plugin/Search/FedoraEntitySearch.php b/src/Plugin/Search/FedoraEntitySearch.php index 6bec2546..e1905ef9 100644 --- a/src/Plugin/Search/FedoraEntitySearch.php +++ b/src/Plugin/Search/FedoraEntitySearch.php @@ -155,7 +155,7 @@ class FedoraResourceSearch extends ConfigurableSearchPluginBase implements Acces /** * {@inheritdoc} */ - static public function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, @@ -189,6 +189,8 @@ class FedoraResourceSearch extends ConfigurableSearchPluginBase implements Acces * A config object for 'search.settings'. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * The language manager. + * @param \Drupal\Core\Render\RendererInterface $renderer + * Renderer. * @param \Drupal\Core\Session\AccountInterface $account * The $account object to use for checking for access to advanced search. */ @@ -458,7 +460,7 @@ class FedoraResourceSearch extends ConfigurableSearchPluginBase implements Acces /** * Adds the configured rankings to the search query. * - * @param SelectExtender $query + * @param \Drupal\Core\Database\Query\SelectExtender $query * A query object that has been extended with the Search DB Extender. */ protected function addFedoraResourceRankings(SelectExtender $query) { diff --git a/src/StompFactory.php b/src/StompFactory.php index 53c01a60..3f170182 100644 --- a/src/StompFactory.php +++ b/src/StompFactory.php @@ -21,7 +21,7 @@ class StompFactory { * @return \Stomp\StatefulStomp * Stomp client. */ - static public function create(ConfigFactoryInterface $config) { + public static function create(ConfigFactoryInterface $config) { // Get broker url from config. $settings = $config->get(IslandoraSettingsForm::CONFIG_NAME); $brokerUrl = $settings->get(IslandoraSettingsForm::BROKER_URL); diff --git a/tests/src/Kernel/BroadcasterTest.php b/tests/src/Kernel/BroadcasterTest.php index 81bfd330..02b023ab 100644 --- a/tests/src/Kernel/BroadcasterTest.php +++ b/tests/src/Kernel/BroadcasterTest.php @@ -97,7 +97,7 @@ class BroadcasterTest extends IslandoraKernelTestBase { /** * Utility function to create a broadcaster action from a Stomp prophecy. * - * @param StatefulStomp $stomp + * @param \Stomp\StatefulStomp $stomp * Stomp instance or prophecy. * * @return \Drupal\islandora\Plugin\RulesAction\Broadcaster @@ -106,6 +106,7 @@ class BroadcasterTest extends IslandoraKernelTestBase { protected function createBroadcaster(StatefulStomp $stomp) { // Pull the plugin definition out of the plugin system. $actionManager = $this->container->get('plugin.manager.rules_action'); + $jwt = $this->container->get('jwt.authentication.jwt'); $definitions = $actionManager->getDefinitions(); $pluginDefinition = $definitions['islandora_broadcast']; @@ -114,7 +115,8 @@ class BroadcasterTest extends IslandoraKernelTestBase { 'islandora_broadcast', $pluginDefinition, $this->testQueue, - $stomp + $stomp, + $jwt ); // Set the required contexts for the action to run. diff --git a/tests/src/Kernel/IslandoraKernelTestBase.php b/tests/src/Kernel/IslandoraKernelTestBase.php index bff7d6d9..a3884941 100644 --- a/tests/src/Kernel/IslandoraKernelTestBase.php +++ b/tests/src/Kernel/IslandoraKernelTestBase.php @@ -30,6 +30,8 @@ abstract class IslandoraKernelTestBase extends KernelTestBase { 'rules', 'jsonld', 'views', + 'key', + 'jwt', 'islandora', ]; diff --git a/tests/src/Kernel/JwtEventSubscriberTest.php b/tests/src/Kernel/JwtEventSubscriberTest.php new file mode 100644 index 00000000..449f84bc --- /dev/null +++ b/tests/src/Kernel/JwtEventSubscriberTest.php @@ -0,0 +1,141 @@ +user = $this->createUser(); + } + + /** + * @covers \Drupal\islandora\EventSubscriber\JwtEventSubscriber::setIslandoraClaims + */ + public function testGeneratesValidToken() { + $entity_storage = $this->container->get('entity_type.manager')->getStorage('user'); + $subscriber = new JwtEventSubscriber($entity_storage, $this->user); + + // Generate a new token. + $jwt = new JsonWebToken(); + $event = new JwtAuthGenerateEvent($jwt); + $subscriber->setIslandoraClaims($event); + + // Validate it. + $validateEvent = new JwtAuthValidateEvent($jwt); + $subscriber->validate($validateEvent); + + $this->assert($validateEvent->isValid(), "Generated tokens must be valid."); + } + + /** + * @covers \Drupal\islandora\EventSubscriber\JwtEventSubscriber::validate + */ + public function testInvalidatesMalformedToken() { + $entity_storage = $this->container->get('entity_type.manager')->getStorage('user'); + $subscriber = new JwtEventSubscriber($entity_storage, $this->user); + + // Create a new event with mock jwt that returns null for all functions. + $prophecy = $this->prophesize(JsonWebTokenInterface::class); + $jwt = $prophecy->reveal(); + $event = new JwtAuthValidateEvent($jwt); + + $subscriber->validate($event); + + assert(!$event->isValid(), "Malformed event must be invalidated"); + } + + /** + * @covers \Drupal\islandora\EventSubscriber\JwtEventSubscriber::validate + */ + public function testInvalidatesBadUid() { + // Mock user entity storage, returns null when loading user. + $prophecy = $this->prophesize(EntityStorageInterface::class); + $entity_storage = $prophecy->reveal(); + + $subscriber = new JwtEventSubscriber($entity_storage, $this->user); + + // Generate a new token. + $jwt = new JsonWebToken(); + $event = new JwtAuthGenerateEvent($jwt); + $subscriber->setIslandoraClaims($event); + + // Validate it. + $validateEvent = new JwtAuthValidateEvent($jwt); + $subscriber->validate($validateEvent); + + assert(!$validateEvent->isValid(), "Event must be invalidated when user cannot be loaded."); + } + + /** + * @covers \Drupal\islandora\EventSubscriber\JwtEventSubscriber::validate + */ + public function testInvliadatesBadAccount() { + $anotherUser = $this->createUser(); + + // Mock user entity storage, loads the wrong user. + $prophecy = $this->prophesize(EntityStorageInterface::class); + $prophecy->load($this->user->id())->willReturn($anotherUser); + $entity_storage = $prophecy->reveal(); + + $subscriber = new JwtEventSubscriber($entity_storage, $this->user); + + // Generate a new token. + $jwt = new JsonWebToken(); + $event = new JwtAuthGenerateEvent($jwt); + $subscriber->setIslandoraClaims($event); + + // Validate it. + $validateEvent = new JwtAuthValidateEvent($jwt); + $subscriber->validate($validateEvent); + + assert(!$validateEvent->isValid(), "Event must be invalidated when users don't align."); + } + + /** + * @covers \Drupal\islandora\EventSubscriber\JwtEventSubscriber::loadUser + */ + public function testLoadsUser() { + $entity_storage = $this->container->get('entity_type.manager')->getStorage('user'); + $subscriber = new JwtEventSubscriber($entity_storage, $this->user); + + // Generate a new token. + $jwt = new JsonWebToken(); + $event = new JwtAuthGenerateEvent($jwt); + $subscriber->setIslandoraClaims($event); + + $validEvent = new JwtAuthValidEvent($jwt); + $subscriber->loadUser($validEvent); + + $this->assert($validEvent->getUser()->id() == $this->user->id(), "Correct user must be loaded to valid event."); + } + +}