From 999ac3af2bc1965a960a15f2b3fd331aa49dc765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 6 Jan 2025 14:37:35 +0100 Subject: [PATCH] Add TempUrl signature validation to local storage Implemented local storage-based file handling with TempUrl signature validation for upload and retrieval. Added validation checks for parameters like max file size/count, expiration, and signature integrity. Included unit tests for TempUrl signature validation and adjusted configuration for local storage. --- config/packages/chill_doc_store.yaml | 12 +- .../TempUrlLocalStorageGenerator.php | 38 +- ...dObjectContentToLocalStorageController.php | 61 +++- .../TempUrlLocalStorageGeneratorTest.php | 184 +++++++++- ...ectContentToLocalStorageControllerTest.php | 338 ++++++++++++++++++ .../dummy_file | 1 + 6 files changed, 609 insertions(+), 25 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Controller/data/StoredObjectContentToLocalStorageControllerTest/dummy_file diff --git a/config/packages/chill_doc_store.yaml b/config/packages/chill_doc_store.yaml index 994e19240..e6cfd10ec 100644 --- a/config/packages/chill_doc_store.yaml +++ b/config/packages/chill_doc_store.yaml @@ -1,6 +1,8 @@ chill_doc_store: - openstack: - temp_url: - temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required - container: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_CONTAINER)%' # Required - temp_url_base_path: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_BASE_PATH)%' # Required + local_storage: + storage_path: '%kernel.project_dir%/var/storage' +# openstack: +# temp_url: +# temp_url_key: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_KEY)%' # Required +# container: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_CONTAINER)%' # Required +# temp_url_base_path: '%env(resolve:ASYNC_UPLOAD_TEMP_URL_BASE_PATH)%' # Required diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGenerator.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGenerator.php index 957413b3a..40ee009c5 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGenerator.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGenerator.php @@ -33,11 +33,11 @@ class TempUrlLocalStorageGenerator implements TempUrlGeneratorInterface $expiration = $this->clock->now()->getTimestamp() + min($expire_delay ?? self::SIGNATURE_DURATION, self::SIGNATURE_DURATION); return new SignedUrl( - $method, + strtoupper($method), $this->urlGenerator->generate('chill_docstore_stored_object_operate', [ 'object_name' => $object_name, 'exp' => $expiration, - 'sig' => $this->sign($method, $object_name, $expiration), + 'sig' => $this->sign(strtoupper($method), $object_name, $expiration), ], UrlGeneratorInterface::ABSOLUTE_URL), \DateTimeImmutable::createFromFormat('U', (string) $expiration), $object_name, @@ -70,6 +70,38 @@ class TempUrlLocalStorageGenerator implements TempUrlGeneratorInterface private function sign(string $method, string $object_name, int $expiration): string { - return hash_hmac('sha512', $method, sprintf('%s.%s.%s.%d', $method, $this->secret, $object_name, $expiration)); + return hash('sha512', sprintf('%s.%s.%s.%d', $method, $this->secret, $object_name, $expiration)); + } + + public function validateSignaturePost(string $signature, string $prefix, int $expiration, int $maxFileSize, int $maxFileCount): bool + { + if (15_000_000 !== $maxFileSize || 1 !== $maxFileCount) { + return false; + } + + return $this->internalValidateSignature($signature, 'POST', $prefix, $expiration); + } + + private function internalValidateSignature(string $signature, string $method, string $object_name, int $expiration): bool + { + if ($expiration < $this->clock->now()->format('U')) { + return false; + } + + if ('' === $object_name) { + return false; + } + + + return $this->sign($method, $object_name, $expiration) === $signature; + } + + public function validateSignature(string $signature, string $method, string $objectName, int $expiration): bool + { + if (!in_array($method, ['GET', 'HEAD'], true)) { + return false; + } + + return $this->internalValidateSignature($signature, $method, $objectName, $expiration); } } diff --git a/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectContentToLocalStorageController.php b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectContentToLocalStorageController.php index b7a53514e..1d7fdf8b4 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectContentToLocalStorageController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/StoredObjectContentToLocalStorageController.php @@ -12,9 +12,11 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Controller; use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager; +use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Annotation\Route; @@ -26,6 +28,7 @@ final readonly class StoredObjectContentToLocalStorageController { public function __construct( private StoredObjectManager $storedObjectManager, + private TempUrlLocalStorageGenerator $tempUrlLocalStorageGenerator, ) {} #[Route('/public/stored-object/post', name: 'chill_docstore_storedobject_post', methods: ['POST'])] @@ -34,14 +37,51 @@ final readonly class StoredObjectContentToLocalStorageController $prefix = $request->query->get('prefix', ''); if ('' === $prefix) { - throw new BadRequestHttpException('prefix parameter is missing'); + throw new BadRequestHttpException('Prefix parameter is missing'); + } + + if (0 === $maxFileSize = $request->request->getInt('max_file_size', 0)) { + throw new BadRequestHttpException('Max file size is not set or equal to zero'); + } + + if (1 !== $maxFileCount = $request->request->getInt('max_file_count', 0)) { + throw new BadRequestHttpException('Max file count is not set or equal to zero'); + } + + if (0 === $expiration = $request->request->getInt('expires', 0)) { + throw new BadRequestHttpException('Expiration is not set or equal to zero'); + } + + if ('' === $signature = $request->request->get('signature', '')) { + throw new BadRequestHttpException('Signature is not set or is a blank string'); + } + + if (!$this->tempUrlLocalStorageGenerator->validateSignaturePost($signature, $prefix, $expiration, $maxFileSize, $maxFileCount)) { + throw new AccessDeniedHttpException('Invalid signature'); } $keyFiles = $request->files->keys(); + if ($maxFileCount < count($keyFiles)) { + throw new AccessDeniedHttpException('More files than max file count'); + } + + if (0 === count($keyFiles)) { + throw new BadRequestHttpException('Zero files given'); + } + foreach ($keyFiles as $keyFile) { /** @var UploadedFile $file */ $file = $request->files->get($keyFile); + + if ($maxFileSize < strlen($file->getContent())) { + throw new AccessDeniedHttpException('File is too big'); + } + + if (!str_starts_with((string) $keyFile, $prefix)) { + throw new AccessDeniedHttpException('Filename does not start with signed prefix'); + } + $this->storedObjectManager->writeContent($keyFile, $file->getContent()); } @@ -51,19 +91,30 @@ final readonly class StoredObjectContentToLocalStorageController #[Route('/public/stored-object/operate', name: 'chill_docstore_stored_object_operate', methods: ['GET', 'HEAD'])] public function contentOperate(Request $request): Response { - $objectName = $request->query->get('object_name', ''); + if ('' === $objectName = $request->query->get('object_name', '')) { + throw new BadRequestHttpException('Object name parameter is missing'); + } - if ('' === $objectName) { - throw new BadRequestHttpException('object name parameter is missing'); + if (0 === $expiration = $request->query->getInt('exp', 0)) { + throw new BadRequestHttpException('Expiration is not set or equal to zero'); + } + + if ('' === $signature = $request->query->get('sig', '')) { + throw new BadRequestHttpException('Signature is not set or is a blank string'); + } + + if (!$this->tempUrlLocalStorageGenerator->validateSignature($signature, strtoupper($request->getMethod()), $objectName, $expiration)) { + throw new AccessDeniedHttpException('Invalid signature'); } if (!$this->storedObjectManager->existsContent($objectName)) { - throw new NotFoundHttpException('object does not exists on disk'); + throw new NotFoundHttpException('Object does not exists on disk'); } return match ($request->getMethod()) { 'GET' => new Response($this->storedObjectManager->readContent($objectName)), 'HEAD' => new Response(''), + default => throw new BadRequestHttpException('method not supported'), }; } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php index f7170abcd..d85d3094a 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php @@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Tests\AsyncUpload\Driver\LocalStorage; use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Clock\MockClock; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -26,24 +27,26 @@ class TempUrlLocalStorageGeneratorTest extends TestCase { use ProphecyTrait; + private const SECRET = 'abc'; + public function testGenerate(): void { $urlGenerator = $this->prophesize(UrlGeneratorInterface::class); $urlGenerator->generate('chill_docstore_stored_object_operate', [ - 'object_name' => 'testABC', - 'exp' => $exp = 1734307200 + 180, - 'sig' => '528a426aebea86ada86035e9f3299788a074b77d3f926c6c15492457f81e88cfa98b270ba489d5d7588612eadaeeb7717a57887b33932d6d449b5f441252e600', + 'object_name' => $object_name = 'testABC', + 'exp' => $expiration = 1734307200 + 180, + 'sig' => TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name, $expiration), ], UrlGeneratorInterface::ABSOLUTE_URL) ->shouldBeCalled() ->willReturn($url = 'http://example.com/public/doc-store/stored-object/operate/testABC'); $generator = $this->buildGenerator($urlGenerator->reveal()); - $signedUrl = $generator->generate('GET', 'testABC'); + $signedUrl = $generator->generate('GET', $object_name); self::assertEquals($url, $signedUrl->url); - self::assertEquals('testABC', $signedUrl->object_name); - self::assertEquals($exp, $signedUrl->expires->getTimestamp()); + self::assertEquals($object_name, $signedUrl->object_name); + self::assertEquals($expiration, $signedUrl->expires->getTimestamp()); self::assertEquals('GET', $signedUrl->method); } @@ -62,17 +65,174 @@ class TempUrlLocalStorageGeneratorTest extends TestCase self::assertEquals($url, $signedUrl->url); self::assertEquals('prefixABC', $signedUrl->object_name); - self::assertEquals(1734307200 + 180 + 180, $signedUrl->expires->getTimestamp()); + self::assertEquals($expiration = 1734307200 + 180 + 180, $signedUrl->expires->getTimestamp()); self::assertEquals('POST', $signedUrl->method); - self::assertEquals('47e9271917c6c9149a47248d88eeadcbc1f804138e445f3406d39f6aa51b2d976c2ee6e5b1196d2bf88852b627b330596c2fbf6f31b06837e9f41cf56103a4e4', $signedUrl->signature); + self::assertEquals(TempUrlLocalStorageGeneratorTest::expectedSignature('POST', 'prefixABC', $expiration), $signedUrl->signature); } - private function buildGenerator(UrlGeneratorInterface $urlGenerator): TempUrlLocalStorageGenerator + private static function expectedSignature(string $method, $objectName, int $expiration): string + { + return hash('sha512', sprintf('%s.%s.%s.%d', $method, self::SECRET, $objectName, $expiration)); + } + + /** + * @dataProvider generateValidateSignatureData + */ + public function testValidateSignature(string $signature, string $method, string $objectName, int $expiration, \DateTimeImmutable $now, bool $expected, string $message): void + { + $urlGenerator = $this->buildGenerator(clock: new MockClock($now)); + + self::assertEquals($expected, $urlGenerator->validateSignature($signature, $method, $objectName, $expiration), $message); + } + + /** + * @dataProvider generateValidateSignaturePostData + */ + public function testValidateSignaturePost(string $signature, int $expiration, string $objectName, int $maxFileSize, int $maxFileCount, \DateTimeImmutable $now, bool $expected, string $message): void + { + $urlGenerator = $this->buildGenerator(clock: new MockClock($now)); + + self::assertEquals($expected, $urlGenerator->validateSignaturePost($signature, $objectName, $expiration, $maxFileSize, $maxFileCount), $message); + } + + public static function generateValidateSignaturePostData(): iterable + { + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + $expiration, + $object_name, + 15_000_000, + 1, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + true, + 'Valid signature', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + $expiration, + $object_name, + 15_000_001, + 1, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Wrong max file size', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + $expiration, + $object_name, + 15_000_000, + 2, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Wrong max file count', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + $expiration, + $object_name.'AAA', + 15_000_000, + 1, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid object name', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180).'A', + $expiration, + $object_name, + 15_000_000, + 1, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid signature', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + $expiration, + $object_name, + 15_000_000, + 1, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)), + false, + 'Expired signature', + ]; + } + + public static function generateValidateSignatureData(): iterable + { + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + true, + 'Valid signature, not expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'HEAD', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + true, + 'Valid signature, not expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A', + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid signature', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)), + false, + 'Signature expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name.'____', + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid object name', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'POST', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Wrong method', + ]; + } + + private function buildGenerator(?UrlGeneratorInterface $urlGenerator = null, ?ClockInterface $clock = null): TempUrlLocalStorageGenerator { return new TempUrlLocalStorageGenerator( - 'abc', - new MockClock('2024-12-16T00:00:00+00:00'), - $urlGenerator, + self::SECRET, + $clock ?? new MockClock('2024-12-16T00:00:00+00:00'), + $urlGenerator ?? $this->prophesize(UrlGeneratorInterface::class)->reveal(), ); } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php new file mode 100644 index 000000000..c680bd0b3 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php @@ -0,0 +1,338 @@ +prophesize(StoredObjectManager::class); + $storedObjectManager->existsContent(Argument::any())->willReturn($existContent); + $storedObjectManager->readContent(Argument::any())->willReturn($readContent); + + $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class); + $tempUrlLocalStorageGenerator->validateSignature( + $request->query->get('sig', ''), + $request->getMethod(), + $request->query->get('object_name', ''), + $request->query->getInt('exp', 0) + ) + ->willReturn($signatureValidity); + + $this->expectException($expectedException); + $this->expectExceptionMessage($expectedExceptionMessage); + + $controller = new StoredObjectContentToLocalStorageController( + $storedObjectManager->reveal(), + $tempUrlLocalStorageGenerator->reveal() + ); + + $controller->contentOperate($request); + } + + public function testOperateContentGetHappyScenario(): void + { + $objectName = 'testABC'; + $expiration = new \DateTimeImmutable(); + $storedObjectManager = $this->prophesize(StoredObjectManager::class); + $storedObjectManager->existsContent($objectName)->willReturn(true); + $storedObjectManager->readContent($objectName)->willReturn('123456789'); + + $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class); + $tempUrlLocalStorageGenerator->validateSignature('signature', 'GET', $objectName, $expiration->getTimestamp()) + ->shouldBeCalled() + ->willReturn(true); + + $controller = new StoredObjectContentToLocalStorageController( + $storedObjectManager->reveal(), + $tempUrlLocalStorageGenerator->reveal() + ); + + $response = $controller->contentOperate(new Request(['object_name' => $objectName, 'sig' => 'signature', 'exp' => $expiration->getTimestamp()])); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('123456789', $response->getContent()); + } + + public function testOperateContentHeadHappyScenario(): void + { + $objectName = 'testABC'; + $expiration = new \DateTimeImmutable(); + $storedObjectManager = $this->prophesize(StoredObjectManager::class); + $storedObjectManager->existsContent($objectName)->willReturn(true); + $storedObjectManager->readContent($objectName)->willReturn('123456789'); + + $tempUrlLocalStorageGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class); + $tempUrlLocalStorageGenerator->validateSignature('signature', 'HEAD', $objectName, $expiration->getTimestamp()) + ->shouldBeCalled() + ->willReturn(true); + + $controller = new StoredObjectContentToLocalStorageController( + $storedObjectManager->reveal(), + $tempUrlLocalStorageGenerator->reveal() + ); + + $request = new Request(['object_name' => $objectName, 'sig' => 'signature', 'exp' => $expiration->getTimestamp()]); + $request->setMethod('HEAD'); + $response = $controller->contentOperate($request); + + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('', $response->getContent()); + } + + public function testPostContentHappyScenario(): void + { + $expiration = 171899000; + $storedObjectManager = $this->prophesize(StoredObjectManager::class); + $storedObjectManager->writeContent('filePrefix/abcSUFFIX', Argument::containingString('fake_encrypted_content')) + ->shouldBeCalled(); + + $tempUrlGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class); + $tempUrlGenerator->validateSignaturePost('signature', 'filePrefix/abc', $expiration, 15_000_000, 1) + ->shouldBeCalled() + ->willReturn(true); + + $controller = new StoredObjectContentToLocalStorageController($storedObjectManager->reveal(), $tempUrlGenerator->reveal()); + + $request = new Request( + ['prefix' => 'filePrefix/abc'], + ['signature' => 'signature', 'expires' => $expiration, 'max_file_size' => 15_000_000, 'max_file_count' => 1], + files: [ + 'filePrefix/abcSUFFIX' => new UploadedFile( + __DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', + 'Document.odt', + test: true + ), + ] + ); + + $response = $controller->postContent($request); + + self::assertEquals(204, $response->getStatusCode()); + } + + /** + * @dataProvider generatePostContentWithExceptionDataProvider + */ + public function testPostContentWithException(Request $request, bool $isSignatureValid, string $expectedException, string $expectedExceptionMessage): void + { + $storedObjectManager = $this->prophesize(StoredObjectManager::class); + $storedObjectManager->writeContent(Argument::any(), Argument::any())->shouldNotBeCalled(); + + $tempUrlGenerator = $this->prophesize(TempUrlLocalStorageGenerator::class); + $tempUrlGenerator->validateSignaturePost('signature', Argument::any(), Argument::any(), Argument::any(), Argument::any()) + ->willReturn($isSignatureValid); + + $controller = new StoredObjectContentToLocalStorageController( + $storedObjectManager->reveal(), + $tempUrlGenerator->reveal() + ); + + $this->expectException($expectedException); + $this->expectExceptionMessage($expectedExceptionMessage); + + $controller->postContent($request); + } + + public static function generatePostContentWithExceptionDataProvider(): iterable + { + $query = ['prefix' => 'filePrefix/abc']; + $attributes = ['signature' => 'signature', 'expires' => 15088556855, 'max_file_size' => 15_000_000, 'max_file_count' => 1]; + + $request = new Request([]); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Prefix parameter is missing', + ]; + + + $attrCloned = [...$attributes]; + unset($attrCloned['max_file_size']); + $request = new Request($query, $attrCloned); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Max file size is not set or equal to zero', + ]; + + $attrCloned = [...$attributes]; + unset($attrCloned['max_file_count']); + $request = new Request($query, $attrCloned); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Max file count is not set or equal to zero', + ]; + + $attrCloned = [...$attributes]; + unset($attrCloned['expires']); + $request = new Request($query, $attrCloned); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Expiration is not set or equal to zero', + ]; + + $attrCloned = [...$attributes]; + unset($attrCloned['signature']); + $request = new Request($query, $attrCloned); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Signature is not set or is a blank string', + ]; + + $request = new Request($query, $attributes); + $request->setMethod('POST'); + + yield [ + $request, + false, + AccessDeniedHttpException::class, + 'Invalid signature', + ]; + + $request = new Request($query, $attributes, files: []); + $request->setMethod('POST'); + + yield [ + $request, + true, + BadRequestHttpException::class, + 'Zero files given', + ]; + + $request = new Request($query, $attributes, files: [ + 'filePrefix/abcSUFFIX_1' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true), + 'filePrefix/abcSUFFIX_2' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content2', test: true), + ]); + $request->setMethod('POST'); + + yield [ + $request, + true, + AccessDeniedHttpException::class, + 'More files than max file count', + ]; + + $request = new Request($query, [...$attributes, 'max_file_size' => 3], files: [ + 'filePrefix/abcSUFFIX_1' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true), + ]); + $request->setMethod('POST'); + + yield [ + $request, + true, + AccessDeniedHttpException::class, + 'File is too big', + ]; + + $request = new Request($query, [...$attributes], files: [ + 'some/other/prefix_SUFFIX' => new UploadedFile(__DIR__.'/data/StoredObjectContentToLocalStorageControllerTest/dummy_file', 'Some content', test: true), + ]); + $request->setMethod('POST'); + + yield [ + $request, + true, + AccessDeniedHttpException::class, + 'Filename does not start with signed prefix', + ]; + } + + public static function generateOperateContentWithExceptionDataProvider(): iterable + { + yield [ + new Request(['object_name' => '', 'sig' => '', 'exp' => 0]), + BadRequestHttpException::class, + 'Object name parameter is missing', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]), + BadRequestHttpException::class, + 'Expiration is not set or equal to zero', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + BadRequestHttpException::class, + 'Signature is not set or is a blank string', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + AccessDeniedHttpException::class, + 'Invalid signature', + false, + '', + false, + ]; + + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + NotFoundHttpException::class, + 'Object does not exists on disk', + false, + '', + true, + ]; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/data/StoredObjectContentToLocalStorageControllerTest/dummy_file b/src/Bundle/ChillDocStoreBundle/Tests/Controller/data/StoredObjectContentToLocalStorageControllerTest/dummy_file new file mode 100644 index 000000000..819563de7 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/data/StoredObjectContentToLocalStorageControllerTest/dummy_file @@ -0,0 +1 @@ +fake_encrypted_content