Skip to content
Snippets Groups Projects
Commit fbd0f8bf authored by Neuber, Eugen Ramon's avatar Neuber, Eugen Ramon :speech_balloon:
Browse files

Add integration test /WIP

parent 983eed7d
No related branches found
No related tags found
No related merge requests found
Showing with 583 additions and 72 deletions
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
"dbp/relay-core-bundle": "^0.1.56", "dbp/relay-core-bundle": "^0.1.56",
"fgrosse/phpasn1": "^2.0", "fgrosse/phpasn1": "^2.0",
"symfony/event-dispatcher": "^5.4", "symfony/event-dispatcher": "^5.4",
"symfony/framework-bundle": "^5.4",
"symfony/mailer": "^5.4", "symfony/mailer": "^5.4",
"symfony/mime": "^5.4",
"symfony/orm-pack": "^2.2", "symfony/orm-pack": "^2.2",
"web-token/jwt-checker": "^2.1", "web-token/jwt-checker": "^2.1",
"web-token/jwt-core": "^2.1", "web-token/jwt-core": "^2.1",
...@@ -72,5 +72,11 @@ ...@@ -72,5 +72,11 @@
"coverage": [ "coverage": [
"@php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html _coverage" "@php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html _coverage"
] ]
},
"repositories": [
{
"type": "vcs",
"url": "git@gitlab.tugraz.at:dbp/relay/dbp-relay-blob-connector-filesystem-bundle.git"
} }
]
} }
...@@ -9191,6 +9191,78 @@ ...@@ -9191,6 +9191,78 @@
], ],
"time": "2022-02-24T20:20:32+00:00" "time": "2022-02-24T20:20:32+00:00"
}, },
{
"name": "dbp/relay-blob-connector-filesystem-bundle",
"version": "dev-main",
"source": {
"type": "git",
"url": "git@gitlab.tugraz.at:dbp/relay/dbp-relay-blob-connector-filesystem-bundle.git",
"reference": "d948924db59ad807c0544f35aaa4984a7ef2a94b"
},
"require": {
"api-platform/core": "^2.6",
"dbp/relay-blob-bundle": "dev-main",
"dbp/relay-core-bundle": "^v0.1.56",
"ext-json": "*",
"php": ">=7.3",
"symfony/framework-bundle": "^5.4"
},
"require-dev": {
"ext-pdo_sqlite": "*",
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^1.0.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-symfony": "^1.2",
"phpunit/phpunit": "^9",
"symfony/browser-kit": "^5.4",
"symfony/http-client": "^5.4",
"symfony/monolog-bundle": "^3.7",
"symfony/phpunit-bridge": "^5.4",
"vimeo/psalm": "^4.2.1"
},
"default-branch": true,
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"Dbp\\Relay\\BlobConnectorFilesystemBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Dbp\\Relay\\BlobConnectorFilesystemBundle\\Tests\\": "tests/"
}
},
"scripts": {
"test": [
"@php vendor/bin/phpunit"
],
"phpstan": [
"@php vendor/bin/phpstan analyze --ansi"
],
"psalm": [
"@php vendor/bin/psalm"
],
"lint": [
"@composer run cs",
"@composer run phpstan",
"@composer run psalm"
],
"cs-fix": [
"@php vendor/bin/php-cs-fixer --ansi fix"
],
"cs": [
"@php vendor/bin/php-cs-fixer --ansi fix --dry-run --diff"
],
"coverage": [
"@php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html _coverage"
]
},
"license": [
"AGPL-3.0-or-later"
],
"description": "A template bundle for the Relay API gateway",
"time": "2022-12-19T00:08:14+00:00"
},
{ {
"name": "dnoegel/php-xdg-base-dir", "name": "dnoegel/php-xdg-base-dir",
"version": "v0.1.1", "version": "v0.1.1",
...@@ -12310,7 +12382,9 @@ ...@@ -12310,7 +12382,9 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {
"dbp/relay-blob-connector-filesystem-bundle": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
......
...@@ -23,6 +23,7 @@ final class CreateFileDataAction extends BaseBlobController ...@@ -23,6 +23,7 @@ final class CreateFileDataAction extends BaseBlobController
public function __construct(BlobService $blobService) public function __construct(BlobService $blobService)
{ {
$this->blobService = $blobService; $this->blobService = $blobService;
// dump('CreateFileDataAction::__construct()');
} }
/** /**
...@@ -31,13 +32,16 @@ final class CreateFileDataAction extends BaseBlobController ...@@ -31,13 +32,16 @@ final class CreateFileDataAction extends BaseBlobController
*/ */
public function __invoke(Request $request): FileData public function __invoke(Request $request): FileData
{ {
// dump('CreateFileDataAction::invoke()');
$sig = $request->headers->get('x-dbp-signature', ''); $sig = $request->headers->get('x-dbp-signature', '');
if (!$sig) { if (!$sig) {
throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig'); throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
} }
$bucketId = (string) $request->query->get('bucketID', ''); $bucketId = $request->query->get('bucketID', '');
$creationTime = (string) $request->query->get('creationTime', ''); assert(is_string($bucketId));
$creationTime = $request->query->get('creationTime', 0);
$prefix = $request->query->get('prefix', ''); $prefix = $request->query->get('prefix', '');
assert(is_string($prefix));
if (!$bucketId || !$creationTime) { if (!$bucketId || !$creationTime) {
throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:createFileData-unset-sig-params'); throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:createFileData-unset-sig-params');
...@@ -50,7 +54,7 @@ final class CreateFileDataAction extends BaseBlobController ...@@ -50,7 +54,7 @@ final class CreateFileDataAction extends BaseBlobController
$secret = $bucket->getPublicKey(); $secret = $bucket->getPublicKey();
$data = DenyAccessUnlessCheckSignature::verify($secret, $sig); $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
dump($data); // dump($data);
// check if signed params aer equal to request params // check if signed params aer equal to request params
if ($data['bucketID'] !== $bucketId) { if ($data['bucketID'] !== $bucketId) {
...@@ -74,6 +78,10 @@ final class CreateFileDataAction extends BaseBlobController ...@@ -74,6 +78,10 @@ final class CreateFileDataAction extends BaseBlobController
// Set exists until time // Set exists until time
$fileData->setExistsUntil($fileData->getDateCreated()->add(new \DateInterval($fileData->getRetentionDuration()))); $fileData->setExistsUntil($fileData->getDateCreated()->add(new \DateInterval($fileData->getRetentionDuration())));
// Set everything else...
$fileData->setFileName($data['fileName']);
$fileData->setNotifyEmail($data['notifyEmail'] ?? '');
$fileData->setAdditionalMetadata($data['additionalMetadata'] ?? '');
// Use given service for bucket // Use given service for bucket
if (!$bucket->getService()) { if (!$bucket->getService()) {
...@@ -82,7 +90,7 @@ final class CreateFileDataAction extends BaseBlobController ...@@ -82,7 +90,7 @@ final class CreateFileDataAction extends BaseBlobController
/** @var ?UploadedFile $uploadedFile */ /** @var ?UploadedFile $uploadedFile */
$uploadedFile = $fileData->getFile(); $uploadedFile = $fileData->getFile();
$fileData->setExtension($uploadedFile->guessExtension()); $fileData->setExtension($uploadedFile->guessExtension() ?? substr($fileData->getFileName(), -3, 3));
$hash = hash('sha256', $uploadedFile->getContent()); $hash = hash('sha256', $uploadedFile->getContent());
// check hash of file // check hash of file
......
...@@ -28,9 +28,12 @@ class DeleteFileDatasByPrefix extends BaseBlobController ...@@ -28,9 +28,12 @@ class DeleteFileDatasByPrefix extends BaseBlobController
if (!$sig) { if (!$sig) {
throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig'); throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
} }
$bucketId = $request->query->get('bucketID', ''); $bucketId = $request->query->get('bucketID', '');
$creationTime = $request->query->get('creationTime', ''); assert(is_string($bucketId));
$creationTime = $request->query->get('creationTime', 0);
$prefix = $request->query->get('prefix', ''); $prefix = $request->query->get('prefix', '');
assert(is_string($prefix));
if (!$bucketId || !$creationTime || !$prefix) { if (!$bucketId || !$creationTime || !$prefix) {
throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:delete-files-per-prefix-unset-sig-params'); throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature cannot checked', 'blob:delete-files-per-prefix-unset-sig-params');
...@@ -43,7 +46,7 @@ class DeleteFileDatasByPrefix extends BaseBlobController ...@@ -43,7 +46,7 @@ class DeleteFileDatasByPrefix extends BaseBlobController
$secret = $bucket->getPublicKey(); $secret = $bucket->getPublicKey();
$data = DenyAccessUnlessCheckSignature::verify($secret, $sig); $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
dump($data); // dump($data);
// check if signed params are equal to request params // check if signed params are equal to request params
if ($data['bucketID'] !== $bucketId) { if ($data['bucketID'] !== $bucketId) {
......
...@@ -28,6 +28,11 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa ...@@ -28,6 +28,11 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa
return $data instanceof FileData; return $data instanceof FileData;
} }
/**
* @param Filedata $data
*
* @return FileData|void
*/
public function persist($data, array $context = []) public function persist($data, array $context = [])
{ {
// no need to check, because signature is checked by getting the data // no need to check, because signature is checked by getting the data
...@@ -55,8 +60,6 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa ...@@ -55,8 +60,6 @@ class FileDataDataPersister extends AbstractController implements ContextAwareDa
/** /**
* @param mixed $data * @param mixed $data
* @param array $context
* @return void
*/ */
public function remove($data, array $context = []): void public function remove($data, array $context = []): void
{ {
......
...@@ -58,6 +58,7 @@ class FileDataDataProvider extends AbstractDataProvider ...@@ -58,6 +58,7 @@ class FileDataDataProvider extends AbstractDataProvider
throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig'); throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
} }
$bucketId = $filters['bucketID'] ?? ''; $bucketId = $filters['bucketID'] ?? '';
assert(is_string($bucketId));
if (!$bucketId) { if (!$bucketId) {
throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID'); throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID');
} }
...@@ -94,6 +95,7 @@ class FileDataDataProvider extends AbstractDataProvider ...@@ -94,6 +95,7 @@ class FileDataDataProvider extends AbstractDataProvider
throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig'); throw ApiError::withDetails(Response::HTTP_UNAUTHORIZED, 'Signature missing', 'blob:createFileData-missing-sig');
} }
$bucketId = $filters['bucketID'] ?? ''; $bucketId = $filters['bucketID'] ?? '';
assert(is_string($bucketId));
if (!$bucketId) { if (!$bucketId) {
throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID'); throw ApiError::withDetails(Response::HTTP_BAD_REQUEST, 'BucketID is missing', 'blob:get-files-by-prefix-missing-bucketID');
} }
...@@ -125,10 +127,8 @@ class FileDataDataProvider extends AbstractDataProvider ...@@ -125,10 +127,8 @@ class FileDataDataProvider extends AbstractDataProvider
} }
/** /**
* Check dbp-signature on GET request * Check dbp-signature on GET request.
* *
* @param string $secret
* @param array $filters
* @throws \JsonException * @throws \JsonException
*/ */
private function checkSignature(string $secret, array $filters): void private function checkSignature(string $secret, array $filters): void
...@@ -145,7 +145,7 @@ class FileDataDataProvider extends AbstractDataProvider ...@@ -145,7 +145,7 @@ class FileDataDataProvider extends AbstractDataProvider
} }
$data = DenyAccessUnlessCheckSignature::verify($secret, $sig); $data = DenyAccessUnlessCheckSignature::verify($secret, $sig);
dump($data); // dump($data);
// check if signed params aer equal to request params // check if signed params aer equal to request params
if ($data['bucketID'] !== $bucketId) { if ($data['bucketID'] !== $bucketId) {
...@@ -157,6 +157,5 @@ class FileDataDataProvider extends AbstractDataProvider ...@@ -157,6 +157,5 @@ class FileDataDataProvider extends AbstractDataProvider
throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Creation Time change forbidden', 'blob:creationtime-change-forbidden'); throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Creation Time change forbidden', 'blob:creationtime-change-forbidden');
} }
// TODO check if request is NOT too old // TODO check if request is NOT too old
} }
} }
...@@ -18,8 +18,8 @@ class Configuration implements ConfigurationInterface ...@@ -18,8 +18,8 @@ class Configuration implements ConfigurationInterface
->children() ->children()
->scalarNode('database_url') ->scalarNode('database_url')
->isRequired() ->isRequired()
->cannotBeEmpty() //->cannotBeEmpty()
->defaultValue('%env(resolve:DATABASE_URL)%') ->defaultValue('%env(resolve:BLOB_DATABASE_NAME)%')
->end() ->end()
->arrayNode('buckets') ->arrayNode('buckets')
->isRequired() ->isRequired()
......
...@@ -50,7 +50,7 @@ class DbpRelayBlobExtension extends ConfigurableExtension implements PrependExte ...@@ -50,7 +50,7 @@ class DbpRelayBlobExtension extends ConfigurableExtension implements PrependExte
'dbal' => [ 'dbal' => [
'connections' => [ 'connections' => [
'dbp_relay_blob_bundle' => [ 'dbp_relay_blob_bundle' => [
'url' => $config['database_url'] ?? '', 'url' => $config['database_url'] ?? 'sqlite:///var/dbp_relay_blob_test.db',
], ],
], ],
], ],
......
...@@ -18,10 +18,11 @@ use Symfony\Component\HttpFoundation\Response; ...@@ -18,10 +18,11 @@ use Symfony\Component\HttpFoundation\Response;
class DenyAccessUnlessCheckSignature class DenyAccessUnlessCheckSignature
{ {
/** /**
* Create a JWS token * Create a JWS token.
* *
* @param string $secret to create the (symmetric) JWK from * @param string $secret to create the (symmetric) JWK from
* @param array $payload to create the token from * @param array $payload to create the token from
*
* @throws \JsonException * @throws \JsonException
*/ */
public static function create(string $secret, array $payload): string public static function create(string $secret, array $payload): string
...@@ -32,11 +33,13 @@ class DenyAccessUnlessCheckSignature ...@@ -32,11 +33,13 @@ class DenyAccessUnlessCheckSignature
} }
/** /**
* Verify a JWS token * Verify a JWS token.
* *
* @param string $secret to create the (symmetric) JWK from * @param string $secret to create the (symmetric) JWK from
* @param string $token to verify * @param string $token to verify
*
* @return array extracted payload from token * @return array extracted payload from token
*
* @throws \JsonException * @throws \JsonException
* @throws ApiError * @throws ApiError
*/ */
...@@ -46,6 +49,7 @@ class DenyAccessUnlessCheckSignature ...@@ -46,6 +49,7 @@ class DenyAccessUnlessCheckSignature
$payload = []; $payload = [];
if (!self::verifyToken($jwk, $token, $payload)) { if (!self::verifyToken($jwk, $token, $payload)) {
dump(['token' => $token, 'payload' => $payload, 'secret' => $secret]);
throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature invalid', 'blob:signature-invalid'); throw ApiError::withDetails(Response::HTTP_FORBIDDEN, 'Signature invalid', 'blob:signature-invalid');
} }
...@@ -56,7 +60,6 @@ class DenyAccessUnlessCheckSignature ...@@ -56,7 +60,6 @@ class DenyAccessUnlessCheckSignature
* Create the JWK from a shared secret. * Create the JWK from a shared secret.
* *
* @param string $secret to create the (symmetric) JWK from * @param string $secret to create the (symmetric) JWK from
* @return JWK
*/ */
public static function createJWK(string $secret): JWK public static function createJWK(string $secret): JWK
{ {
...@@ -74,7 +77,9 @@ class DenyAccessUnlessCheckSignature ...@@ -74,7 +77,9 @@ class DenyAccessUnlessCheckSignature
* *
* @param JWK $jwk json web key * @param JWK $jwk json web key
* @param array $payload as json string to secure * @param array $payload as json string to secure
*
* @return string secure token * @return string secure token
*
* @throws \JsonException * @throws \JsonException
*/ */
public static function generateToken(JWK $jwk, array $payload): string public static function generateToken(JWK $jwk, array $payload): string
...@@ -94,10 +99,10 @@ class DenyAccessUnlessCheckSignature ...@@ -94,10 +99,10 @@ class DenyAccessUnlessCheckSignature
/** /**
* Verify a JWS token. * Verify a JWS token.
* *
* @param JWK $jwk
* @param string $token the JWS token as string * @param string $token the JWS token as string
* @param array $payload to extract from token on success * @param array $payload to extract from token on success
* @return bool * @return bool
*
* @throws \JsonException * @throws \JsonException
*/ */
public static function verifyToken(JWK $jwk, string $token, array &$payload): bool public static function verifyToken(JWK $jwk, string $token, array &$payload): bool
...@@ -110,6 +115,9 @@ class DenyAccessUnlessCheckSignature ...@@ -110,6 +115,9 @@ class DenyAccessUnlessCheckSignature
if ($ok = $jwsVerifier->verifyWithKey($jws, $jwk, 0)) { if ($ok = $jwsVerifier->verifyWithKey($jws, $jwk, 0)) {
$payload = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR); $payload = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR);
} }
// $ok = $jwsVerifier->verifyWithKey($jws, $jwk, 0);
// $payload = json_decode($jws->getPayload(), true, 512, JSON_THROW_ON_ERROR);
return $ok; return $ok;
} }
} }
...@@ -2,6 +2,8 @@ services: ...@@ -2,6 +2,8 @@ services:
Dbp\Relay\BlobBundle\Service\BlobService: Dbp\Relay\BlobBundle\Service\BlobService:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
arguments:
$em: '@doctrine.orm.dbp_relay_blob_bundle_entity_manager'
Dbp\Relay\BlobBundle\Controller\: Dbp\Relay\BlobBundle\Controller\:
tags: [ 'controller.service_arguments' ] tags: [ 'controller.service_arguments' ]
......
services:
Dbp\Relay\BlobConnectorFilesystemBundle\Cron\CleanupCronJob:
autowire: true
autoconfigure: true
Dbp\Relay\BlobConnectorFilesystemBundle\Service\FilesystemService:
autowire: true
autoconfigure: true
public: true
Dbp\Relay\BlobConnectorFilesystemBundle\Service\ShareLinkPersistenceService:
autowire: true
autoconfigure: true
public: true
Dbp\Relay\BlobConnectorFilesystemBundle\Service\ConfigurationService:
autowire: true
autoconfigure: true
Dbp\Relay\BlobConnectorFilesystemBundle\Controller\:
tags: [ 'controller.service_arguments' ]
resource: '../../../../relay-blob-connector-filesystem-bundle/src/Controller/'
autowire: true
autoconfigure: true
Dbp\Relay\BlobBundle\Tests\DummyFileSystemService:
autowire: true
autoconfigure: true
public: true
...@@ -7,8 +7,8 @@ namespace Dbp\Relay\BlobBundle\Service; ...@@ -7,8 +7,8 @@ namespace Dbp\Relay\BlobBundle\Service;
use Dbp\Relay\BlobBundle\Entity\Bucket; use Dbp\Relay\BlobBundle\Entity\Bucket;
use Dbp\Relay\BlobBundle\Entity\FileData; use Dbp\Relay\BlobBundle\Entity\FileData;
use Dbp\Relay\CoreBundle\Exception\ApiError; use Dbp\Relay\CoreBundle\Exception\ApiError;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\TextUI\XmlConfiguration\File; use PHPUnit\TextUI\XmlConfiguration\File;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
...@@ -39,16 +39,18 @@ class BlobService ...@@ -39,16 +39,18 @@ class BlobService
*/ */
private $datasystemService; private $datasystemService;
public function __construct(ManagerRegistry $managerRegistry, ConfigurationService $configurationService, DatasystemProviderService $datasystemService) public function __construct(EntityManagerInterface $em, ConfigurationService $configurationService, DatasystemProviderService $datasystemService)
{ {
$manager = $managerRegistry->getManager('dbp_relay_blob_bundle'); $this->em = $em;
assert($manager instanceof EntityManagerInterface);
$this->em = $manager;
$this->configurationService = $configurationService; $this->configurationService = $configurationService;
$this->datasystemService = $datasystemService; $this->datasystemService = $datasystemService;
} }
public function setDatasystemService(DatasystemProviderService $datasystemService): void
{
$this->datasystemService = $datasystemService;
}
public function checkConnection() public function checkConnection()
{ {
$this->em->getConnection()->connect(); $this->em->getConnection()->connect();
...@@ -126,6 +128,7 @@ class BlobService ...@@ -126,6 +128,7 @@ class BlobService
$fileData->setLastAccess($time); $fileData->setLastAccess($time);
try { try {
// dump($fileData);
$this->em->persist($fileData); $this->em->persist($fileData);
$this->em->flush(); $this->em->flush();
} catch (\Exception $e) { } catch (\Exception $e) {
...@@ -350,7 +353,7 @@ class BlobService ...@@ -350,7 +353,7 @@ class BlobService
private function sendEmail(array $config, array $context) private function sendEmail(array $config, array $context)
{ {
$loader = new FilesystemLoader(dirname(__FILE__).'/../Resources/views/'); $loader = new FilesystemLoader(__DIR__ . '/../Resources/views/');
$twig = new Environment($loader); $twig = new Environment($loader);
$template = $twig->load($config['html_template']); $template = $twig->load($config['html_template']);
......
<?php <?php declare(strict_types=1);
declare(strict_types=1);
namespace Dbp\Relay\BlobBundle\Tests; namespace Dbp\Relay\BlobBundle\Tests;
use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use Dbp\Relay\BlobBundle\Controller\CreateFileDataAction;
use Dbp\Relay\BlobBundle\Controller\DeleteFileDatasByPrefix;
use Dbp\Relay\BlobBundle\Entity\FileData;
use Dbp\Relay\BlobBundle\Helper\DenyAccessUnlessCheckSignature; use Dbp\Relay\BlobBundle\Helper\DenyAccessUnlessCheckSignature;
use Dbp\Relay\BlobBundle\Helper\PoliciesStruct;
use Dbp\Relay\BlobBundle\Service\BlobService;
use Dbp\Relay\BlobBundle\Service\ConfigurationService;
use Dbp\Relay\BlobBundle\Service\DatasystemProviderServiceInterface;
use Dbp\Relay\CoreBundle\TestUtils\UserAuthTrait;
use Doctrine\ORM\Tools\SchemaTool;
use Exception; use Exception;
use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use function uuid_is_valid;
class DummyFileSystemService implements DatasystemProviderServiceInterface
{
public function saveFile(FileData &$fileData): ?FileData
{
return $fileData;
}
public function renameFile(FileData &$fileData): ?FileData
{
return $fileData;
}
public function getLink(FileData &$fileData, PoliciesStruct $policiesStruct): ?FileData
{
return $fileData;
}
public function removeFile(FileData &$fileData): bool
{
return true;
}
}
class CurlGetTest extends ApiTestCase
{
use UserAuthTrait;
/** @var \Doctrine\ORM\EntityManagerInterface $entityManager */
private $entityManager;
class CurlGetTest extends TestCase /**
* @throws Exception
*/
protected function setUp(): void
{ {
/** @var KernelInterface $kernel */
$kernel = self::bootKernel();
if ('test' !== $kernel->getEnvironment()) {
throw new \RuntimeException('Execution only in Test environment possible!');
}
$this->entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');
$metaData = $this->entityManager->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool($this->entityManager);
$schemaTool->updateSchema($metaData);
}
public function testGet(): void public function testGet(): void
{ {
try { try {
$secret = '08d848fd868d83646778b87dd0695b10f59c78e23b286e9884504d1bb43cce93'; $client = $this->withUser('foobar');
$bucketId = '1234'; $configService = $client->getContainer()->get(ConfigurationService::class);
$bucket = $configService->getBuckets()[0];
$secret = $bucket->getPublicKey();
$bucketId = $bucket->getIdentifier();
$creationTime = date('U'); $creationTime = date('U');
$prefix = 'playground'; $prefix = 'playground';
$payload = [ $payload = [
...@@ -23,32 +85,304 @@ class CurlGetTest extends TestCase ...@@ -23,32 +85,304 @@ class CurlGetTest extends TestCase
'prefix' => $prefix, 'prefix' => $prefix,
]; ];
$uri = "http://127.0.0.1:8000/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime"; $token = DenyAccessUnlessCheckSignature::create($secret, $payload);
$url = "/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime";
$options = [
'headers' => [
'HTTP_ACCEPT' => 'application/ld+json',
'x-dbp-signature' => $token,
],
];
/** @noinspection PhpInternalEntityUsedInspection */
$client->getKernelBrowser()->followRedirects();
/** @var Response $response */
$response = $client->request('GET', $url, $options);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayHasKey('hydra:view', $data);
$this->assertArrayHasKey('hydra:member', $data);
$this->assertCount(0, $data['hydra:member'], 'More files than expected');
} catch (\Throwable $e) {
echo $e->getTraceAsString() . "\n";
$this->fail($e->getMessage());
}
}
/**
* Integration test for a full life cycle: create, use and destroy a blob
* - create blob no 1
* - get all blobs: blob no 1 is available
* - create blob no 2
* - get all blobs: two blobs are available
* - delete all blobs for the prefix: no entries in database
* - get all blobs: no blobs available
*
* @return void
*/
public function testPostGetDelete(): void
{
try {
$files = [
0 => [
'name' => $n = 'Test.php',
'path' => $p = __DIR__ . '/' . $n,
'content' => $c = file_get_contents($p),
'hash' => hash('sha256', $c),
'size' => strlen($c),
'mime' => 'application/x-php',
'retention' => 'P1W',
],
1 => [
'name' => $n = 'Kernel.php',
'path' => $p = __DIR__ . '/' . $n,
'content' => $c = file_get_contents($p),
'hash' => hash('sha256', $c),
'size' => strlen($c),
'mime' => 'application/x-php',
'retention' => 'P1M',
],
];
$client = $this->withUser('foobar');
$blobService = $client->getContainer()->get(BlobService::class);
$configService = $client->getContainer()->get(ConfigurationService::class);
$bucket = $configService->getBuckets()[0];
$secret = $bucket->getPublicKey();
$bucketId = $bucket->getIdentifier();
$creationTime = date('U');
$prefix = 'playground';
$notifyEmail = 'eugen.neuber@tugraz.at';
$url = "/blob/files/?bucketID=$bucketId&prefix=$prefix&creationTime=$creationTime";
// =======================================================
// POST a file
// =======================================================
$payload = [
'bucketID' => $bucketId,
'creationTime' => $creationTime,
'prefix' => $prefix,
'fileName' => $files[0]['name'],
'fileHash' => $files[0]['hash'],
'notifyEmail' => $notifyEmail,
'retentionDuration' => $files[0]['retention'],
'additionalMetadata' => '',
];
$token = DenyAccessUnlessCheckSignature::create($secret, $payload);
$requestPost = Request::create($url, 'POST', [], [],
[
'file' => new UploadedFile($files[0]['path'], $files[0]['name'], $files[0]['mime'])
],
[
'HTTP_ACCEPT' => 'application/ld+json',
// 'x-dbp-signature' => $token,
'HTTP_X_DBP_SIGNATURE' => $token,
],
"HTTP_ACCEPT: application/ld+json\r\n"
. "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
. "file=" . base64_encode($files[0]['content'])
. "&fileName={$files[0]['name']}&prefix=$prefix&bucketID=$bucketId"
);
$c = new CreateFileDataAction($blobService);
try {
$fileData = $c->__invoke($requestPost);
} catch (\Throwable $e) {
echo $e->getTraceAsString() . "\n";
$this->fail($e->getMessage());
}
$this->assertNotNull($fileData);
$this->assertEquals($prefix, $fileData->getPrefix(), 'File data prefix not correct.');
$this->assertObjectHasAttribute('identifier', $fileData, 'File data has no identifier.');
$this->assertTrue(uuid_is_valid($fileData->getIdentifier()), 'File data identifier is not a valid UUID.');
$this->assertEquals($files[0]['name'], $fileData->getFileName(), 'File name not correct.');
$files[0]['uuid'] = $fileData->getIdentifier();
$files[0]['created'] = $fileData->getDateCreated();
$files[0]['until'] = $fileData->getExistsUntil();
$this->assertEquals(
$files[0]['created']->format('c'),
date('c', (int)$creationTime),
'File creation time not correct.'
);
// =======================================================
// GET a file
// =======================================================
$options = [
'headers' => [
'HTTP_ACCEPT' => 'application/ld+json',
'x-dbp-signature' => $token,
],
];
/** @noinspection PhpInternalEntityUsedInspection */
$client->getKernelBrowser()->followRedirects();
$response = $client->request('GET', $url, $options);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayHasKey('hydra:view', $data);
$this->assertArrayHasKey('hydra:member', $data);
$this->assertArrayHasKey(0, $data['hydra:member']);
$resultFile = $data['hydra:member'][0];
$this->assertEquals($prefix, $resultFile['prefix'], 'File data prefix not correct.');
$this->assertEquals($files[0]['name'], $resultFile['fileName'], 'File name not correct.');
$this->assertEquals($files[0]['size'], $resultFile['fileSize'], 'File size not correct.');
$this->assertEquals($files[0]['uuid'], $resultFile['identifier'], 'File identifier not correct.');
$this->assertEquals($notifyEmail, $resultFile['notifyEmail'], 'File data notify email not correct.');
$this->assertCount(1, $data['hydra:member'], 'More files than expected');
// dump($data);
// =======================================================
// POST another file
// =======================================================
$payload = [
'bucketID' => $bucketId,
'creationTime' => $creationTime,
'prefix' => $prefix,
'fileName' => $files[1]['name'],
'fileHash' => $files[1]['hash'],
'notifyEmail' => $notifyEmail,
'retentionDuration' => $files[1]['retention'],
'additionalMetadata' => '',
];
$token = DenyAccessUnlessCheckSignature::create($secret, $payload); $token = DenyAccessUnlessCheckSignature::create($secret, $payload);
// echo "Signatur: $token\n"; $requestPost = Request::create($url, 'POST', [], [],
[
'file' => new UploadedFile($files[1]['path'], $files[1]['name'], $files[1]['mime'])
],
[
'HTTP_ACCEPT' => 'application/ld+json',
// 'x-dbp-signature' => $token,
'HTTP_X_DBP_SIGNATURE' => $token,
],
"HTTP_ACCEPT: application/ld+json\r\n"
. "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
. "file=" . base64_encode($files[1]['content'])
. "&fileName={$files[1]['name']}&prefix=$prefix&bucketID=$bucketId"
);
$c = new CreateFileDataAction($blobService);
try {
$fileData = $c->__invoke($requestPost);
} catch (\Throwable $e) {
echo $e->getTraceAsString() . "\n";
$this->fail($e->getMessage());
}
$this->assertNotNull($fileData);
$this->assertEquals($prefix, $fileData->getPrefix(), 'File data prefix not correct.');
$this->assertObjectHasAttribute('identifier', $fileData, 'File data has no identifier.');
$this->assertTrue(uuid_is_valid($fileData->getIdentifier()), 'File data identifier is not a valid UUID.');
$this->assertEquals($files[1]['name'], $fileData->getFileName(), 'File name not correct.');
$files[1]['uuid'] = $fileData->getIdentifier();
$files[1]['created'] = $fileData->getDateCreated();
$files[1]['until'] = $fileData->getExistsUntil();
dump($fileData);
$header = [ // =======================================================
'x-dbp-signature: '.$token, // GET all files
// =======================================================
$options = [
'headers' => [
'HTTP_ACCEPT' => 'application/ld+json',
'x-dbp-signature' => $token,
],
]; ];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri); /** @noinspection PhpInternalEntityUsedInspection */
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); $client->getKernelBrowser()->followRedirects();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); $response = $client->request('GET', $url, $options);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_NOBODY, 0); $this->assertEquals(200, $response->getStatusCode());
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$result = curl_exec($ch); $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
$info = curl_getinfo($ch);
curl_close($ch);
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
// print_r($result);
$this->assertEquals(200, $info['http_code']);
$this->assertArrayHasKey('hydra:view', $data); $this->assertArrayHasKey('hydra:view', $data);
} catch (Exception $e) { $this->assertArrayHasKey('hydra:member', $data);
$this->assertCount(2, $data['hydra:member'], 'More files than expected');
foreach ($data['hydra:member'] as $resultFile) {
$found = false;
foreach ($files as $file) {
if ($file['uuid'] === $resultFile['identifier']) {
$found = true;
$this->assertEquals($prefix, $resultFile['prefix'], 'File prefix not correct.');
$this->assertEquals($file['name'], $resultFile['fileName'], 'File name not correct.');
$this->assertEquals($file['size'], $resultFile['fileSize'], 'File size not correct.');
$this->assertEquals(
$file['created']->format('c'),
date('c', (int)$creationTime),
'File creation time not correct.'
);
$until = $file['created']->add(new \DateInterval($file['retention']));
dump([$until->format('c'), $resultFile['existsUntil']]);
$this->assertEquals(
$until->format('c'),
$resultFile['existsUntil'],
'File retention time not correct.'
);
break;
}
}
$this->assertTrue($found, 'Uploaded file not found.');
}
// dump($data['hydra:member']);
// dump($data);
// =======================================================
// DELETE all files
// =======================================================
$requestDelete = Request::create($url, 'DELETE', [], [], [],
[
'HTTP_ACCEPT' => 'application/ld+json',
'HTTP_X_DBP_SIGNATURE' => $token,
],
"HTTP_ACCEPT: application/ld+json\r\n"
. "HTTP_X_DBP_SIGNATURE: $token\r\n\r\n"
);
$d = new DeleteFileDatasByPrefix($blobService);
try {
$d->__invoke($requestDelete);
} catch (\Throwable $e) {
echo $e->getTraceAsString() . "\n";
$this->fail($e->getMessage());
}
$query = $this->entityManager->getConnection()->createQueryBuilder();
$files = $query->select('*')
->from('blob_files')
->where("prefix = '$prefix' AND bucket_id = '$bucketId'")
->fetchAllAssociativeIndexed();
$this->assertEmpty($files, 'Files not deleted');
// =======================================================
// GET all files
// =======================================================
/** @var Response $response */
$response = $client->request('GET', $url, $options);
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayHasKey('hydra:view', $data);
$this->assertArrayHasKey('hydra:member', $data);
$this->assertCount(0, $data['hydra:member'], 'More files than expected');
} catch (\Throwable $e) {
echo $e->getTraceAsString() . "\n";
$this->fail($e->getMessage()); $this->fail($e->getMessage());
} }
} }
......
...@@ -7,6 +7,8 @@ namespace Dbp\Relay\BlobBundle\Tests; ...@@ -7,6 +7,8 @@ namespace Dbp\Relay\BlobBundle\Tests;
use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle; use ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle;
use Dbp\Relay\BlobBundle\DbpRelayBlobBundle; use Dbp\Relay\BlobBundle\DbpRelayBlobBundle;
use Dbp\Relay\CoreBundle\DbpRelayCoreBundle; use Dbp\Relay\CoreBundle\DbpRelayCoreBundle;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use Nelmio\CorsBundle\NelmioCorsBundle; use Nelmio\CorsBundle\NelmioCorsBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
...@@ -29,6 +31,8 @@ class Kernel extends BaseKernel ...@@ -29,6 +31,8 @@ class Kernel extends BaseKernel
yield new TwigBundle(); yield new TwigBundle();
yield new NelmioCorsBundle(); yield new NelmioCorsBundle();
yield new MonologBundle(); yield new MonologBundle();
yield new DoctrineBundle();
yield new DoctrineMigrationsBundle();
yield new ApiPlatformBundle(); yield new ApiPlatformBundle();
yield new DbpRelayBlobBundle(); yield new DbpRelayBlobBundle();
yield new DbpRelayCoreBundle(); yield new DbpRelayCoreBundle();
...@@ -42,11 +46,49 @@ class Kernel extends BaseKernel ...@@ -42,11 +46,49 @@ class Kernel extends BaseKernel
protected function configureContainer(ContainerConfigurator $container, LoaderInterface $loader) protected function configureContainer(ContainerConfigurator $container, LoaderInterface $loader)
{ {
$container->import('@DbpRelayCoreBundle/Resources/config/services_test.yaml'); $container->import('@DbpRelayCoreBundle/Resources/config/services_test.yaml');
$container->import('@DbpRelayBlobBundle/Resources/config/services_test.yaml');
$container->extension('framework', [ $container->extension('framework', [
'test' => true, 'test' => true,
'secret' => '', 'secret' => '',
]); ]);
$container->extension('dbp_relay_blob', []); $container->extension('dbp_relay_blob', [
'database_url' => 'sqlite:///var/dbp_relay_blob_test.db',
'buckets' => [
'test_bucket' => [
'service' => 'Dbp\Relay\BlobBundle\Tests\DummyFileSystemService',
'bucket_id' => '1234',
'bucket_name' => 'Test bucket',
'public_key' => '08d848fd868d83646778b87dd0695b10f59c78e23b286e9884504d1bb43cce93',
'path' => 'testpath',
'quota' => 500, // in MB
'bucket_owner' => 'tamara.steiwnender@tugraz.at',
'max_retention_duration' => 'P1Y',
'link_expire_time' => 'P7D',
'policies' => [
'create' => true,
'delete' => true,
'open' => true,
'download' => true,
'rename' => true,
'work' => true,
],
'notify_quota' => [
'dsn' => 'smtp:localhost',
'from' => 'noreply@tugraz.at',
'to' => 'tamara.steinwender@tugraz.at',
'subject' => 'Blob notify quota',
'html_template' => 'emails/notify-quota.html.twig',
],
'reporting' => [
'dsn' => 'smtp:localhost',
'from' => 'noreply@tugraz.at',
'to' => 'tamara.steinwender@tugraz.at',
'subject' => 'Blob file deletion reporting',
'html_template' => 'emails/reporting.html.twig',
],
],
],
]);
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment