From 264fff5c36e05e3f38cc6480a8ab031e62c4b286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 24 Nov 2023 12:11:29 +0100 Subject: [PATCH 01/12] Implement asynchronous upload feature in Openstack Object Store Those features were previously stored in champs-libres/async-upload-bundle This commit introduces several new classes within the ChillDocStore bundle for handling asynchronous uploads onto an Openstack Object Store. Specifically, the TempUrlOpenstackGenerator, SignedUrl, TempUrlGeneratorInterface, TempUrlGenerateEvent, and TempUrlGeneratorException classes have been created. This implementation will allow for generating "temporary URLs", which assist in securely and temporarily uploading resources to the OpenStack Object Store. This feature enables the handling of file uploads in a more scalable and efficient manner in high-volume environments. Additionally, corresponding unit tests have also been added to ensure the accuracy of this new feature. --- .../TempUrlOpenstackGenerator.php | 192 ++++++++++++++++++ .../Event/TempUrlGenerateEvent.php | 31 +++ .../Exception/TempUrlGeneratorException.php | 14 ++ .../AsyncUpload/SignedUrl.php | 21 ++ .../AsyncUpload/SignedUrlPost.php | 28 +++ .../AsyncUpload/TempUrlGeneratorInterface.php | 23 +++ .../TempUrlOpenstackGeneratorTest.php | 151 ++++++++++++++ 7 files changed, 460 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Event/TempUrlGenerateEvent.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/TempUrlGeneratorException.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrl.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/TempUrlGeneratorInterface.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php new file mode 100644 index 000000000..742e4285c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php @@ -0,0 +1,192 @@ +max_expire_delay; + $submit_delay ??= $this->max_submit_delay; + + if ($delay < 2) { + throw new TempUrlGeneratorException("The delay of {$delay} is too ".'short (<2 sec) to properly use this token'); + } + + if ($delay > $this->max_expire_delay) { + throw new TempUrlGeneratorException('The given delay is greater than the max delay authorized.'); + } + + if ($submit_delay < 15) { + throw new TempUrlGeneratorException("The submit delay of {$delay} is too ".'short (<15 sec) to properly use this token'); + } + + if ($submit_delay > $this->max_submit_delay) { + throw new TempUrlGeneratorException('The given submit delay is greater than the max submit delay authorized.'); + } + + if ($max_file_count > $this->max_file_count) { + throw new TempUrlGeneratorException('The number of files is greater than the authorized number of files'); + } + + $expires = $this->clock->now()->add(new \DateInterval('PT'.(string) $delay.'S')); + + $object_name = $this->generateObjectName(); + + $g = new SignedUrlPost( + $url = $this->generateUrl($object_name), + $expires, + $this->max_post_file_size, + $max_file_count, + $submit_delay, + '', + $object_name, + $this->generateSignaturePost($url, $expires) + ); + + $this->event_dispatcher->dispatch( + new TempUrlGenerateEvent($g), + ); + + $this->logger->info( + 'generate signature for url', + (array) $g + ); + + return $g; + } + + /** + * Generate an absolute public url for a GET request on the object. + */ + public function generate(string $method, string $object_name, int $expire_delay = null): SignedUrl + { + if ($expire_delay > $this->max_expire_delay) { + throw new TempUrlGeneratorException(sprintf('The expire delay (%d) is greater than the max_expire_delay (%d)', $expire_delay, $this->max_expire_delay)); + } + $url = $this->generateUrl($object_name); + + $expires = $this->clock->now() + ->add(new \DateInterval(sprintf('PT%dS', $expire_delay ?? $this->max_expire_delay))); + $args = [ + 'temp_url_sig' => $this->generateSignature($method, $url, $expires), + 'temp_url_expires' => $expires->format('U'), + ]; + $url = $url.'?'.\http_build_query($args); + + $signature = new SignedUrl($method, $url, $expires); + + $this->event_dispatcher->dispatch( + new TempUrlGenerateEvent($signature) + ); + + return $signature; + } + + private function generateUrl($relative_path): string + { + return match (str_ends_with($this->base_url, '/')) { + true => $this->base_url.$relative_path, + false => $this->base_url.'/'.$relative_path + }; + } + + private function generateObjectName() + { + // inspiration from https://stackoverflow.com/a/4356295/1572236 + $charactersLength = strlen(self::CHARACTERS); + $randomString = ''; + for ($i = 0; $i < 21; ++$i) { + $randomString .= self::CHARACTERS[random_int(0, $charactersLength - 1)]; + } + + return $randomString; + } + + private function generateSignaturePost($url, \DateTimeImmutable $expires) + { + $path = \parse_url((string) $url, PHP_URL_PATH); + + $body = sprintf( + "%s\n%s\n%s\n%s\n%s", + $path, + '', // redirect is empty + (string) $this->max_post_file_size, + (string) $this->max_file_count, + $expires->format('U') + ) + ; + + $this->logger->debug( + 'generate signature post', + ['url' => $body, 'method' => 'POST'] + ); + + return \hash_hmac('sha512', $body, $this->key, false); + } + + private function generateSignature($method, $url, \DateTimeImmutable $expires) + { + if ('POST' === $method) { + return $this->generateSignaturePost($url, $expires); + } + + $path = \parse_url((string) $url, PHP_URL_PATH); + + $body = sprintf( + "%s\n%s\n%s", + $method, + $expires->format('U'), + $path + ) + ; + + $this->logger->debug( + 'generate signature GET', + ['url' => $body, 'method' => 'GET'] + ); + + return \hash_hmac('sha512', $body, $this->key, false); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Event/TempUrlGenerateEvent.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Event/TempUrlGenerateEvent.php new file mode 100644 index 000000000..619dba4e0 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Event/TempUrlGenerateEvent.php @@ -0,0 +1,31 @@ +data->method; + } + + public function getData(): SignedUrl + { + return $this->data; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/TempUrlGeneratorException.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/TempUrlGeneratorException.php new file mode 100644 index 000000000..29c7e78ed --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/TempUrlGeneratorException.php @@ -0,0 +1,14 @@ +generate($method, $objectName, $expireDelay); + + self::assertEquals($expected, $signedUrl); + } + + /** + * @dataProvider dataProviderGeneratePost + */ + public function testGeneratePost(string $baseUrl, \DateTimeImmutable $now, string $key, string $method, string $objectName, int $expireDelay, SignedUrl $expected): void + { + $logger = new NullLogger(); + $eventDispatcher = new EventDispatcher(); + $clock = new MockClock($now); + + $generator = new TempUrlOpenstackGenerator( + $logger, + $eventDispatcher, + $clock, + $baseUrl, + $key, + 1800, + 1800, + 150 + ); + + $signedUrl = $generator->generatePost(); + + self::assertEquals('POST', $signedUrl->method); + self::assertEquals((int) $clock->now()->format('U') + 1800, $signedUrl->expires->getTimestamp()); + self::assertEquals(150, $signedUrl->max_file_size); + self::assertEquals(1, $signedUrl->max_file_count); + self::assertEquals(1800, $signedUrl->submit_delay); + self::assertEquals('', $signedUrl->redirect); + self::assertGreaterThanOrEqual(20, strlen($signedUrl->prefix)); + } + + public function dataProviderGenerate(): iterable + { + $now = \DateTimeImmutable::createFromFormat('U', '1702041743'); + $expireDelay = 1800; + $baseUrls = [ + 'https://objectstore.example/v1/my_account/container/', + 'https://objectstore.example/v1/my_account/container', + ]; + $objectName = 'object'; + $method = 'GET'; + $key = 'MYKEY'; + + $signedUrl = new SignedUrl( + 'GET', + 'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543', + \DateTimeImmutable::createFromFormat('U', '1702043543') + ); + + foreach ($baseUrls as $baseUrl) { + yield [ + $baseUrl, + $now, + $key, + $method, + $objectName, + $expireDelay, + $signedUrl, + ]; + } + } + + public function dataProviderGeneratePost(): iterable + { + $now = \DateTimeImmutable::createFromFormat('U', '1702041743'); + $expireDelay = 1800; + $baseUrls = [ + 'https://objectstore.example/v1/my_account/container/', + 'https://objectstore.example/v1/my_account/container', + ]; + $objectName = 'object'; + $method = 'GET'; + $key = 'MYKEY'; + + $signedUrl = new SignedUrlPost( + 'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543', + \DateTimeImmutable::createFromFormat('U', '1702043543'), + 150, + 1, + 1800, + '', + 'abc', + 'abc' + ); + + foreach ($baseUrls as $baseUrl) { + yield [ + $baseUrl, + $now, + $key, + $method, + $objectName, + $expireDelay, + $signedUrl, + ]; + } + } +} From d68802282552ad02a6ba8ce48d9bfd595f0fbb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 13:45:27 +0100 Subject: [PATCH 02/12] Add Controller to get asyncUpload signatures Added new files for handling asynchronous file uploads in ChillDocStoreBundle. The new files include a controller for generating temporary URLs (AsyncUploadController.php), a security authorization file (AsyncUploadVoter.php), and a corresponding test file (AsyncUploadControllerTest.php). These implementations permit asynchronous uploads via POST, GET, and HEAD methods while maintaining security protocols. --- .../Controller/AsyncUploadController.php | 100 ++++++++++++++++ .../Authorization/AsyncUploadVoter.php | 41 +++++++ .../Controller/AsyncUploadControllerTest.php | 110 ++++++++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Controller/AsyncUploadController.php create mode 100644 src/Bundle/ChillDocStoreBundle/Security/Authorization/AsyncUploadVoter.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Controller/AsyncUploadControllerTest.php 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() + ); + } +} From a70572266f9dea4d715704af9c5ad1c3bed5db87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 16:08:29 +0100 Subject: [PATCH 03/12] Refactor TempUrlOpenstackGenerator to use parameter bag and Bundle configuration The TempUrlOpenstackGenerator now uses a ParameterBag for configuration instead of individual parameters. This simplifies the handling of configuration values and makes the code more maintainable. The parameter configuration has also been included in the chill_doc_store configuration array for a unified approach. --- .../TempUrlOpenstackGenerator.php | 28 +++++--- .../ChillDocStoreExtension.php | 3 +- .../DependencyInjection/Configuration.php | 64 ++++++++++++++++++- .../TempUrlOpenstackGeneratorTest.php | 45 ++++++++++--- 4 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php index 742e4285c..9d79d3603 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php @@ -18,6 +18,7 @@ use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost; use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -27,17 +28,28 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf { private const CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + private string $base_url; + private string $key; + private int $max_expire_delay; + private int $max_submit_delay; + private int $max_post_file_size; + private int $max_file_count; + public function __construct( private LoggerInterface $logger, private EventDispatcherInterface $event_dispatcher, private ClockInterface $clock, - private string $base_url, - private string $key, - private int $max_expire_delay, - private int $max_submit_delay, - private int $max_post_file_size, - private int $max_file_count = 1 - ) {} + ParameterBagInterface $parameterBag, + ) { + $config = $parameterBag->get('chill_doc_store')['openstack']['temp_url']; + + $this->key = $config['temp_url_key']; + $this->base_url = $config['temp_url_base_path']; + $this->max_expire_delay = $config['max_expires_delay']; + $this->max_submit_delay = $config['max_submit_delay']; + $this->max_post_file_size = $config['max_post_file_size']; + $this->max_file_count = $config['max_post_file_count']; + } /** * @throws TempUrlGeneratorException @@ -115,7 +127,7 @@ final readonly class TempUrlOpenstackGenerator implements TempUrlGeneratorInterf ]; $url = $url.'?'.\http_build_query($args); - $signature = new SignedUrl($method, $url, $expires); + $signature = new SignedUrl(strtoupper($method), $url, $expires); $this->event_dispatcher->dispatch( new TempUrlGenerateEvent($signature) diff --git a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php index f57e675dd..70c479456 100644 --- a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php +++ b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php @@ -32,6 +32,8 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); + $container->setParameter('chill_doc_store', $config); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/media.yaml'); @@ -94,7 +96,6 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf 'routing' => [ 'resources' => [ '@ChillDocStoreBundle/config/routes.yaml', - '@ChampsLibresAsyncUploaderBundle/config/routes.yaml', ], ], ]); diff --git a/src/Bundle/ChillDocStoreBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillDocStoreBundle/DependencyInjection/Configuration.php index 466158783..7c6a08f80 100644 --- a/src/Bundle/ChillDocStoreBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillDocStoreBundle/DependencyInjection/Configuration.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -24,11 +25,68 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder('chill_doc_store'); + /** @var ArrayNodeDefinition $rootNode */ $rootNode = $treeBuilder->getRootNode(); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. + /* @phpstan-ignore-next-line As there are inconsistencies in return types, but the code works... */ + $rootNode->children() + // openstack node + ->arrayNode('openstack') + ->info('parameters to authenticate and generate temp url against the openstack object storage service') + ->addDefaultsIfNotSet() + ->children() + // openstack.temp_url + ->arrayNode('temp_url') + ->addDefaultsIfNotSet() + ->children() + // openstack.temp_url.temp_url_key + ->scalarNode('temp_url_key') + ->isRequired()->cannotBeEmpty() + ->info('the temp url key') + ->end() + + ->scalarNode('temp_url_base_path') + ->isRequired()->cannotBeEmpty() + ->info('the base path to add **before** the path to media. Must contains the container') + ->end() + + ->scalarNode('container') + ->info('the container name') + ->isRequired()->cannotBeEmpty() + ->end() + + ->integerNode('max_post_file_size') + ->defaultValue(15_000_000) + ->info('Maximum size of the posted file, in bytes') + ->end() + + ->integerNode('max_post_file_count') + ->defaultValue(1) + ->info('Maximum number of files which may be posted at once using a POST operation using async upload') + ->end() + + ->integerNode('max_expires_delay') + ->defaultValue(180) + ->info('the maximum of seconds a cryptographic signature ' + .'will be valid for submitting a file. This should be ' + .'short, to avoid uploading multiple files') + ->end() + + ->integerNode('max_submit_delay') + ->defaultValue(3600) + ->info('the maximum of seconds between the upload of a file and ' + .'a the submission of the form. This delay will also prevent ' + .'the check of persistence of uploaded file. Should be long ' + .'enough for keeping user-friendly forms') + ->end() + + ->end() // end of children 's temp_url + ->end() // end array temp_url + + ->end() // end of children's openstack + ->end() // end of openstack + ->end() // end of root's children + ->end(); return $treeBuilder; } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php index 25c5a1ebc..77d2f8252 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php @@ -17,6 +17,7 @@ use Chill\DocStoreBundle\AsyncUpload\SignedUrlPost; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; use Symfony\Component\Clock\MockClock; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcher; /** @@ -34,16 +35,28 @@ class TempUrlOpenstackGeneratorTest extends TestCase $logger = new NullLogger(); $eventDispatcher = new EventDispatcher(); $clock = new MockClock($now); + $parameters = new ParameterBag( + [ + 'chill_doc_store' => [ + 'openstack' => [ + 'temp_url' => [ + 'temp_url_key' => $key, + 'temp_url_base_path' => $baseUrl, + 'max_post_file_size' => 150, + 'max_post_file_count' => 1, + 'max_expires_delay' => 1800, + 'max_submit_delay' => 1800, + ], + ], + ], + ] + ); $generator = new TempUrlOpenstackGenerator( $logger, $eventDispatcher, $clock, - $baseUrl, - $key, - 1800, - 1800, - 150 + $parameters, ); $signedUrl = $generator->generate($method, $objectName, $expireDelay); @@ -59,16 +72,28 @@ class TempUrlOpenstackGeneratorTest extends TestCase $logger = new NullLogger(); $eventDispatcher = new EventDispatcher(); $clock = new MockClock($now); + $parameters = new ParameterBag( + [ + 'chill_doc_store' => [ + 'openstack' => [ + 'temp_url' => [ + 'temp_url_key' => $key, + 'temp_url_base_path' => $baseUrl, + 'max_post_file_size' => 150, + 'max_post_file_count' => 1, + 'max_expires_delay' => 1800, + 'max_submit_delay' => 1800, + ], + ], + ], + ] + ); $generator = new TempUrlOpenstackGenerator( $logger, $eventDispatcher, $clock, - $baseUrl, - $key, - 1800, - 1800, - 150 + $parameters, ); $signedUrl = $generator->generatePost(); From 450e7c348bf01f6a3b6595827682c48e3641c16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 16:09:06 +0100 Subject: [PATCH 04/12] Fix and clean DI configuration for chill_doc_store for usage with AsyncUpload --- .../Service/StoredObjectManager.php | 7 +++- .../ChillDocStoreBundle/config/services.yaml | 38 +++++++------------ .../src/Resources/config/services.php | 5 --- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php index 34df98fa2..e5f7c4ee4 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Service; use Base64Url\Base64Url; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; +use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; use Symfony\Component\HttpFoundation\Request; @@ -27,7 +27,10 @@ final class StoredObjectManager implements StoredObjectManagerInterface private array $inMemory = []; - public function __construct(private readonly HttpClientInterface $client, private readonly TempUrlGeneratorInterface $tempUrlGenerator) {} + public function __construct( + private readonly HttpClientInterface $client, + private readonly TempUrlGeneratorInterface $tempUrlGenerator + ) {} public function getLastModified(StoredObject $document): \DateTimeInterface { diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 04fc3ace3..061b0f9c8 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -1,11 +1,12 @@ -parameters: -# cl_chill_person.example.class: Chill\PersonBundle\Example - services: - Chill\DocStoreBundle\Repository\: + _defaults: autowire: true autoconfigure: true + + Chill\DocStoreBundle\Repository\: resource: "../Repository/" + tags: + - { name: doctrine.repository_service } Chill\DocStoreBundle\Form\DocumentCategoryType: class: Chill\DocStoreBundle\Form\DocumentCategoryType @@ -15,8 +16,6 @@ services: Chill\DocStoreBundle\Form\PersonDocumentType: class: Chill\DocStoreBundle\Form\PersonDocumentType - autowire: true - autoconfigure: true # arguments: # - "@chill.main.helper.translatable_string" tags: @@ -24,50 +23,41 @@ services: Chill\DocStoreBundle\Security\Authorization\: resource: "./../Security/Authorization" - autowire: true - autoconfigure: true tags: - { name: chill.role } Chill\DocStoreBundle\Workflow\: resource: './../Workflow/' - autoconfigure: true - autowire: true Chill\DocStoreBundle\Serializer\Normalizer\: - autowire: true resource: '../Serializer/Normalizer/' tags: - - { name: 'serializer.normalizer', priority: 16 } + - { name: serializer.normalizer, priority: 16 } Chill\DocStoreBundle\Service\: - autowire: true - autoconfigure: true resource: '../Service/' Chill\DocStoreBundle\GenericDoc\Manager: - autowire: true - autoconfigure: true arguments: $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider $providersForPerson: !tagged_iterator chill_doc_store.generic_doc_person_provider - Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: - autoconfigure: true - autowire: true + Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension: ~ Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtensionRuntime: - autoconfigure: true - autowire: true arguments: $renderers: !tagged_iterator chill_doc_store.generic_doc_renderer Chill\DocStoreBundle\GenericDoc\Providers\: - autowire: true - autoconfigure: true resource: '../GenericDoc/Providers/' Chill\DocStoreBundle\GenericDoc\Renderer\: + resource: '../GenericDoc/Renderer/' + + Chill\DocStoreBundle\AsyncUpload\Driver\: autowire: true autoconfigure: true - resource: '../GenericDoc/Renderer/' + resource: '../AsyncUpload/Driver/' + + Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface: + alias: Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php index a962c9e67..005994bb6 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/services.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/services.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\WopiBundle\Contracts\AuthorizationManagerInterface; use ChampsLibres\WopiBundle\Contracts\UserManagerInterface; use ChampsLibres\WopiBundle\Service\Wopi as CLWopi; @@ -60,8 +59,4 @@ return static function (ContainerConfigurator $container) { ->set(UserManager::class); $services->alias(UserManagerInterface::class, UserManager::class); - - // TODO: Move this into the async bundle (low priority) - $services - ->alias(TempUrlGeneratorInterface::class, 'async_uploader.temp_url_generator'); }; From 4fd5a37df382df3017de292372989855dbe839d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 16:09:26 +0100 Subject: [PATCH 05/12] Add serializer groups to SignedUrl and SignedUrlPost classes, include tests Serializer groups have been added to the SignedUrl and SignedUrlPost classes to ensure correct serialization. Two new test files were also included: SignedUrlNormalizerTest and SignedUrlPostNormalizerTest which test the normalization of instances of the classes. --- .../AsyncUpload/SignedUrl.php | 17 +++++ .../AsyncUpload/SignedUrlPost.php | 26 ++++++++ .../Normalizer/SignedUrlNormalizerTest.php | 55 ++++++++++++++++ .../SignedUrlPostNormalizerTest.php | 66 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlNormalizerTest.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlPostNormalizerTest.php diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrl.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrl.php index 583325a26..9003a69ea 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrl.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrl.php @@ -11,11 +11,28 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\AsyncUpload; +use Symfony\Component\Serializer\Annotation as Serializer; + readonly class SignedUrl { public function __construct( + /** + * @Serializer\Groups({"read"}) + */ public string $method, + + /** + * @Serializer\Groups({"read"}) + */ public string $url, public \DateTimeImmutable $expires, ) {} + + /** + * @Serializer\Groups({"read"}) + */ + public function getExpires(): int + { + return $this->expires->getTimestamp(); + } } diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php index 98e273635..4a5b4e0fb 100644 --- a/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php @@ -11,16 +11,42 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\AsyncUpload; +use Symfony\Component\Serializer\Annotation as Serializer; + readonly class SignedUrlPost extends SignedUrl { public function __construct( string $url, \DateTimeImmutable $expires, + + /** + * @Serializer\Groups({"read"}) + */ public int $max_file_size, + + /** + * @Serializer\Groups({"read"}) + */ public int $max_file_count, + + /** + * @Serializer\Groups({"read"}) + */ public int $submit_delay, + + /** + * @Serializer\Groups({"read"}) + */ public string $redirect, + + /** + * @Serializer\Groups({"read"}) + */ public string $prefix, + + /** + * @Serializer\Groups({"read"}) + */ public string $signature, ) { parent::__construct('POST', $url, $expires); diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlNormalizerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlNormalizerTest.php new file mode 100644 index 000000000..8cf9c5a6d --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlNormalizerTest.php @@ -0,0 +1,55 @@ +get(NormalizerInterface::class); + } + + public function testNormalizerSignedUrl(): void + { + $signedUrl = new SignedUrl( + 'GET', + 'https://object.store.example/container/object', + \DateTimeImmutable::createFromFormat('U', '1700000') + ); + + $actual = self::$normalizer->normalize($signedUrl, 'json', [AbstractNormalizer::GROUPS => ['read']]); + + self::assertEqualsCanonicalizing( + [ + 'method' => 'GET', + 'expires' => 1_700_000, + 'url' => 'https://object.store.example/container/object', + ], + $actual + ); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlPostNormalizerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlPostNormalizerTest.php new file mode 100644 index 000000000..3d510d22b --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Serializer/Normalizer/SignedUrlPostNormalizerTest.php @@ -0,0 +1,66 @@ +get(NormalizerInterface::class); + } + + public function testNormalizerSignedUrl(): void + { + $signedUrl = new SignedUrlPost( + 'https://object.store.example/container/object', + \DateTimeImmutable::createFromFormat('U', '1700000'), + 15000, + 1, + 180, + '', + 'abc', + 'SiGnaTure' + ); + + $actual = self::$normalizer->normalize($signedUrl, 'json', [AbstractNormalizer::GROUPS => ['read']]); + + self::assertEqualsCanonicalizing( + [ + 'max_file_size' => 15000, + 'max_file_count' => 1, + 'submit_delay' => 180, + 'redirect' => '', + 'prefix' => 'abc', + 'signature' => 'SiGnaTure', + 'method' => 'POST', + 'expires' => 1_700_000, + 'url' => 'https://object.store.example/container/object', + ], + $actual + ); + } +} From 1195767eb3f6641802ea63810a7600f2193b8156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 16:53:38 +0100 Subject: [PATCH 06/12] Add Twig AsyncUploadExtension and its associated test Created AsyncUploadExtension under Chill\DocStoreBundle\AsyncUpload\Templating to provide Twig filter functions for generating URLs for asynchronous file uploads. To ensure correctness, AsyncUploadExtensionTest was implemented in Bundle\ChillDocStoreBundle\Tests\AsyncUpload\Templating. Service definitions were also updated in services.yaml. --- .../Templating/AsyncUploadExtension.php | 69 +++++++++++++++++ .../Templating/AsyncUploadExtensionTest.php | 77 +++++++++++++++++++ .../ChillDocStoreBundle/config/services.yaml | 5 +- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Templating/AsyncUploadExtensionTest.php diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php new file mode 100644 index 000000000..0464b37b9 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Templating/AsyncUploadExtension.php @@ -0,0 +1,69 @@ +computeSignedUrl(...)), + new TwigFilter('generate_url', $this->computeGenerateUrl(...)), + ]; + } + + public function computeSignedUrl(StoredObject|string $file, string $method = 'GET', int $expiresDelay = null): string + { + if ($file instanceof StoredObject) { + $object_name = $file->getFilename(); + } else { + $object_name = $file; + } + + return $this->tempUrlGenerator->generate($method, $object_name, $expiresDelay)->url; + } + + public function computeGenerateUrl(StoredObject|string $file, string $method = 'GET', int $expiresDelay = null): string + { + if ($file instanceof StoredObject) { + $object_name = $file->getFilename(); + } else { + $object_name = $file; + } + + $args = [ + 'method' => $method, + 'object_name' => $object_name, + ]; + + if (null !== $expiresDelay) { + $args['expires_delay'] = $expiresDelay; + } + + return $this->routingUrlGenerator->generate('async_upload.generate_url', $args); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Templating/AsyncUploadExtensionTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Templating/AsyncUploadExtensionTest.php new file mode 100644 index 000000000..78ed1a1d9 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Templating/AsyncUploadExtensionTest.php @@ -0,0 +1,77 @@ +prophesize(TempUrlGeneratorInterface::class); + $generator->generate(Argument::in(['GET', 'POST']), Argument::type('string'), Argument::any()) + ->will(fn (array $args): SignedUrl => new SignedUrl($args[0], 'https://object.store.example/container/'.$args[1], new \DateTimeImmutable('1 hours'))); + + $urlGenerator = $this->prophesize(UrlGeneratorInterface::class); + $urlGenerator->generate('async_upload.generate_url', Argument::type('array')) + ->willReturn('url'); + + $this->asyncUploadExtension = new AsyncUploadExtension( + $generator->reveal(), + $urlGenerator->reveal() + ); + } + + /** + * @dataProvider dataProviderStoredObject + */ + public function testComputeSignedUrl(StoredObject|string $storedObject): void + { + $actual = $this->asyncUploadExtension->computeSignedUrl($storedObject); + + self::assertStringContainsString('https://object.store.example/container', $actual); + self::assertStringContainsString(is_string($storedObject) ? $storedObject : $storedObject->getFilename(), $actual); + } + + /** + * @dataProvider dataProviderStoredObject + */ + public function testComputeGenerateUrl(StoredObject|string $storedObject): void + { + $actual = $this->asyncUploadExtension->computeGenerateUrl($storedObject); + + self::assertEquals('url', $actual); + } + + public function dataProviderStoredObject(): iterable + { + yield [(new StoredObject())->setFilename('blabla')]; + + yield ['blabla']; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 061b0f9c8..c510064f1 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -55,9 +55,10 @@ services: resource: '../GenericDoc/Renderer/' Chill\DocStoreBundle\AsyncUpload\Driver\: - autowire: true - autoconfigure: true resource: '../AsyncUpload/Driver/' + Chill\DocStoreBundle\AsyncUpload\Templating\: + resource: '../AsyncUpload/Templating/' + Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface: alias: Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator From 28d09a820677f59b6533df510d4d621bf1158898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 18:37:58 +0100 Subject: [PATCH 07/12] Add form and types to handle async files --- .../Entity/StoredObject.php | 2 +- .../Form/AccompanyingCourseDocumentType.php | 33 +------- .../Form/DocumentCategoryType.php | 20 ++--- .../Form/StoredObjectType.php | 12 +-- .../Form/Type/AsyncUploaderType.php | 77 +++++++++++++++++++ .../AsyncFileExistsValidatorTest.php | 67 ++++++++++++++++ .../Validator/Constraints/AsyncFileExists.php | 32 ++++++++ .../Constraints/AsyncFileExistsValidator.php | 67 ++++++++++++++++ .../ChillDocStoreBundle/config/services.yaml | 15 +--- .../config/services/form.yaml | 24 +++--- 10 files changed, 271 insertions(+), 78 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/Validator/Constraints/AsyncFileExistsValidatorTest.php create mode 100644 src/Bundle/ChillDocStoreBundle/Validator/Constraints/AsyncFileExists.php create mode 100644 src/Bundle/ChillDocStoreBundle/Validator/Constraints/AsyncFileExistsValidator.php diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 2ee60d80a..abccdccc7 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -12,7 +12,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Entity; use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; -use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; +use Chill\DocStoreBundle\Validator\Constraints\AsyncFileExists; use ChampsLibres\WopiLib\Contract\Entity\Document; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; diff --git a/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php b/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php index 0fced39d3..7ae41f96e 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/AccompanyingCourseDocumentType.php @@ -14,13 +14,10 @@ namespace Chill\DocStoreBundle\Form; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\Document; use Chill\DocStoreBundle\Entity\DocumentCategory; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; -use Chill\MainBundle\Security\Authorization\AuthorizationHelper; -use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\Persistence\ObjectManager; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -29,33 +26,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class AccompanyingCourseDocumentType extends AbstractType { - /** - * @var AuthorizationHelper - */ - protected $authorizationHelper; - - /** - * @var ObjectManager - */ - protected $om; - - /** - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; - - /** - * the user running this form. - * - * @var User - */ - protected $user; - public function __construct( - TranslatableStringHelper $translatableStringHelper - ) { - $this->translatableStringHelper = $translatableStringHelper; - } + private readonly TranslatableStringHelperInterface $translatableStringHelper + ) {} public function buildForm(FormBuilderInterface $builder, array $options) { diff --git a/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php b/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php index f3506d631..6396314e2 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/DocumentCategoryType.php @@ -20,23 +20,15 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class DocumentCategoryType extends AbstractType { - private $chillBundlesFlipped; - - public function __construct($kernelBundles) - { - // TODO faire un service dans CHillMain - foreach ($kernelBundles as $key => $value) { - if (str_starts_with((string) $key, 'Chill')) { - $this->chillBundlesFlipped[$value] = $key; - } - } - } - public function buildForm(FormBuilderInterface $builder, array $options) { + $bundles = [ + 'chill-doc-store' => 'chill-doc-store', + ]; + $builder ->add('bundleId', ChoiceType::class, [ - 'choices' => $this->chillBundlesFlipped, + 'choices' => $bundles, 'disabled' => false, ]) ->add('idInsideBundle', null, [ @@ -44,7 +36,7 @@ class DocumentCategoryType extends AbstractType ]) ->add('documentClass', null, [ 'disabled' => false, - ]) // cahcerh par default PersonDocument + ]) ->add('name', TranslatableStringFormType::class); } diff --git a/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php b/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php index 77484208f..414d94f20 100644 --- a/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php +++ b/src/Bundle/ChillDocStoreBundle/Form/StoredObjectType.php @@ -11,7 +11,7 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Form; -use ChampsLibres\AsyncUploaderBundle\Form\Type\AsyncUploaderType; +use Chill\DocStoreBundle\Form\Type\AsyncUploaderType; use Chill\DocStoreBundle\Entity\StoredObject; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\AbstractType; @@ -26,15 +26,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ class StoredObjectType extends AbstractType { - /** - * @var EntityManagerInterface - */ - protected $em; - - public function __construct(EntityManagerInterface $em) - { - $this->em = $em; - } + public function __construct(private readonly EntityManagerInterface $em) {} public function buildForm(FormBuilderInterface $builder, array $options) { diff --git a/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php b/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php new file mode 100644 index 000000000..58d4eebb1 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Form/Type/AsyncUploaderType.php @@ -0,0 +1,77 @@ +get('chill_doc_store')['openstack']['temp_url']; + + $this->expires_delay = $config['max_expires_delay']; + $this->max_submit_delay = $config['max_submit_delay']; + $this->max_post_file_size = $config['max_post_file_size']; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'expires_delay' => $this->expires_delay, + 'max_post_size' => $this->max_post_file_size, + 'submit_delay' => $this->max_submit_delay, + 'max_files' => 1, + 'error_bubbling' => false, + ]); + + $resolver->setAllowedTypes('expires_delay', ['int']); + $resolver->setAllowedTypes('max_post_size', ['int']); + $resolver->setAllowedTypes('max_files', ['int']); + $resolver->setAllowedTypes('submit_delay', ['int']); + } + + public function buildView( + FormView $view, + FormInterface $form, + array $options + ) { + $view->vars['attr']['data-async-file-upload'] = true; + $view->vars['attr']['data-generate-temp-url-post'] = $this + ->url_generator->generate('async_upload.generate_url', [ + 'expires_delay' => $options['expires_delay'], + 'method' => 'post', + 'submit_delay' => $options['submit_delay'], + ]); + $view->vars['attr']['data-temp-url-get'] = $this->url_generator + ->generate('async_upload.generate_url', ['method' => 'GET']); + $view->vars['attr']['data-max-files'] = $options['max_files']; + $view->vars['attr']['data-max-post-size'] = $options['max_post_size']; + } + + public function getParent() + { + return HiddenType::class; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Validator/Constraints/AsyncFileExistsValidatorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Validator/Constraints/AsyncFileExistsValidatorTest.php new file mode 100644 index 000000000..6e46ee370 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/Validator/Constraints/AsyncFileExistsValidatorTest.php @@ -0,0 +1,67 @@ + 404]); + } + + return new MockResponse('', ['http_code' => 200]); + }); + + $generator = $this->prophesize(TempUrlGeneratorInterface::class); + $generator->generate(Argument::in(['GET', 'HEAD']), Argument::type('string'), Argument::any()) + ->will(fn (array $args): SignedUrl => new SignedUrl($args[0], 'https://object.store.example/container/'.$args[1], new \DateTimeImmutable('1 hours'))); + + return new AsyncFileExistsValidator($generator->reveal(), $client); + } + + public function testWhenFileExistsIsValid(): void + { + $this->validator->validate((new StoredObject())->setFilename('present'), new AsyncFileExists()); + + $this->assertNoViolation(); + } + + public function testWhenFileIsNotPresent(): void + { + $this->validator->validate( + (new StoredObject())->setFilename('is_404'), + new AsyncFileExists(['message' => 'my_message']) + ); + + $this->buildViolation('my_message')->setParameter('{{ filename }}', 'is_404')->assertRaised(); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Validator/Constraints/AsyncFileExists.php b/src/Bundle/ChillDocStoreBundle/Validator/Constraints/AsyncFileExists.php new file mode 100644 index 000000000..4e251629b --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Validator/Constraints/AsyncFileExists.php @@ -0,0 +1,32 @@ +validateObject($value->getFilename(), $constraint); + } elseif (is_string($value)) { + $this->validateObject($value, $constraint); + } else { + throw new UnexpectedValueException($value, StoredObject::class.' or string'); + } + } + + protected function validateObject(string $file, Constraint $constraint): void + { + if (!$constraint instanceof AsyncFileExists) { + throw new UnexpectedTypeException($constraint, AsyncFileExists::class); + } + + $urlHead = $this->tempUrlGenerator->generate( + 'HEAD', + $file, + 30 + ); + + try { + $response = $this->client->request('HEAD', $urlHead->url); + + if (404 === $response->getStatusCode()) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ filename }}', $file) + ->addViolation(); + } + } catch (HttpExceptionInterface $exception) { + if (404 !== $exception->getResponse()->getStatusCode()) { + throw $exception; + } + } + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index c510064f1..27c04a60e 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -8,18 +8,6 @@ services: tags: - { name: doctrine.repository_service } - Chill\DocStoreBundle\Form\DocumentCategoryType: - class: Chill\DocStoreBundle\Form\DocumentCategoryType - arguments: [ "%kernel.bundles%" ] - tags: - - { name: form.type } - - Chill\DocStoreBundle\Form\PersonDocumentType: - class: Chill\DocStoreBundle\Form\PersonDocumentType - # arguments: - # - "@chill.main.helper.translatable_string" - tags: - - { name: form.type, alias: chill_docstorebundle_form_document } Chill\DocStoreBundle\Security\Authorization\: resource: "./../Security/Authorization" @@ -54,6 +42,9 @@ services: Chill\DocStoreBundle\GenericDoc\Renderer\: resource: '../GenericDoc/Renderer/' + Chill\DocStoreBundle\Validator\: + resource: '../Validator' + Chill\DocStoreBundle\AsyncUpload\Driver\: resource: '../AsyncUpload/Driver/' diff --git a/src/Bundle/ChillDocStoreBundle/config/services/form.yaml b/src/Bundle/ChillDocStoreBundle/config/services/form.yaml index 8d53d81f1..7515f306a 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services/form.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services/form.yaml @@ -1,13 +1,15 @@ services: - Chill\DocStoreBundle\Form\StoredObjectType: - arguments: - $em: '@Doctrine\ORM\EntityManagerInterface' - tags: - - { name: form.type } + _defaults: + autowire: true + autoconfigure: true - Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType: - class: Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType - arguments: - - "@chill.main.helper.translatable_string" - tags: - - { name: form.type, alias: chill_docstorebundle_form_document } + Chill\DocStoreBundle\Form\: + resource: '../../Form' + + Chill\DocStoreBundle\Form\PersonDocumentType: + tags: + - { name: form.type, alias: chill_docstorebundle_form_document } + + Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType: + tags: + - { name: form.type, alias: chill_docstorebundle_form_document } From 82ca32171570692f115bbc797bf6d58f0aa72b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Dec 2023 22:47:15 +0100 Subject: [PATCH 08/12] Add exceptions for async file handling failures Introduced two new exceptions, `BadCallToRemoteServer` and `TempUrlRemoteServerException`, to improve error handling in asynchronous file operations. These exceptions are thrown when there are issues with the remote server during an async file operation such as HTTP status codes >= 400 and if the server is unreachable. --- .../Exception/BadCallToRemoteServer.php | 20 +++++++++++++++++++ .../TempUrlRemoteServerException.php | 20 +++++++++++++++++++ .../Constraints/AsyncFileExistsValidator.php | 11 +++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/BadCallToRemoteServer.php create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/TempUrlRemoteServerException.php diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/BadCallToRemoteServer.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/BadCallToRemoteServer.php new file mode 100644 index 000000000..58ddfe0b0 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Exception/BadCallToRemoteServer.php @@ -0,0 +1,20 @@ +client->request('HEAD', $urlHead->url); - if (404 === $response->getStatusCode()) { + if (404 === $status = $response->getStatusCode()) { $this->context->buildViolation($constraint->message) ->setParameter('{{ filename }}', $file) ->addViolation(); + } elseif (500 <= $status) { + throw new TempUrlRemoteServerException($response->getStatusCode()); + } elseif (400 <= $status) { + throw new BadCallToRemoteServer($response->getContent(false), $response->getStatusCode()); } } catch (HttpExceptionInterface $exception) { if (404 !== $exception->getResponse()->getStatusCode()) { throw $exception; } + } catch (TransportExceptionInterface $e) { + throw new TempUrlRemoteServerException(0, previous: $e); } } } From 45e1ce034a3063b3da35994efb80f787e9900dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Dec 2023 11:35:44 +0100 Subject: [PATCH 09/12] Add ConfigureOpenstackObjectStorageCommand to ChillDocStoreBundle Added new command file "ConfigureOpenstackObjectStorageCommand.php" under ChillDocStoreBundle that helps in setting up OpenStack container for document storage. Along with the command, added corresponding test file "ConfigureOpenstackObjectStorageCommandTest.php" to ensure the functionality is working as expected. --- ...ConfigureOpenstackObjectStorageCommand.php | 90 +++++++++++++++++++ ...igureOpenstackObjectStorageCommandTest.php | 65 ++++++++++++++ .../ChillDocStoreBundle/config/services.yaml | 3 + 3 files changed, 158 insertions(+) create mode 100644 src/Bundle/ChillDocStoreBundle/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommand.php create mode 100644 src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommandTest.php diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommand.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommand.php new file mode 100644 index 000000000..de6690faf --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommand.php @@ -0,0 +1,90 @@ +get('chill_doc_store')['openstack']['temp_url']; + + $this->tempUrlKey = $config['temp_url_key']; + $this->basePath = $config['temp_url_base_path']; + + parent::__construct(); + } + + protected function configure() + { + $this->setDescription('Configure openstack container to store documents') + ->setName('chill:doc-store:configure-openstack') + ->addOption('os_token', 'o', InputOption::VALUE_REQUIRED, 'Openstack token') + ->addOption('domain', 'd', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Domain name') + ; + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + if (!$input->hasOption('os_token')) { + $output->writeln('The option os_token is required'); + + throw new \RuntimeException('The option os_token is required'); + } + + if (0 === count($input->getOption('domain'))) { + $output->writeln('At least one occurence of option domain is required'); + + throw new \RuntimeException('At least one occurence of option domain is required'); + } + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $domains = trim(implode(' ', $input->getOption('domain'))); + + if ($output->isVerbose()) { + $output->writeln(['Domains configured will be', $domains]); + } + + try { + $response = $this->client->request('POST', $this->basePath, [ + 'headers' => [ + 'X-Auth-Token' => $input->getOption('os_token'), + 'X-Container-Meta-Access-Control-Allow-Origin' => $domains, + 'X-Container-Meta-Temp-URL-Key' => $this->tempUrlKey, + ], + ]); + $response->getContent(); + } catch (HttpExceptionInterface $e) { + $output->writeln('Error'); + $output->writeln($e->getMessage()); + + if ($output->isVerbose()) { + $output->writeln($e->getTraceAsString()); + } + } + + return 0; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommandTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommandTest.php new file mode 100644 index 000000000..e0318313c --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Command/ConfigureOpenstackObjectStorageCommandTest.php @@ -0,0 +1,65 @@ + 204]); + }); + + $parameters = new ParameterBag([ + 'chill_doc_store' => [ + 'openstack' => [ + 'temp_url' => [ + 'temp_url_key' => '12345679801234567890', + 'temp_url_base_path' => 'https://object.store.example/v1/AUTH/container', + ], + ], + ], + ]); + + $command = new ConfigureOpenstackObjectStorageCommand($client, $parameters); + + $tester = new CommandTester($command); + + $status = $tester->execute([ + '--os_token' => 'abc', + '--domain' => ['https://chill.domain.social', 'https://chill2.domain.social'], + ]); + + self::assertSame(0, $status); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 27c04a60e..55163abb3 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -51,5 +51,8 @@ services: Chill\DocStoreBundle\AsyncUpload\Templating\: resource: '../AsyncUpload/Templating/' + Chill\DocStoreBundle\AsyncUpload\Command\: + resource: '../AsyncUpload/Command/' + Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface: alias: Chill\DocStoreBundle\AsyncUpload\Driver\OpenstackObjectStore\TempUrlOpenstackGenerator From f131572344f175249bdb3ea3e68ae00330ecaaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Dec 2023 12:01:51 +0100 Subject: [PATCH 10/12] Remove references to old async upload bundle --- composer.json | 1 - .../ChillDocStoreExtension.php | 1 - .../Entity/StoredObject.php | 3 +- .../Object/ObjectToAsyncFileTransformer.php | 47 ------------------- .../Object/PersistenceChecker.php | 40 ---------------- .../Tests/StoredObjectManagerTest.php | 10 ++-- .../config/services/media.yaml | 9 ---- 7 files changed, 8 insertions(+), 103 deletions(-) delete mode 100644 src/Bundle/ChillDocStoreBundle/Object/ObjectToAsyncFileTransformer.php delete mode 100644 src/Bundle/ChillDocStoreBundle/Object/PersistenceChecker.php delete mode 100644 src/Bundle/ChillDocStoreBundle/config/services/media.yaml diff --git a/composer.json b/composer.json index 3195f732b..3a12f5617 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,6 @@ "ext-json": "*", "ext-openssl": "*", "ext-redis": "*", - "champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290", "champs-libres/wopi-bundle": "dev-master@dev", "champs-libres/wopi-lib": "dev-master@dev", "doctrine/doctrine-bundle": "^2.1", diff --git a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php index 70c479456..7ae5dcbe1 100644 --- a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php +++ b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php @@ -36,7 +36,6 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); - $loader->load('services/media.yaml'); $loader->load('services/controller.yaml'); $loader->load('services/menu.yaml'); $loader->load('services/fixtures.yaml'); diff --git a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index abccdccc7..e937b2539 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Entity; -use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; use Chill\DocStoreBundle\Validator\Constraints\AsyncFileExists; use ChampsLibres\WopiLib\Contract\Entity\Document; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; @@ -33,7 +32,7 @@ use Symfony\Component\Serializer\Annotation as Serializer; * message="The file is not stored properly" * ) */ -class StoredObject implements AsyncFileInterface, Document, TrackCreationInterface +class StoredObject implements Document, TrackCreationInterface { use TrackCreationTrait; final public const STATUS_READY = 'ready'; diff --git a/src/Bundle/ChillDocStoreBundle/Object/ObjectToAsyncFileTransformer.php b/src/Bundle/ChillDocStoreBundle/Object/ObjectToAsyncFileTransformer.php deleted file mode 100644 index 8755b1c61..000000000 --- a/src/Bundle/ChillDocStoreBundle/Object/ObjectToAsyncFileTransformer.php +++ /dev/null @@ -1,47 +0,0 @@ -em = $em; - } - - public function toAsyncFile($data) - { - if ($data instanceof StoredObject) { - return $data; - } - } - - public function toData(AsyncFileInterface $asyncFile) - { - $object = $this->em - ->getRepository(StoredObject::class) - ->findByFilename($asyncFile->getObjectName()); - - return $object ?? (new StoredObject()) - ->setFilename($asyncFile->getObjectName()); - } -} diff --git a/src/Bundle/ChillDocStoreBundle/Object/PersistenceChecker.php b/src/Bundle/ChillDocStoreBundle/Object/PersistenceChecker.php deleted file mode 100644 index 97dfdfa92..000000000 --- a/src/Bundle/ChillDocStoreBundle/Object/PersistenceChecker.php +++ /dev/null @@ -1,40 +0,0 @@ -em = $em; - } - - public function isPersisted($object_name): bool - { - $qb = $this->em->createQueryBuilder(); - $qb->select('COUNT(m)') - ->from(StoredObject::class, 'm') - ->where($qb->expr()->eq('m.filename', ':object_name')) - ->setParameter('object_name', $object_name); - - return 1 === $qb->getQuery()->getSingleScalarResult(); - } -} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php index bf659ba9b..226f67118 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/StoredObjectManagerTest.php @@ -11,7 +11,8 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Tests; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; +use Chill\DocStoreBundle\AsyncUpload\SignedUrl; +use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; use Chill\DocStoreBundle\Service\StoredObjectManager; @@ -163,8 +164,11 @@ final class StoredObjectManagerTest extends TestCase private function getTempUrlGenerator(StoredObject $storedObject): TempUrlGeneratorInterface { - $response = new \stdClass(); - $response->url = $storedObject->getFilename(); + $response = new SignedUrl( + 'PUT', + 'https://example.com/'.$storedObject->getFilename(), + new \DateTimeImmutable('1 hours') + ); $tempUrlGenerator = $this->createMock(TempUrlGeneratorInterface::class); diff --git a/src/Bundle/ChillDocStoreBundle/config/services/media.yaml b/src/Bundle/ChillDocStoreBundle/config/services/media.yaml deleted file mode 100644 index e6afe2155..000000000 --- a/src/Bundle/ChillDocStoreBundle/config/services/media.yaml +++ /dev/null @@ -1,9 +0,0 @@ -services: - chill_doc_store.persistence_checker: - class: Chill\DocStoreBundle\Object\PersistenceChecker - arguments: - $em: '@Doctrine\ORM\EntityManagerInterface' - - Chill\DocStoreBundle\Object\ObjectToAsyncFileTransformer: - arguments: - $em: '@Doctrine\ORM\EntityManagerInterface' From 1065706e60cfa12ca4e7c6762df6e42a2e677fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Dec 2023 12:08:45 +0100 Subject: [PATCH 11/12] Re-configure test app --- tests/app/config/bundles.php | 1 - tests/app/config/packages/champs-libres.yaml | 14 -------------- tests/app/config/packages/chill_doc_store.yaml | 6 ++++++ 3 files changed, 6 insertions(+), 15 deletions(-) delete mode 100644 tests/app/config/packages/champs-libres.yaml create mode 100644 tests/app/config/packages/chill_doc_store.yaml diff --git a/tests/app/config/bundles.php b/tests/app/config/bundles.php index 36e3bcf03..d39a7c469 100644 --- a/tests/app/config/bundles.php +++ b/tests/app/config/bundles.php @@ -10,7 +10,6 @@ declare(strict_types=1); */ return [ - ChampsLibres\AsyncUploaderBundle\ChampsLibresAsyncUploaderBundle::class => ['all' => true], Chill\ActivityBundle\ChillActivityBundle::class => ['all' => true], Chill\AsideActivityBundle\ChillAsideActivityBundle::class => ['all' => true], Chill\CalendarBundle\ChillCalendarBundle::class => ['all' => true], diff --git a/tests/app/config/packages/champs-libres.yaml b/tests/app/config/packages/champs-libres.yaml deleted file mode 100644 index ebea3ef58..000000000 --- a/tests/app/config/packages/champs-libres.yaml +++ /dev/null @@ -1,14 +0,0 @@ -champs_libres_async_uploader: - openstack: - os_username: '%env(resolve:OS_USERNAME)%' # Required - os_password: '%env(resolve:OS_PASSWORD)%' # Required - os_tenant_id: '%env(resolve:OS_TENANT_ID)%' # Required - os_region_name: '%env(resolve:OS_REGION_NAME)%' # Required - os_auth_url: '%env(resolve:OS_AUTH_URL)%' # Required - 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. Do not forget a trailing slash - max_post_file_size: 15000000 # 15Mo (bytes) - max_expires_delay: 180 - max_submit_delay: 3600 diff --git a/tests/app/config/packages/chill_doc_store.yaml b/tests/app/config/packages/chill_doc_store.yaml new file mode 100644 index 000000000..994e19240 --- /dev/null +++ b/tests/app/config/packages/chill_doc_store.yaml @@ -0,0 +1,6 @@ +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 From 3fb04594eb30b491e52edeb98054e749132d9dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 12 Dec 2023 15:49:37 +0100 Subject: [PATCH 12/12] add changie [ci-skip] --- .changes/unreleased/Feature-20231212-154841.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/unreleased/Feature-20231212-154841.yaml diff --git a/.changes/unreleased/Feature-20231212-154841.yaml b/.changes/unreleased/Feature-20231212-154841.yaml new file mode 100644 index 000000000..14e19345b --- /dev/null +++ b/.changes/unreleased/Feature-20231212-154841.yaml @@ -0,0 +1,5 @@ +kind: Feature +body: '[DX] move async-upload-bundle features into chill-bundles' +time: 2023-12-12T15:48:41.954970271+01:00 +custom: + Issue: "221"