mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
211 lines
6.5 KiB
PHP
211 lines
6.5 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\Service;
|
|
|
|
use Base64Url\Base64Url;
|
|
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
|
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
|
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use DateTimeZone;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
|
use Throwable;
|
|
use function array_key_exists;
|
|
use const OPENSSL_RAW_DATA;
|
|
|
|
final class StoredObjectManager implements StoredObjectManagerInterface
|
|
{
|
|
private const ALGORITHM = 'AES-256-CBC';
|
|
|
|
private HttpClientInterface $client;
|
|
|
|
private array $inMemory = [];
|
|
|
|
private TempUrlGeneratorInterface $tempUrlGenerator;
|
|
|
|
public function __construct(
|
|
HttpClientInterface $client,
|
|
TempUrlGeneratorInterface $tempUrlGenerator
|
|
) {
|
|
$this->client = $client;
|
|
$this->tempUrlGenerator = $tempUrlGenerator;
|
|
}
|
|
|
|
public function getLastModified(StoredObject $document): DateTimeInterface
|
|
{
|
|
if ($this->hasCache($document)) {
|
|
$response = $this->getResponseFromCache($document);
|
|
} else {
|
|
try {
|
|
$response = $this
|
|
->client
|
|
->request(
|
|
Request::METHOD_HEAD,
|
|
$this
|
|
->tempUrlGenerator
|
|
->generate(
|
|
Request::METHOD_HEAD,
|
|
$document->getFilename()
|
|
)
|
|
->url
|
|
);
|
|
} catch (TransportExceptionInterface $exception) {
|
|
throw StoredObjectManagerException::errorDuringHttpRequest($exception);
|
|
}
|
|
}
|
|
|
|
return $this->extractLastModifiedFromResponse($response);
|
|
}
|
|
|
|
public function read(StoredObject $document): string
|
|
{
|
|
$response = $this->getResponseFromCache($document);
|
|
|
|
try {
|
|
$data = $response->getContent();
|
|
} catch (Throwable $e) {
|
|
throw StoredObjectManagerException::unableToGetResponseContent($e);
|
|
}
|
|
|
|
if (false === $this->hasKeysAndIv($document)) {
|
|
return $data;
|
|
}
|
|
|
|
$clearData = openssl_decrypt(
|
|
$data,
|
|
self::ALGORITHM,
|
|
// TODO: Why using this library and not use base64_decode() ?
|
|
Base64Url::decode($document->getKeyInfos()['k']),
|
|
OPENSSL_RAW_DATA,
|
|
pack('C*', ...$document->getIv())
|
|
);
|
|
|
|
if (false === $clearData) {
|
|
throw StoredObjectManagerException::unableToDecrypt(openssl_error_string());
|
|
}
|
|
|
|
return $clearData;
|
|
}
|
|
|
|
public function write(StoredObject $document, string $clearContent): void
|
|
{
|
|
if ($this->hasCache($document)) {
|
|
unset($this->inMemory[$document->getUuid()->toString()]);
|
|
}
|
|
|
|
$encryptedContent = $this->hasKeysAndIv($document)
|
|
? openssl_encrypt(
|
|
$clearContent,
|
|
self::ALGORITHM,
|
|
// TODO: Why using this library and not use base64_decode() ?
|
|
Base64Url::decode($document->getKeyInfos()['k']),
|
|
OPENSSL_RAW_DATA,
|
|
pack('C*', ...$document->getIv())
|
|
)
|
|
: $clearContent;
|
|
|
|
try {
|
|
$response = $this
|
|
->client
|
|
->request(
|
|
Request::METHOD_PUT,
|
|
$this
|
|
->tempUrlGenerator
|
|
->generate(
|
|
Request::METHOD_PUT,
|
|
$document->getFilename()
|
|
)
|
|
->url,
|
|
[
|
|
'body' => $encryptedContent,
|
|
]
|
|
);
|
|
} catch (TransportExceptionInterface $exception) {
|
|
throw StoredObjectManagerException::errorDuringHttpRequest($exception);
|
|
}
|
|
|
|
if ($response->getStatusCode() !== Response::HTTP_CREATED) {
|
|
throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode());
|
|
}
|
|
}
|
|
|
|
private function extractLastModifiedFromResponse(ResponseInterface $response): DateTimeImmutable
|
|
{
|
|
$lastModifiedString = (($response->getHeaders()['last-modified'] ?? [])[0] ?? '');
|
|
|
|
$date = DateTimeImmutable::createFromFormat(
|
|
DateTimeImmutable::RFC7231,
|
|
$lastModifiedString,
|
|
new DateTimeZone('GMT')
|
|
);
|
|
|
|
if (false === $date) {
|
|
throw new RuntimeException('the date from remote storage could not be parsed: '
|
|
. $lastModifiedString);
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
private function fillCache(StoredObject $document): void
|
|
{
|
|
try {
|
|
$response = $this
|
|
->client
|
|
->request(
|
|
Request::METHOD_GET,
|
|
$this
|
|
->tempUrlGenerator
|
|
->generate(
|
|
Request::METHOD_GET,
|
|
$document->getFilename()
|
|
)
|
|
->url
|
|
);
|
|
} catch (Throwable $e) {
|
|
throw StoredObjectManagerException::errorDuringHttpRequest($e);
|
|
}
|
|
|
|
if ($response->getStatusCode() !== Response::HTTP_OK) {
|
|
throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode());
|
|
}
|
|
|
|
$this->inMemory[$document->getUuid()->toString()] = $response;
|
|
}
|
|
|
|
private function getResponseFromCache(StoredObject $document): ResponseInterface
|
|
{
|
|
if (!$this->hasCache($document)) {
|
|
$this->fillCache($document);
|
|
}
|
|
|
|
return $this->inMemory[$document->getUuid()->toString()];
|
|
}
|
|
|
|
private function hasCache(StoredObject $document): bool
|
|
{
|
|
return array_key_exists($document->getUuid()->toString(), $this->inMemory);
|
|
}
|
|
|
|
private function hasKeysAndIv(StoredObject $storedObject): bool
|
|
{
|
|
return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv());
|
|
}
|
|
}
|