Feature: use JWT access token for securing wopi endpoints

This commit is contained in:
Julien Fastré 2023-01-09 20:55:25 +01:00
parent e542ebe531
commit c1c92dc296
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
2 changed files with 40 additions and 20 deletions

View File

@ -15,11 +15,13 @@ use ChampsLibres\WopiLib\Contract\Service\Configuration\ConfigurationInterface;
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\User;
use Chill\WopiBundle\Service\Controller\ResponderInterface;
use Exception;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use loophp\psr17\Psr17Interface;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
@ -33,6 +35,8 @@ final class Editor
{
private DocumentManagerInterface $documentManager;
private JWTTokenManagerInterface $JWTTokenManager;
private Psr17Interface $psr17;
private ResponderInterface $responder;
@ -49,12 +53,14 @@ final class Editor
ConfigurationInterface $wopiConfiguration,
DiscoveryInterface $wopiDiscovery,
DocumentManagerInterface $documentManager,
JWTTokenManagerInterface $JWTTokenManager,
ResponderInterface $responder,
Security $security,
Psr17Interface $psr17,
RouterInterface $router
) {
$this->documentManager = $documentManager;
$this->JWTTokenManager = $JWTTokenManager;
$this->wopiConfiguration = $wopiConfiguration;
$this->wopiDiscovery = $wopiDiscovery;
$this->responder = $responder;
@ -65,8 +71,12 @@ final class Editor
public function __invoke(string $fileId): Response
{
if (null === $user = $this->security->getUser()->getUsername()) {
throw new AccessDeniedException('You must be logged in to access to this resource.');
if (null === $user = $this->security->getUser()) {
throw new AccessDeniedHttpException('Please authenticate to access this feature');
}
if (!$user instanceof User) {
throw new AccessDeniedHttpException('Please authenticate as a user to access this feature');
}
$configuration = $this->wopiConfiguration->jsonSerialize();
@ -82,7 +92,19 @@ final class Editor
}
$configuration['favIconUrl'] = '';
$configuration['access_token'] = $user;
$configuration['access_token'] = $this->JWTTokenManager->createFromPayload($user, [
'UserCanWrite' => true,
'UserCanAttend' => true,
'UserCanPresent' => true,
'fileId' => $fileId,
]);
// we parse the token back to get the access_token_ttl
// reminder: access_token_ttl is a javascript epoch, not a number of seconds; it is the
// time when the token will expire, not the time to live:
// https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/concepts#the-access_token_ttl-property
$jwt = $this->JWTTokenManager->parse($configuration['access_token']);
$configuration['access_token_ttl'] = $jwt['exp'] * 1000;
$configuration['server'] = $this
->psr17

View File

@ -13,16 +13,15 @@ namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
use DateTimeImmutable;
use Chill\MainBundle\Entity\User;
use DateTimeInterface;
use loophp\psr17\Psr17Interface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use function strlen;
final class ChillWopi implements WopiInterface
{
@ -32,6 +31,8 @@ final class ChillWopi implements WopiInterface
private Psr17Interface $psr17;
private Security $security;
private UserProviderInterface $userProvider;
private WopiInterface $wopi;
@ -40,12 +41,14 @@ final class ChillWopi implements WopiInterface
CacheItemPoolInterface $cache,
DocumentManagerInterface $documentManager,
Psr17Interface $psr17,
Security $security,
UserProviderInterface $userProvider,
WopiInterface $wopi
) {
$this->cache = $cache;
$this->documentManager = $documentManager;
$this->psr17 = $psr17;
$this->security = $security;
$this->userProvider = $userProvider;
$this->wopi = $wopi;
}
@ -55,18 +58,13 @@ final class ChillWopi implements WopiInterface
?string $accessToken,
RequestInterface $request
): ResponseInterface {
try {
$user = $this->userProvider->loadUserByUsername($accessToken);
} catch (UsernameNotFoundException $e) {
return $this
->psr17
->createResponse(401);
}
$user = $this->security->getUser();
// @ TODO : Replace this with a call to decorated object once authentication is done.
if (!$user instanceof User) {
throw new AccessDeniedHttpException('User must be authenticated');
}
$document = $this->documentManager->findByDocumentId($fileId);
$userIdentifier = $user->getUsername();
$userCacheKey = sprintf('wopi_putUserInfo_%s', $userIdentifier);
$userCacheKey = sprintf('wopi_putUserInfo_%s', $user->getId());
return $this
->psr17
@ -77,7 +75,7 @@ final class ChillWopi implements WopiInterface
'BaseFileName' => $this->documentManager->getBasename($document),
'OwnerId' => 'Symfony',
'Size' => $this->documentManager->getSize($document),
'UserId' => $userIdentifier,
'UserId' => $user->getId(),
'ReadOnly' => false,
'UserCanAttend' => true,
'UserCanPresent' => true,
@ -89,7 +87,7 @@ final class ChillWopi implements WopiInterface
'SupportsLocks' => true,
'SupportsGetLock' => true,
'SupportsExtendedLockLength' => true,
'UserFriendlyName' => $userIdentifier,
'UserFriendlyName' => $user->getLabel(),
'SupportsUpdate' => true,
'SupportsRename' => true,
'SupportsFolder' => false,