Browse Source
* 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 sniffspull/756/head
Jonathan Green
8 years ago
committed by
Nick Ruest
30 changed files with 361 additions and 60 deletions
@ -0,0 +1,140 @@
|
||||
<?php |
||||
|
||||
namespace Drupal\islandora\EventSubscriber; |
||||
|
||||
use Drupal\jwt\Authentication\Event\JwtAuthValidateEvent; |
||||
use Drupal\jwt\Authentication\Event\JwtAuthValidEvent; |
||||
use Drupal\jwt\Authentication\Event\JwtAuthGenerateEvent; |
||||
use Drupal\jwt\Authentication\Event\JwtAuthEvents; |
||||
use Drupal\Core\Entity\EntityStorageInterface; |
||||
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||
use Drupal\Core\Session\AccountInterface; |
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
||||
|
||||
/** |
||||
* Class JwtEventSubscriber. |
||||
* |
||||
* @package Drupal\islandora\EventSubscriber |
||||
*/ |
||||
class JwtEventSubscriber implements EventSubscriberInterface { |
||||
|
||||
/** |
||||
* User storage to load users. |
||||
* |
||||
* @var \Drupal\Core\Entity\EntityStorageInterface |
||||
*/ |
||||
protected $userStorage; |
||||
|
||||
/** |
||||
* The current user. |
||||
* |
||||
* @var \Drupal\Core\Session\AccountInterface |
||||
*/ |
||||
protected $currentUser; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $userStorage |
||||
* User storage to load users. |
||||
* @param \Drupal\Core\Session\AccountInterface $user |
||||
* The current user. |
||||
*/ |
||||
public function __construct( |
||||
EntityStorageInterface $userStorage, |
||||
AccountInterface $user |
||||
) { |
||||
$this->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); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,141 @@
|
||||
<?php |
||||
|
||||
namespace Drupal\Tests\islandora\Kernel; |
||||
|
||||
use Drupal\jwt\Authentication\Event\JwtAuthGenerateEvent; |
||||
use Drupal\jwt\Authentication\Event\JwtAuthValidEvent; |
||||
use Drupal\jwt\Authentication\Event\JwtAuthValidateEvent; |
||||
use Drupal\jwt\JsonWebToken\JsonWebToken; |
||||
use Drupal\jwt\JsonWebToken\JsonWebTokenInterface; |
||||
use Drupal\simpletest\UserCreationTrait; |
||||
use Drupal\core\Entity\EntityStorageInterface; |
||||
use Drupal\islandora\EventSubscriber\JwtEventSubscriber; |
||||
|
||||
/** |
||||
* JwtEventSubscriber tests. |
||||
* |
||||
* @group islandora |
||||
* @coversDefaultClass \Drupal\islandora\EventSubscriber\JwtEventSubscriber |
||||
*/ |
||||
class JwtEventSubscriberTest extends IslandoraKernelTestBase { |
||||
|
||||
use UserCreationTrait; |
||||
|
||||
/** |
||||
* The current user. |
||||
* |
||||
* @var \Drupal\Core\Session\AccountInterface |
||||
*/ |
||||
protected $user; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function setUp() { |
||||
parent::setUp(); |
||||
|
||||
$this->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."); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue