diff --git a/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php new file mode 100644 index 000000000..decfde596 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php @@ -0,0 +1,100 @@ +tempUrlGenerator + ->generatePost( + $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null, + $request->query->has('submit_delay') ? $request->query->getInt('submit_delay') : null + ) + ; + break; + case 'get': + case 'head': + $object_name = $request->query->get('object_name', null); + + if (null === $object_name) { + return (new JsonResponse((object) [ + 'message' => 'the object_name is null', + ])) + ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); + } + $p = $this->tempUrlGenerator->generate( + $method, + $object_name, + $request->query->has('expires_delay') ? $request->query->getInt('expires_delay') : null + ); + break; + default: + return (new JsonResponse((object) ['message' => 'the method ' + ."{$method} is not valid"])) + ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); + } + } catch (TempUrlGeneratorException $e) { + $this->logger->warning('The client requested a temp url' + .' which sparkle an error.', [ + 'message' => $e->getMessage(), + 'expire_delay' => $request->query->getInt('expire_delay', 0), + 'file_count' => $request->query->getInt('file_count', 1), + 'method' => $method, + ]); + + $p = new \stdClass(); + $p->message = $e->getMessage(); + $p->status = JsonResponse::HTTP_BAD_REQUEST; + + return new JsonResponse($p, JsonResponse::HTTP_BAD_REQUEST); + } + + if (!$this->security->isGranted(AsyncUploadVoter::GENERATE_SIGNATURE, $p)) { + throw new AccessDeniedHttpException('not allowed to generate this signature'); + } + + return new JsonResponse( + $this->serializer->serialize($p, 'json', [AbstractNormalizer::GROUPS => ['read']]), + Response::HTTP_OK, + [], + true + ); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/AsyncUploadVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/AsyncUploadVoter.php new file mode 100644 index 000000000..1d7ad759a --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/AsyncUploadVoter.php @@ -0,0 +1,41 @@ +method, ['POST', 'GET', 'HEAD'], true)) { + return false; + } + + return $this->security->isGranted('ROLE_USER') || $this->security->isGranted('ROLE_ADMIN'); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/AsyncUploadControllerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Controller/AsyncUploadControllerTest.php new file mode 100644 index 000000000..02150e3be --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/AsyncUploadControllerTest.php @@ -0,0 +1,110 @@ +expectException(AccessDeniedHttpException::class); + $controller = $this->buildAsyncUploadController(false); + + $controller->getSignedUrl('POST', new Request()); + } + + public function testGeneratePost(): void + { + $controller = $this->buildAsyncUploadController(true); + + $actual = $controller->getSignedUrl('POST', new Request()); + $decodedActual = json_decode($actual->getContent(), true, JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR); + + self::assertArrayHasKey('method', $decodedActual); + self::assertEquals('POST', $decodedActual['method']); + } + + public function testGenerateGet(): void + { + $controller = $this->buildAsyncUploadController(true); + + $actual = $controller->getSignedUrl('GET', new Request(['object_name' => 'abc'])); + $decodedActual = json_decode($actual->getContent(), true, JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR); + + self::assertArrayHasKey('method', $decodedActual); + self::assertEquals('GET', $decodedActual['method']); + } + + private function buildAsyncUploadController( + bool $isGranted, + ): AsyncUploadController { + $tempUrlGenerator = new class () implements TempUrlGeneratorInterface { + public function generatePost(int $expire_delay = null, int $submit_delay = null, int $max_file_count = 1): SignedUrlPost + { + return new SignedUrlPost( + 'https://object.store.example', + new \DateTimeImmutable('1 hour'), + 150, + 1, + 1800, + '', + 'abc', + 'abc' + ); + } + + public function generate(string $method, string $object_name, int $expire_delay = null): SignedUrl + { + return new SignedUrl( + $method, + 'https://object.store.example', + new \DateTimeImmutable('1 hour') + ); + } + }; + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize(Argument::type(SignedUrl::class), 'json', Argument::type('array')) + ->will(fn (array $args): string => json_encode(['method' => $args[0]->method], JSON_THROW_ON_ERROR, 3)); + + $security = $this->prophesize(Security::class); + $security->isGranted(AsyncUploadVoter::GENERATE_SIGNATURE, Argument::type(SignedUrl::class)) + ->willReturn($isGranted); + + return new AsyncUploadController( + $tempUrlGenerator, + $serializer->reveal(), + $security->reveal(), + new NullLogger() + ); + } +}