Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller\Api;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
|
||||
class AssetApiControllerFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testCreateNewRemoteAsset(): void
|
||||
{
|
||||
$payload = [
|
||||
'file' => 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
'storageLocation' => 'remote',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(201, $clientResponse->getContent());
|
||||
$response = json_decode($clientResponse->getContent(), true);
|
||||
$this->assertEquals($payload['title'], $response['asset']['title']);
|
||||
$this->assertEquals($payload['storageLocation'], $response['asset']['storageLocation']);
|
||||
$this->assertStringContainsString('application/pdf', $response['asset']['mime']);
|
||||
$this->assertStringContainsString('pdf', $response['asset']['extension']);
|
||||
$this->assertNotNull($response['asset']['size']);
|
||||
}
|
||||
|
||||
public function testCreateNewRemoteAssetWithVulnerableFile(): void
|
||||
{
|
||||
$payload = [
|
||||
'file' => 'file:///etc/passwd',
|
||||
'storageLocation' => 'remote',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(400, $clientResponse->getContent());
|
||||
$this->assertEquals('{"errors":[{"code":400,"message":"remotePath: The remote should be a valid URL.","details":{"remotePath":["The remote should be a valid URL."]}}]}', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testCreateNewLocalAsset(): void
|
||||
{
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
file_put_contents($assetsPath.'/file.txt', 'test');
|
||||
|
||||
$payload = [
|
||||
'file' => 'file.txt',
|
||||
'storageLocation' => 'local',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(201, $clientResponse->getContent());
|
||||
$response = json_decode($clientResponse->getContent(), true);
|
||||
$this->assertEquals($payload['title'], $response['asset']['title']);
|
||||
$this->assertEquals($payload['storageLocation'], $response['asset']['storageLocation']);
|
||||
$this->assertStringContainsString('text/plain', $response['asset']['mime']);
|
||||
$this->assertNotNull($response['asset']['size']);
|
||||
$this->assertStringContainsString('txt', $response['asset']['extension']);
|
||||
unlink($assetsPath.'/file.txt');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
use Mautic\CoreBundle\Tests\Traits\ControllerTrait;
|
||||
use Mautic\PageBundle\Tests\Controller\PageControllerTest;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use Mautic\UserBundle\Entity\Permission;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use Mautic\UserBundle\Model\RoleModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AssetControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
use ControllerTrait;
|
||||
|
||||
private const SALES_USER = 'sales';
|
||||
private const ADMIN_USER = 'admin';
|
||||
|
||||
/**
|
||||
* Index action should return status code 200.
|
||||
*/
|
||||
public function testIndexAction(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('test');
|
||||
$asset->setAlias('test');
|
||||
$asset->setDateAdded(new \DateTime('2020-02-07 20:29:02'));
|
||||
$asset->setDateModified(new \DateTime('2020-03-21 20:29:02'));
|
||||
$asset->setCreatedByUser('Test User');
|
||||
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->detach($asset);
|
||||
|
||||
$urlAlias = 'assets';
|
||||
$routeAlias = 'asset';
|
||||
$column = 'dateModified';
|
||||
$column2 = 'title';
|
||||
$tableAlias = 'a.';
|
||||
|
||||
$this->getControllerColumnTests($urlAlias, $routeAlias, $column, $tableAlias, $column2);
|
||||
}
|
||||
|
||||
public function testAssetSizes(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/ajax?action=email:getAttachmentsSize&assets%5B%5D='.$this->asset->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
Assert::assertSame('{"size":"178 bytes"}', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the file content.
|
||||
*/
|
||||
public function testPreviewActionStreamByDefault(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId());
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the file content.
|
||||
*/
|
||||
public function testPreviewActionStreamIsZero(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId().'?stream=0&download=1');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
$this->assertSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the html code.
|
||||
*/
|
||||
public function testPreviewActionStreamDownloadAreZero(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId().'?stream=0&download=0');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode(), $content);
|
||||
$this->assertNotEquals($this->expectedPngContent, $content);
|
||||
PageControllerTest::assertTrue($response->isOk());
|
||||
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
PageControllerTest::assertStringContainsString(
|
||||
'/asset/'.$assetSlug,
|
||||
$content,
|
||||
'The return must contain the assert slug'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string[]> $permission
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('getValuesProvider')]
|
||||
public function testEditWithPermissions(string $route, array $permission, int $expectedStatusCode, string $userCreatorUN): void
|
||||
{
|
||||
$userCreator = $this->getUser($userCreatorUN);
|
||||
$userEditor = $this->getUser(self::SALES_USER);
|
||||
$this->setPermission($userEditor, ['asset:assets' => $permission]);
|
||||
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('Asset A');
|
||||
$asset->setAlias('asset-a');
|
||||
$asset->setStorageLocation('local');
|
||||
$asset->setPath('broken-image.jpg');
|
||||
$asset->setExtension('jpg');
|
||||
$asset->setCreatedByUser($userCreator->getUserIdentifier());
|
||||
$asset->setCreatedBy($userCreator->getId());
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->logoutUser();
|
||||
|
||||
$this->loginUser($userEditor);
|
||||
|
||||
$this->client->request(Request::METHOD_GET, "/s/assets/{$route}/{$asset->getId()}");
|
||||
|
||||
Assert::assertSame($expectedStatusCode, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, mixed[]>
|
||||
*/
|
||||
public static function getValuesProvider(): \Generator
|
||||
{
|
||||
yield 'The sales user with edit own permission can edits its own asset' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::SALES_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with edit own permission cannot edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with edit other permission can edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown', 'editother'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission cannot edit or asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view other permission cannot edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['viewown', 'viewother'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission cannot view asset created by admin' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view others permission can view asset created by admin' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown', 'viewother'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission can view its own asset' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::SALES_USER,
|
||||
];
|
||||
}
|
||||
|
||||
public function testAssetUploadPathTraversal(): void
|
||||
{
|
||||
$client = $this->client;
|
||||
$container = $this->getContainer();
|
||||
|
||||
// Get CSRF token
|
||||
$csrfToken = $container->get('security.csrf.token_manager')->getToken('mautic_ajax_post')->getValue();
|
||||
|
||||
// Create a temporary file
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'test_');
|
||||
file_put_contents($tempFile, '111');
|
||||
|
||||
// Prepare the file for upload
|
||||
$uploadedFile = new UploadedFile(
|
||||
$tempFile,
|
||||
'test.txt',
|
||||
'text/plain',
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
$tmpDir = 'tmp_'.substr(md5(uniqid()), 0, 13);
|
||||
$client->request(
|
||||
'POST',
|
||||
'/s/_uploader/asset/upload',
|
||||
['tempId' => '../../'.$tmpDir],
|
||||
['file' => $uploadedFile],
|
||||
[
|
||||
'HTTP_X-Requested-With' => 'XMLHttpRequest',
|
||||
'HTTP_X-CSRF-Token' => $csrfToken,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->getResponse();
|
||||
|
||||
// Assert response is successful
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
|
||||
// Decode JSON response
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
|
||||
// Assert the response contains expected keys
|
||||
$this->assertArrayHasKey('tmpFileName', $responseData);
|
||||
|
||||
// Assert file was created in the correct directory
|
||||
$expectedDir = $container->getParameter('mautic.upload_dir').join('/', ['', 'tmp', $tmpDir]);
|
||||
$expectedFilePath = join('/', [$expectedDir, $responseData['tmpFileName']]);
|
||||
$this->assertFileExists($expectedFilePath);
|
||||
|
||||
// Clean up
|
||||
if (file_exists($expectedFilePath)) {
|
||||
unlink($expectedFilePath);
|
||||
}
|
||||
if (is_dir($expectedDir)) {
|
||||
rmdir($expectedDir);
|
||||
}
|
||||
if (file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
private function getUser(string $username): User
|
||||
{
|
||||
$repository = $this->em->getRepository(User::class);
|
||||
|
||||
return $repository->findOneBy(['username' => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array<string>>> $permissions
|
||||
*/
|
||||
private function setPermission(User $user, array $permissions): void
|
||||
{
|
||||
$role = $user->getRole();
|
||||
|
||||
// Delete previous permissions
|
||||
$this->em->createQueryBuilder()
|
||||
->delete(Permission::class, 'p')
|
||||
->where('p.bundle = :bundle')
|
||||
->andWhere('p.role = :role_id')
|
||||
->setParameters(['bundle' => 'asset', 'role_id' => $role->getId()])
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// Set new permissions
|
||||
$role->setIsAdmin(false);
|
||||
$roleModel = static::getContainer()->get('mautic.user.model.role');
|
||||
\assert($roleModel instanceof RoleModel);
|
||||
$roleModel->setRolePermissions($role, $permissions);
|
||||
$this->em->persist($role);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function testPostRequestWithWrongTempNameAndOriginalFileNameFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image2.php';
|
||||
$data['asset']['originalFileName'] = 'originalImage2.php';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
preg_match_all('/Upload failed as the file extension, php/', $this->client->getResponse()->getContent(), $matches);
|
||||
$this->assertCount(2, $matches[0]);
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testPostRequestWithWrongTempNameFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image2.php';
|
||||
$data['asset']['originalFileName'] = 'originalImage2.png';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
preg_match_all('/Upload failed as the file extension, php/', $this->client->getResponse()->getContent(), $matches);
|
||||
$this->assertCount(1, $matches[0]);
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testPostResquetSuccessWithCorrectFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image.png';
|
||||
$data['asset']['originalFileName'] = 'originalImage.png';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$this->assertStringNotContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testAssetWithProject(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('test');
|
||||
$asset->setAlias('test');
|
||||
$this->em->persist($asset);
|
||||
|
||||
$project = new Project();
|
||||
$project->setName('Test Project');
|
||||
$this->em->persist($project);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$crawler = $this->client->request('GET', '/s/assets/edit/'.$asset->getId());
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['asset[projects]']->setValue((string) $project->getId());
|
||||
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$savedAsset = $this->em->find(Asset::class, $asset->getId());
|
||||
Assert::assertSame($project->getId(), $savedAsset->getProjects()->first()->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
class AssetDetailFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testLeadViewPreventsXSS(): void
|
||||
{
|
||||
$title = 'aaa" onerror=alert(1) a="';
|
||||
$asset = new Asset();
|
||||
$asset->setTitle($title);
|
||||
$asset->setAlias('dummy-alias');
|
||||
$asset->setStorageLocation('local');
|
||||
$asset->setPath('broken-image.jpg');
|
||||
$asset->setExtension('jpg');
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->detach($asset);
|
||||
|
||||
$crawler = $this->client->request('GET', sprintf('/s/assets/view/%d', $asset->getId()));
|
||||
$imageTag = $crawler->filter('.img-thumbnail');
|
||||
|
||||
$onError = $imageTag->attr('onerror');
|
||||
$altProp = $imageTag->attr('alt');
|
||||
|
||||
Assert::assertNull($onError);
|
||||
Assert::assertSame($title, $altProp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class AssetDownloadFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testDownloadOfNotFoundAsset(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/logout');
|
||||
|
||||
// The 500 error happened only on the second request.
|
||||
// It happened only if the device was already tracked.
|
||||
$this->client->request(Request::METHOD_GET, '/asset/unicorn'); // returns 404 correctly
|
||||
$this->client->request(Request::METHOD_GET, '/asset/unicorn'); // returned 500 but it should return 404
|
||||
|
||||
Assert::assertSame(Response::HTTP_NOT_FOUND, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\ProjectBundle\Tests\Functional\AbstractProjectSearchTestCase;
|
||||
|
||||
final class AssetProjectSearchFunctionalTest extends AbstractProjectSearchTestCase
|
||||
{
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')]
|
||||
public function testProjectSearch(string $searchTerm, array $expectedEntities, array $unexpectedEntities): void
|
||||
{
|
||||
$projectOne = $this->createProject('Project One');
|
||||
$projectTwo = $this->createProject('Project Two');
|
||||
$projectThree = $this->createProject('Project Three');
|
||||
|
||||
$assetAlpha = $this->createAsset('Asset Alpha');
|
||||
$assetBeta = $this->createAsset('Asset Beta');
|
||||
$this->createAsset('Asset Gamma');
|
||||
$this->createAsset('Asset Delta');
|
||||
|
||||
$assetAlpha->addProject($projectOne);
|
||||
$assetAlpha->addProject($projectTwo);
|
||||
$assetBeta->addProject($projectTwo);
|
||||
$assetBeta->addProject($projectThree);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->searchAndAssert($searchTerm, $expectedEntities, $unexpectedEntities, ['/api/assets', '/s/assets']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, array{searchTerm: string, expectedEntities: array<string>, unexpectedEntities: array<string>}>
|
||||
*/
|
||||
public static function searchDataProvider(): \Generator
|
||||
{
|
||||
yield 'search by one project' => [
|
||||
'searchTerm' => 'project:"Project Two"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project AND asset name' => [
|
||||
'searchTerm' => 'project:"Project Two" AND Beta',
|
||||
'expectedEntities' => ['Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project OR asset name' => [
|
||||
'searchTerm' => 'project:"Project Two" OR Gamma',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta', 'Asset Gamma'],
|
||||
'unexpectedEntities' => ['Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by NOT one project' => [
|
||||
'searchTerm' => '!project:"Project Two"',
|
||||
'expectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with AND' => [
|
||||
'searchTerm' => 'project:"Project Two" AND project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT AND' => [
|
||||
'searchTerm' => '!project:"Project Two" AND !project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with OR' => [
|
||||
'searchTerm' => 'project:"Project Two" OR project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT OR' => [
|
||||
'searchTerm' => '!project:"Project Two" OR !project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Beta'],
|
||||
];
|
||||
}
|
||||
|
||||
private function createAsset(string $name): Asset
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle($name);
|
||||
$asset->setAlias($name);
|
||||
$this->em->persist($asset);
|
||||
|
||||
return $asset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Download;
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
|
||||
class PublicControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
/**
|
||||
* Download action should return the file content.
|
||||
*/
|
||||
public function testDownloadActionStreamByDefault(): void
|
||||
{
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug);
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download action should return the file content.
|
||||
*/
|
||||
public function testDownloadActionStreamIsZero(): void
|
||||
{
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug.'?stream=0');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download action with UTM should return the file content.
|
||||
*/
|
||||
public function testDownloadActionWithUTM(): void
|
||||
{
|
||||
$this->logoutUser();
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias().'?utm_source=test2&utm_medium=test3&utm_campaign=test6&utm_term=test4&utm_content=test5';
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug);
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
|
||||
$downloadRepo = $this->em->getRepository(Download::class);
|
||||
|
||||
$download = $downloadRepo->findOneBy(['asset' => $this->asset]);
|
||||
\assert($download instanceof Download);
|
||||
$this->assertSame('test2', $download->getUtmSource());
|
||||
$this->assertSame('test3', $download->getUtmMedium());
|
||||
$this->assertSame('test4', $download->getUtmTerm());
|
||||
$this->assertSame('test5', $download->getUtmContent());
|
||||
$this->assertSame('test6', $download->getUtmCampaign());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class UploadControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
public function testUploadWithWrongMimetype(): void
|
||||
{
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
|
||||
$fileName = 'image2.png';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
copy('index.php', $filePath);
|
||||
|
||||
$binaryFile = new UploadedFile($filePath, $fileName, 'application/x-httpd-php', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Upload the file
|
||||
$this->client->request(
|
||||
Request::METHOD_POST,
|
||||
'/s/_uploader/asset/upload',
|
||||
[
|
||||
'tempId' => $tmpId,
|
||||
],
|
||||
[
|
||||
'file' => $binaryFile,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertStringContainsString('Upload failed as the file mimetype', $response->getContent());
|
||||
$this->assertStringContainsString('text\/x-php is not allowed', $response->getContent());
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
public function testSuccessUploadWithPng(): void
|
||||
{
|
||||
// Create a temporary PNG file
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
$assetsPathFrom = $this->client->getKernel()->getContainer()->getParameter('mautic.application_dir').'/app/assets/images/mautic_logo_db64.png';
|
||||
|
||||
$fileName = 'image3.png';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
copy($assetsPathFrom, $filePath);
|
||||
// Create an UploadedFile instance with the correct MIME type
|
||||
$uploadedFile = new UploadedFile($filePath, $fileName, 'image/png', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Perform the request with the file
|
||||
$this->client->request(
|
||||
'POST',
|
||||
'/s/_uploader/asset/upload',
|
||||
['tempId' => $tmpId],
|
||||
['file' => $uploadedFile]
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$this->assertStringContainsString('state":1', $this->client->getResponse()->getContent());
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
$data = json_decode($this->client->getResponse()->getContent(), true);
|
||||
unlink($assetsPath.'/tmp/'.$tmpId.'/'.$data['tmpFileName']);
|
||||
rmdir($assetsPath.'/tmp/'.$tmpId);
|
||||
}
|
||||
|
||||
public function testUploadWithWrongExtension(): void
|
||||
{
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
$assetsPathFrom = $this->client->getKernel()->getContainer()->getParameter('mautic.application_dir').'/app/assets/images/mautic_logo_db64.png';
|
||||
|
||||
$fileName = 'image2.php';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
copy($assetsPathFrom, $filePath);
|
||||
|
||||
$binaryFile = new UploadedFile($filePath, $fileName, 'image/png', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Upload the file
|
||||
$this->client->request(
|
||||
Request::METHOD_POST,
|
||||
'/s/_uploader/asset/upload',
|
||||
[
|
||||
'tempId' => $tmpId,
|
||||
],
|
||||
[
|
||||
'file' => $binaryFile,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertStringContainsString('Upload failed as the file extension', $response->getContent());
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php,', $response->getContent());
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user