Add TempUrl signature validation to local storage

Implemented local storage-based file handling with TempUrl signature validation for upload and retrieval. Added validation checks for parameters like max file size/count, expiration, and signature integrity. Included unit tests for TempUrl signature validation and adjusted configuration for local storage.
This commit is contained in:
2025-01-06 14:37:35 +01:00
parent 0c628c39db
commit 999ac3af2b
6 changed files with 609 additions and 25 deletions

View File

@@ -12,9 +12,11 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\StoredObjectManager;
use Chill\DocStoreBundle\AsyncUpload\Driver\LocalStorage\TempUrlLocalStorageGenerator;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
@@ -26,6 +28,7 @@ final readonly class StoredObjectContentToLocalStorageController
{
public function __construct(
private StoredObjectManager $storedObjectManager,
private TempUrlLocalStorageGenerator $tempUrlLocalStorageGenerator,
) {}
#[Route('/public/stored-object/post', name: 'chill_docstore_storedobject_post', methods: ['POST'])]
@@ -34,14 +37,51 @@ final readonly class StoredObjectContentToLocalStorageController
$prefix = $request->query->get('prefix', '');
if ('' === $prefix) {
throw new BadRequestHttpException('prefix parameter is missing');
throw new BadRequestHttpException('Prefix parameter is missing');
}
if (0 === $maxFileSize = $request->request->getInt('max_file_size', 0)) {
throw new BadRequestHttpException('Max file size is not set or equal to zero');
}
if (1 !== $maxFileCount = $request->request->getInt('max_file_count', 0)) {
throw new BadRequestHttpException('Max file count is not set or equal to zero');
}
if (0 === $expiration = $request->request->getInt('expires', 0)) {
throw new BadRequestHttpException('Expiration is not set or equal to zero');
}
if ('' === $signature = $request->request->get('signature', '')) {
throw new BadRequestHttpException('Signature is not set or is a blank string');
}
if (!$this->tempUrlLocalStorageGenerator->validateSignaturePost($signature, $prefix, $expiration, $maxFileSize, $maxFileCount)) {
throw new AccessDeniedHttpException('Invalid signature');
}
$keyFiles = $request->files->keys();
if ($maxFileCount < count($keyFiles)) {
throw new AccessDeniedHttpException('More files than max file count');
}
if (0 === count($keyFiles)) {
throw new BadRequestHttpException('Zero files given');
}
foreach ($keyFiles as $keyFile) {
/** @var UploadedFile $file */
$file = $request->files->get($keyFile);
if ($maxFileSize < strlen($file->getContent())) {
throw new AccessDeniedHttpException('File is too big');
}
if (!str_starts_with((string) $keyFile, $prefix)) {
throw new AccessDeniedHttpException('Filename does not start with signed prefix');
}
$this->storedObjectManager->writeContent($keyFile, $file->getContent());
}
@@ -51,19 +91,30 @@ final readonly class StoredObjectContentToLocalStorageController
#[Route('/public/stored-object/operate', name: 'chill_docstore_stored_object_operate', methods: ['GET', 'HEAD'])]
public function contentOperate(Request $request): Response
{
$objectName = $request->query->get('object_name', '');
if ('' === $objectName = $request->query->get('object_name', '')) {
throw new BadRequestHttpException('Object name parameter is missing');
}
if ('' === $objectName) {
throw new BadRequestHttpException('object name parameter is missing');
if (0 === $expiration = $request->query->getInt('exp', 0)) {
throw new BadRequestHttpException('Expiration is not set or equal to zero');
}
if ('' === $signature = $request->query->get('sig', '')) {
throw new BadRequestHttpException('Signature is not set or is a blank string');
}
if (!$this->tempUrlLocalStorageGenerator->validateSignature($signature, strtoupper($request->getMethod()), $objectName, $expiration)) {
throw new AccessDeniedHttpException('Invalid signature');
}
if (!$this->storedObjectManager->existsContent($objectName)) {
throw new NotFoundHttpException('object does not exists on disk');
throw new NotFoundHttpException('Object does not exists on disk');
}
return match ($request->getMethod()) {
'GET' => new Response($this->storedObjectManager->readContent($objectName)),
'HEAD' => new Response(''),
default => throw new BadRequestHttpException('method not supported'),
};
}
}