Feature: [wopi] Implements the new required AuthorizationManager and UserManager for wopi

This commit is contained in:
Julien Fastré 2023-01-10 20:26:44 +01:00
parent a34c102c4b
commit 34296e7841
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
6 changed files with 172 additions and 81 deletions

View File

@ -15,7 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use RuntimeException; use RuntimeException;
use Symfony\Component\Security\Core\User\AdvancedUserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
@ -31,7 +31,7 @@ use function in_array;
* "user": User::class * "user": User::class
* }) * })
*/ */
class User implements AdvancedUserInterface class User implements UserInterface
{ {
/** /**
* @ORM\Id * @ORM\Id
@ -58,8 +58,6 @@ class User implements AdvancedUserInterface
private ?Location $currentLocation = null; private ?Location $currentLocation = null;
/** /**
* @var string
*
* @ORM\Column(type="string", length=150, nullable=true) * @ORM\Column(type="string", length=150, nullable=true)
*/ */
private ?string $email = null; private ?string $email = null;
@ -216,7 +214,7 @@ class User implements AdvancedUserInterface
} }
/** /**
* @return GroupCenter * @return Collection<GroupCenter>
*/ */
public function getGroupCenters() public function getGroupCenters()
{ {
@ -225,10 +223,8 @@ class User implements AdvancedUserInterface
/** /**
* Get id. * Get id.
*
* @return int
*/ */
public function getId() public function getId(): int
{ {
return $this->id; return $this->id;
} }
@ -487,7 +483,7 @@ class User implements AdvancedUserInterface
* *
* @param string $name * @param string $name
* *
* @return Agent * @return User
*/ */
public function setUsername($name) public function setUsername($name)
{ {

View File

@ -12,12 +12,15 @@ declare(strict_types=1);
namespace Symfony\Component\DependencyInjection\Loader\Configurator; namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface;
use ChampsLibres\WopiBundle\Contracts\UserManagerInterface;
use ChampsLibres\WopiBundle\Service\Wopi as CLWopi; use ChampsLibres\WopiBundle\Service\Wopi as CLWopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\WopiBundle\Service\Wopi\AuthorizationManager;
use Chill\WopiBundle\Service\Wopi\ChillDocumentLockManager; use Chill\WopiBundle\Service\Wopi\ChillDocumentLockManager;
use Chill\WopiBundle\Service\Wopi\ChillDocumentManager; use Chill\WopiBundle\Service\Wopi\ChillDocumentManager;
use Chill\WopiBundle\Service\Wopi\ChillWopi; use Chill\WopiBundle\Service\Wopi\ChillWopi;
use Chill\WopiBundle\Service\Wopi\UserManager;
return static function (ContainerConfigurator $container) { return static function (ContainerConfigurator $container) {
$services = $container $services = $container
@ -44,8 +47,17 @@ return static function (ContainerConfigurator $container) {
->alias(DocumentManagerInterface::class, ChillDocumentManager::class); ->alias(DocumentManagerInterface::class, ChillDocumentManager::class);
$services $services
->set(ChillDocumentLockManager::class) ->set(ChillDocumentLockManager::class);
->decorate(DocumentLockManagerInterface::class);
$services
->set(AuthorizationManager::class);
$services->alias(AuthorizationManagerInterface::class, AuthorizationManager::class);
$services
->set(UserManager::class);
$services->alias(UserManagerInterface::class, UserManager::class);
// TODO: Move this into the async bundle (low priority) // TODO: Move this into the async bundle (low priority)
$services $services

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\WopiLib\Contract\Entity\Document;
use Chill\MainBundle\Entity\User;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\Security\Core\Security;
class AuthorizationManager implements \ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface
{
private Security $security;
private JWTTokenManagerInterface $tokenManager;
public function __construct(JWTTokenManagerInterface $tokenManager, Security $security)
{
$this->tokenManager = $tokenManager;
$this->security = $security;
}
public function isRestrictedWebViewOnly(string $accessToken, Document $document, RequestInterface $request): bool
{
return false;
}
public function isTokenValid(string $accessToken, Document $document, RequestInterface $request): bool
{
$metadata = $this->tokenManager->parse($accessToken);
if (false === $metadata) {
return false;
}
$user = $this->security->getUser();
if (!$user instanceof User) {
return false;
}
return $document->getWopiDocId() === $metadata['fileId'];
}
public function userCanAttend(string $accessToken, Document $document, RequestInterface $request): bool
{
return $this->isTokenValid($accessToken, $document, $request);
}
public function userCanDelete(string $accessToken, Document $document, RequestInterface $request): bool
{
return false;
}
public function userCannotWriteRelative(string $accessToken, Document $document, RequestInterface $request): bool
{
return true;
}
public function userCanPresent(string $accessToken, Document $document, RequestInterface $request): bool
{
return $this->isTokenValid($accessToken, $document, $request);
}
public function userCanRead(string $accessToken, Document $document, RequestInterface $request): bool
{
return $this->isTokenValid($accessToken, $document, $request);
}
public function userCanRename(string $accessToken, Document $document, RequestInterface $request): bool
{
return false;
}
public function userCanWrite(string $accessToken, Document $document, RequestInterface $request): bool
{
return $this->isTokenValid($accessToken, $document, $request);
}
}

View File

@ -24,11 +24,12 @@ use loophp\psr17\Psr17Interface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface; use Psr\Http\Message\StreamInterface;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use RuntimeException;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use function strlen; use function strlen;
final class ChillDocumentManager implements DocumentManagerInterface final class ChillDocumentManager implements DocumentManagerInterface
@ -190,7 +191,12 @@ final class ChillDocumentManager implements DocumentManagerInterface
public function remove(Document $document): void public function remove(Document $document): void
{ {
// TODO: To implement when we have a clearer view and API. throw new RuntimeException('this is not implemented and should not happens');
}
public function rename(Document $document, string $requestedName): void
{
throw new RuntimeException('this is not implemented and should not happens');
} }
public function write(Document $document, array $properties = []): void public function write(Document $document, array $properties = []): void

View File

@ -13,43 +13,20 @@ namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface; use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
use Chill\MainBundle\Entity\User;
use DateTimeInterface;
use loophp\psr17\Psr17Interface; use loophp\psr17\Psr17Interface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface;
final class ChillWopi implements WopiInterface final class ChillWopi implements WopiInterface
{ {
private CacheItemPoolInterface $cache;
private DocumentManagerInterface $documentManager;
private Psr17Interface $psr17;
private Security $security;
private UserProviderInterface $userProvider;
private WopiInterface $wopi; private WopiInterface $wopi;
public function __construct( public function __construct(
CacheItemPoolInterface $cache,
DocumentManagerInterface $documentManager,
Psr17Interface $psr17,
Security $security,
UserProviderInterface $userProvider,
WopiInterface $wopi WopiInterface $wopi
) { ) {
$this->cache = $cache;
$this->documentManager = $documentManager;
$this->psr17 = $psr17;
$this->security = $security;
$this->userProvider = $userProvider;
$this->wopi = $wopi; $this->wopi = $wopi;
} }
@ -58,50 +35,9 @@ final class ChillWopi implements WopiInterface
?string $accessToken, ?string $accessToken,
RequestInterface $request RequestInterface $request
): ResponseInterface { ): ResponseInterface {
$user = $this->security->getUser(); return $this->wopi->checkFileInfo($fileId, $accessToken, $request, [
'SupportsRename' => false,
if (!$user instanceof User) { ]);
throw new AccessDeniedHttpException('User must be authenticated');
}
$document = $this->documentManager->findByDocumentId($fileId);
$userCacheKey = sprintf('wopi_putUserInfo_%s', $user->getId());
return $this
->psr17
->createResponse()
->withHeader('Content-Type', 'application/json')
->withBody($this->psr17->createStream((string) json_encode(
[
'BaseFileName' => $this->documentManager->getBasename($document),
'OwnerId' => 'Symfony',
'Size' => $this->documentManager->getSize($document),
'UserId' => $user->getId(),
'ReadOnly' => false,
'UserCanAttend' => true,
'UserCanPresent' => true,
'UserCanRename' => true,
'UserCanWrite' => true,
'UserCanNotWriteRelative' => false,
'SupportsUserInfo' => true,
'SupportsDeleteFile' => true,
'SupportsLocks' => true,
'SupportsGetLock' => true,
'SupportsExtendedLockLength' => true,
'UserFriendlyName' => $user->getLabel(),
'SupportsUpdate' => true,
'SupportsRename' => true,
'SupportsFolder' => false,
'DisablePrint' => false,
'AllowExternalMarketplace' => true,
'SupportedShareUrlTypes' => [
'ReadOnly',
],
'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)
->format(DateTimeInterface::ATOM),
'SHA256' => $this->documentManager->getSha256($document),
'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(),
]
)));
} }
public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\WopiBundle\Service\Wopi;
use Chill\MainBundle\Entity\User;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\Security\Core\Security;
class UserManager implements \ChampsLibres\WopiBundle\Contracts\UserManagerInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function getUserFriendlyName(string $accessToken, string $fileId, RequestInterface $request): ?string
{
$user = $this->security->getUser();
if (!$user instanceof User) {
return null;
}
return (string) $user->getLabel();
}
public function getUserId(string $accessToken, string $fileId, RequestInterface $request): ?string
{
$user = $this->security->getUser();
if (!$user instanceof User) {
return null;
}
return (string) $user->getId();
}
public function isAnonymousUser(string $accessToken, string $fileId, RequestInterface $request): bool
{
return false;
}
}