mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-11 07:49:43 +00:00
Implemented local storage-based file handling with TempUrl signature validation for upload and retrieval. Added validation checks for parameters like max file size/count, expiration, and signature integrity. Included unit tests for TempUrl signature validation and adjusted configuration for local storage.
121 lines
4.6 KiB
PHP
121 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Chill is a software for social workers
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Chill\DocStoreBundle\Controller;
|
|
|
|
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager;
|
|
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
|
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use Symfony\Component\Routing\Annotation\Route;
|
|
|
|
/**
|
|
* Controller to deal with local storage operation.
|
|
*/
|
|
final readonly class StoredObjectContentToLocalStorageController
|
|
{
|
|
public function __construct(
|
|
private StoredObjectManager $storedObjectManager,
|
|
private TempUrlLocalStorageGenerator $tempUrlLocalStorageGenerator,
|
|
) {}
|
|
|
|
#[Route('/public/stored-object/post', name: 'chill_docstore_storedobject_post', methods: ['POST'])]
|
|
public function postContent(Request $request): Response
|
|
{
|
|
$prefix = $request->query->get('prefix', '');
|
|
|
|
if ('' === $prefix) {
|
|
throw new BadRequestHttpException('Prefix parameter is missing');
|
|
}
|
|
|
|
if (0 === $maxFileSize = $request->request->getInt('max_file_size', 0)) {
|
|
throw new BadRequestHttpException('Max file size is not set or equal to zero');
|
|
}
|
|
|
|
if (1 !== $maxFileCount = $request->request->getInt('max_file_count', 0)) {
|
|
throw new BadRequestHttpException('Max file count is not set or equal to zero');
|
|
}
|
|
|
|
if (0 === $expiration = $request->request->getInt('expires', 0)) {
|
|
throw new BadRequestHttpException('Expiration is not set or equal to zero');
|
|
}
|
|
|
|
if ('' === $signature = $request->request->get('signature', '')) {
|
|
throw new BadRequestHttpException('Signature is not set or is a blank string');
|
|
}
|
|
|
|
if (!$this->tempUrlLocalStorageGenerator->validateSignaturePost($signature, $prefix, $expiration, $maxFileSize, $maxFileCount)) {
|
|
throw new AccessDeniedHttpException('Invalid signature');
|
|
}
|
|
|
|
$keyFiles = $request->files->keys();
|
|
|
|
if ($maxFileCount < count($keyFiles)) {
|
|
throw new AccessDeniedHttpException('More files than max file count');
|
|
}
|
|
|
|
if (0 === count($keyFiles)) {
|
|
throw new BadRequestHttpException('Zero files given');
|
|
}
|
|
|
|
foreach ($keyFiles as $keyFile) {
|
|
/** @var UploadedFile $file */
|
|
$file = $request->files->get($keyFile);
|
|
|
|
if ($maxFileSize < strlen($file->getContent())) {
|
|
throw new AccessDeniedHttpException('File is too big');
|
|
}
|
|
|
|
if (!str_starts_with((string) $keyFile, $prefix)) {
|
|
throw new AccessDeniedHttpException('Filename does not start with signed prefix');
|
|
}
|
|
|
|
$this->storedObjectManager->writeContent($keyFile, $file->getContent());
|
|
}
|
|
|
|
return new Response(status: Response::HTTP_NO_CONTENT);
|
|
}
|
|
|
|
#[Route('/public/stored-object/operate', name: 'chill_docstore_stored_object_operate', methods: ['GET', 'HEAD'])]
|
|
public function contentOperate(Request $request): Response
|
|
{
|
|
if ('' === $objectName = $request->query->get('object_name', '')) {
|
|
throw new BadRequestHttpException('Object name parameter is missing');
|
|
}
|
|
|
|
if (0 === $expiration = $request->query->getInt('exp', 0)) {
|
|
throw new BadRequestHttpException('Expiration is not set or equal to zero');
|
|
}
|
|
|
|
if ('' === $signature = $request->query->get('sig', '')) {
|
|
throw new BadRequestHttpException('Signature is not set or is a blank string');
|
|
}
|
|
|
|
if (!$this->tempUrlLocalStorageGenerator->validateSignature($signature, strtoupper($request->getMethod()), $objectName, $expiration)) {
|
|
throw new AccessDeniedHttpException('Invalid signature');
|
|
}
|
|
|
|
if (!$this->storedObjectManager->existsContent($objectName)) {
|
|
throw new NotFoundHttpException('Object does not exists on disk');
|
|
}
|
|
|
|
return match ($request->getMethod()) {
|
|
'GET' => new Response($this->storedObjectManager->readContent($objectName)),
|
|
'HEAD' => new Response(''),
|
|
default => throw new BadRequestHttpException('method not supported'),
|
|
};
|
|
}
|
|
}
|