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" diff --git a/composer.json b/composer.json index 9a17a8257..f3bcba635 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,6 @@ "ext-json": "*", "ext-openssl": "*", "ext-redis": "*", - "champs-libres/async-uploader-bundle": "dev-master@dev", "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/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/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php new file mode 100644 index 000000000..9d79d3603 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGenerator.php @@ -0,0 +1,204 @@ +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 + */ + public function generatePost( + int $expire_delay = null, + int $submit_delay = null, + int $max_file_count = 1, + ): SignedUrlPost { + $delay = $expire_delay ?? $this->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(strtoupper($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/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 @@ +expires->getTimestamp(); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php new file mode 100644 index 000000000..4a5b4e0fb --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/AsyncUpload/SignedUrlPost.php @@ -0,0 +1,54 @@ +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/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/DependencyInjection/ChillDocStoreExtension.php b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php index f57e675dd..7ae5dcbe1 100644 --- a/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php +++ b/src/Bundle/ChillDocStoreBundle/DependencyInjection/ChillDocStoreExtension.php @@ -32,9 +32,10 @@ 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'); $loader->load('services/controller.yaml'); $loader->load('services/menu.yaml'); $loader->load('services/fixtures.yaml'); @@ -94,7 +95,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/Entity/StoredObject.php b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php index 2ee60d80a..e937b2539 100644 --- a/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php +++ b/src/Bundle/ChillDocStoreBundle/Entity/StoredObject.php @@ -11,8 +11,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; @@ -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/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/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/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/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/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/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php new file mode 100644 index 000000000..77d2f8252 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php @@ -0,0 +1,176 @@ + [ + '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, + $parameters, + ); + + $signedUrl = $generator->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); + $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, + $parameters, + ); + + $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, + ]; + } + } +} 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/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() + ); + } +} 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 + ); + } +} 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/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 === $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); + } + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 04fc3ace3..55163abb3 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -1,73 +1,58 @@ -parameters: -# cl_chill_person.example.class: Chill\PersonBundle\Example - services: + _defaults: + autowire: true + autoconfigure: true + Chill\DocStoreBundle\Repository\: - autowire: true - autoconfigure: true resource: "../Repository/" - - Chill\DocStoreBundle\Form\DocumentCategoryType: - class: Chill\DocStoreBundle\Form\DocumentCategoryType - arguments: [ "%kernel.bundles%" ] tags: - - { name: form.type } + - { name: doctrine.repository_service } - Chill\DocStoreBundle\Form\PersonDocumentType: - class: Chill\DocStoreBundle\Form\PersonDocumentType - autowire: true - autoconfigure: true - # arguments: - # - "@chill.main.helper.translatable_string" - tags: - - { name: form.type, alias: chill_docstorebundle_form_document } 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\: - autowire: true - autoconfigure: true resource: '../GenericDoc/Renderer/' + + Chill\DocStoreBundle\Validator\: + resource: '../Validator' + + Chill\DocStoreBundle\AsyncUpload\Driver\: + resource: '../AsyncUpload/Driver/' + + 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 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 } 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' 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'); }; 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