diff --git a/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php index 879230cd9..52e0882a1 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php @@ -78,7 +78,7 @@ final readonly class AsyncUploadController if ($request->query->has('version')) { $filename = $request->query->get('version'); - $storedObjectVersion = $storedObject->getVersions()->findFirst(fn(int $index, StoredObjectVersion $version): bool => $version->getFilename() === $filename); + $storedObjectVersion = $storedObject->getVersions()->findFirst(fn (int $index, StoredObjectVersion $version): bool => $version->getFilename() === $filename); if (null === $storedObjectVersion) { // we are here in the case where the version is not stored into the database diff --git a/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php b/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php index e06d8d7cc..b5c7c3930 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php +++ b/src/Bundle/ChillDocStoreBundle/Form/DataTransformer/StoredObjectDataTransformer.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Form\DataTransformer; use Chill\DocStoreBundle\Entity\StoredObject; -use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Serializer\SerializerInterface; @@ -30,11 +29,7 @@ class StoredObjectDataTransformer implements DataTransformerInterface } if ($value instanceof StoredObject) { - return $this->serializer->serialize($value, 'json', [ - 'groups' => [ - StoredObjectNormalizer::ADD_DAV_EDIT_LINK_CONTEXT, - ], - ]); + return $this->serializer->serialize($value, 'json'); } throw new UnexpectedTypeException($value, StoredObject::class); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts index 1d21feacd..235b375ce 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts @@ -1,28 +1,44 @@ -import {DateTime} from "../../../ChillMainBundle/Resources/public/types"; +import {DateTime, User} from "../../../ChillMainBundle/Resources/public/types"; export type StoredObjectStatus = "empty"|"ready"|"failure"|"pending"; export interface StoredObject { id: number, - - /** - * filename of the object in the object storage - */ - filename: string, - creationDate: DateTime, - datas: object, - iv: number[], - keyInfos: object, title: string, - type: string, uuid: string, + prefix: string, status: StoredObjectStatus, + currentVersion: null|StoredObjectVersion, + totalVersions: number, + datas: object, + /** @deprecated */ + creationDate: DateTime, + createdAt: DateTime|null, + createdBy: User|null, + _permissions: { + canEdit: boolean, + canSee: boolean, + }, _links?: { dav_link?: { href: string expiration: number }, - } + }, +} + +export interface StoredObjectVersion { + /** + * filename of the object in the object storage + */ + filename: string, + version: number, + id: number, + iv: number[], + keyInfos: object, + type: string, + createdAt: DateTime|null, + createdBy: User|null, } export interface StoredObjectCreated { diff --git a/src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectNormalizer.php b/src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectNormalizer.php index ebd7d3564..dbd9d72b8 100644 --- a/src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectNormalizer.php +++ b/src/Bundle/ChillDocStoreBundle/Serializer/Normalizer/StoredObjectNormalizer.php @@ -16,6 +16,7 @@ use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum; use Chill\DocStoreBundle\Security\Guard\JWTDavTokenProviderInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -28,8 +29,16 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; final class StoredObjectNormalizer implements NormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; - public const ADD_DAV_SEE_LINK_CONTEXT = 'dav-see-link-context'; - public const ADD_DAV_EDIT_LINK_CONTEXT = 'dav-edit-link-context'; + + /** + * @deprecated + */ + public const string ADD_DAV_SEE_LINK_CONTEXT = 'dav-see-link-context'; + + /** + * @deprecated + */ + public const string ADD_DAV_EDIT_LINK_CONTEXT = 'dav-edit-link-context'; public function __construct( private readonly JWTDavTokenProviderInterface $JWTDavTokenProvider, @@ -41,17 +50,16 @@ final class StoredObjectNormalizer implements NormalizerInterface, NormalizerAwa { /** @var StoredObject $object */ $datas = [ - 'datas' => $object->getDatas(), - 'filename' => $object->getFilename(), 'id' => $object->getId(), - 'iv' => $object->getIv(), - 'keyInfos' => $object->getKeyInfos(), + 'datas' => $object->getDatas(), + 'prefix' => $object->getPrefix(), 'title' => $object->getTitle(), - 'type' => $object->getType(), - 'uuid' => $object->getUuid(), + 'uuid' => $object->getUuid()->toString(), 'status' => $object->getStatus(), 'createdAt' => $this->normalizer->normalize($object->getCreatedAt(), $format, $context), 'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context), + 'currentVersion' => $this->normalizer->normalize($object->getCurrentVersion(), $format, [...$context, [AbstractNormalizer::GROUPS => 'read']]), + 'totalVersions' => $object->getVersions()->count(), ]; // deprecated property @@ -60,6 +68,11 @@ final class StoredObjectNormalizer implements NormalizerInterface, NormalizerAwa $canSee = $this->security->isGranted(StoredObjectRoleEnum::SEE->value, $object); $canEdit = $this->security->isGranted(StoredObjectRoleEnum::EDIT->value, $object); + $datas['_permissions'] = [ + 'canEdit' => $canEdit, + 'canSee' => $canSee, + ]; + if ($canSee || $canEdit) { $accessToken = $this->JWTDavTokenProvider->createToken( $object, diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectNormalizerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectNormalizerTest.php new file mode 100644 index 000000000..a2cc75541 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectNormalizerTest.php @@ -0,0 +1,98 @@ +setTitle('test'); + $reflection = new \ReflectionClass(StoredObject::class); + $idProperty = $reflection->getProperty('id'); + $idProperty->setValue($storedObject, 1); + + $jwtProvider = $this->createMock(JWTDavTokenProviderInterface::class); + $jwtProvider->expects($this->once())->method('createToken')->withAnyParameters()->willReturn('token'); + $jwtProvider->expects($this->once())->method('getTokenExpiration')->with('token')->willReturn($d = new \DateTimeImmutable()); + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator->expects($this->once())->method('generate') + ->with( + 'chill_docstore_dav_document_get', + [ + 'uuid' => $storedObject->getUuid(), + 'access_token' => 'token', + ], + UrlGeneratorInterface::ABSOLUTE_URL, + ) + ->willReturn($davLink = 'http://localhost/dav/token'); + + $security = $this->createMock(Security::class); + $security->expects($this->exactly(2))->method('isGranted') + ->with( + $this->logicalOr(StoredObjectRoleEnum::EDIT->value, StoredObjectRoleEnum::SEE->value), + $storedObject + ) + ->willReturn(true); + + $globalNormalizer = $this->createMock(NormalizerInterface::class); + $globalNormalizer->expects($this->exactly(3))->method('normalize') + ->withAnyParameters() + ->willReturnCallback(function (?object $object, string $format, array $context) { + if (null === $object) { + return null; + } + + return ['sub' => 'sub']; + }); + + $normalizer = new StoredObjectNormalizer($jwtProvider, $urlGenerator, $security); + $normalizer->setNormalizer($globalNormalizer); + + $actual = $normalizer->normalize($storedObject, 'json'); + + self::assertArrayHasKey('id', $actual); + self::assertEquals(1, $actual['id']); + self::assertArrayHasKey('title', $actual); + self::assertEquals('test', $actual['title']); + self::assertArrayHasKey('uuid', $actual); + self::assertArrayHasKey('prefix', $actual); + self::assertArrayHaskey('status', $actual); + self::assertArrayHasKey('currentVersion', $actual); + self::assertEquals(null, $actual['currentVersion']); + self::assertArrayHasKey('totalVersions', $actual); + self::assertEquals(0, $actual['totalVersions']); + self::assertArrayHasKey('datas', $actual); + self::assertArrayHasKey('createdAt', $actual); + self::assertArrayHasKey('createdBy', $actual); + self::assertArrayHasKey('_permissions', $actual); + self::assertEqualsCanonicalizing(['canEdit' => true, 'canSee' => true], $actual['_permissions']); + self::assertArrayHaskey('_links', $actual); + self::assertArrayHasKey('dav_link', $actual['_links']); + self::assertEqualsCanonicalizing(['href' => $davLink, 'expiration' => $d->getTimestamp()], $actual['_links']['dav_link']); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectVersionNormalizerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectVersionNormalizerTest.php new file mode 100644 index 000000000..c6bd87686 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/StoredObjectVersionNormalizerTest.php @@ -0,0 +1,61 @@ +normalizer = self::getContainer()->get(NormalizerInterface::class); + } + + public function testNormalize(): void + { + $storedObject = new StoredObject(); + $version = $storedObject->registerVersion( + iv: [1, 2, 3, 4], + keyInfos: ['someKey' => 'someKey'], + type: 'text/text', + ); + $reflection = new \ReflectionClass($version); + $idProperty = $reflection->getProperty('id'); + $idProperty->setValue($version, 1); + + $actual = $this->normalizer->normalize($version, 'json', ['groups' => ['read']]); + + self::assertEqualsCanonicalizing( + [ + 'id' => 1, + 'version' => 0, + 'filename' => $version->getFilename(), + 'iv' => [1, 2, 3, 4], + 'keyInfos' => ['someKey' => 'someKey'], + 'type' => 'text/text', + 'createdAt' => null, + 'createdBy' => null, + ], + $actual + ); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php index 0243d55b5..c43dbd128 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; -use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Templating\Listing\FilterOrderHelper; use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; @@ -116,7 +115,7 @@ final class AccompanyingCourseWorkController extends AbstractController { $this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::UPDATE, $work); - $json = $this->serializer->normalize($work, 'json', ['groups' => ['read', StoredObjectNormalizer::ADD_DAV_EDIT_LINK_CONTEXT]]); + $json = $this->serializer->normalize($work, 'json', ['groups' => ['read']]); return $this->render('@ChillPerson/AccompanyingCourseWork/edit.html.twig', [ 'accompanyingCourse' => $work->getAccompanyingPeriod(),