image_to_media_swapper-2.x-dev/tests/src/Kernel/SecurityAttackSimulationTest.php
tests/src/Kernel/SecurityAttackSimulationTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\image_to_media_swapper\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
use Drupal\user\Entity\Role;
use Drupal\image_to_media_swapper\Controller\SwapperController;
use Drupal\user\UserInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests security vulnerabilities for the image_to_media_swapper module.
*
* @group image_to_media_swapper
* @group security
*/
class SecurityAttackSimulationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'file',
'image',
'media',
'serialization',
'image_to_media_swapper',
];
/**
* The controller to test.
*/
protected SwapperController $controller;
/**
* Test user with permissions.
*/
protected User $testUser;
/**
* Anonymous user without permissions.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $anonymousUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installEntitySchema('media');
$this->installSchema('system', ['sequences']);
$this->installConfig(['image_to_media_swapper']);
// Create authenticated user role with required permissions.
$role = Role::create([
'id' => 'media_user',
'label' => 'Media User',
]);
$role->grantPermission('create media');
$role->grantPermission('update media');
$role->save();
// Create test user with permissions.
$this->testUser = User::create([
'name' => 'test_user',
'uid' => 2,
'roles' => ['media_user'],
]);
$this->testUser->save();
// Get anonymous user.
$this->anonymousUser = User::getAnonymousUser();
// Get controller instance.
$this->controller = SwapperController::create($this->container);
}
/**
* Tests anonymous user access to protected endpoints.
*/
public function testAnonymousUserAccessDenied(): void {
// Set anonymous user as current user.
$this->container->get('current_user')->setAccount($this->anonymousUser);
// Test token endpoint access.
$request = Request::create('/media-api/security-tokens', 'GET');
$request->headers->set('Referer', 'http://example.com/admin');
$response = $this->controller->getSecurityTokens($request);
$this->assertEquals(403, $response->getStatusCode());
// Test main API endpoint access.
$request = $this->createApiRequest([
'uuid' => 'test-uuid',
'csrf_token' => 'fake-token',
'user_uuid' => 'fake-uuid',
]);
$response = $this->controller->swapFileEntityWithMediaFromUuid($request);
$this->assertEquals(403, $response->getStatusCode());
}
/**
* Tests missing CSRF token handling.
*/
public function testMissingCsrfTokenBlocked(): void {
$this->container->get('current_user')->setAccount($this->testUser);
// Test with missing CSRF token.
$request = $this->createApiRequest([
'uuid' => 'test-uuid',
'user_uuid' => $this->testUser->uuid(),
]);
$response = $this->controller->swapFileEntityWithMediaFromUuid($request);
$this->assertEquals(403, $response->getStatusCode());
$this->assertStringContainsString('Missing CSRF token', $response->getContent());
}
/**
* Tests that security validation fails on the first missing requirement.
*/
public function testSecurityValidationOrder(): void {
$this->container->get('current_user')->setAccount($this->testUser);
// Test 1: Missing CSRF token (should fail first).
$request = $this->createApiRequest([
'uuid' => 'test-uuid',
'user_uuid' => $this->testUser->uuid(),
]);
$response = $this->controller->swapFileEntityWithMediaFromUuid($request);
$this->assertEquals(403, $response->getStatusCode());
$this->assertStringContainsString('Missing CSRF token', $response->getContent());
// Test 2: Invalid CSRF token (should fail on token validation).
$request = $this->createApiRequest([
'uuid' => 'test-uuid',
'csrf_token' => 'fake-token',
'user_uuid' => $this->testUser->uuid(),
]);
$response = $this->controller->swapFileEntityWithMediaFromUuid($request);
$this->assertEquals(403, $response->getStatusCode());
$this->assertStringContainsString('Invalid CSRF token', $response->getContent());
}
/**
* Tests that malicious payloads don't crash the system.
*/
public function testMaliciousPayloadHandling(): void {
$this->container->get('current_user')->setAccount($this->testUser);
$malicious_payloads = [
// XSS attempts.
['uuid' => '<script>alert("xss")</script>'],
// Path traversal.
['uuid' => '../../../etc/passwd'],
// Null bytes.
['uuid' => "test\0uuid"],
];
foreach ($malicious_payloads as $payload) {
$request = $this->createApiRequest($payload);
try {
$response = $this->controller->swapFileEntityWithMediaFromUuid($request);
// Should handle gracefully. Will likely fail on missing CSRF.
$this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response);
$this->assertEquals(403, $response->getStatusCode());
}
catch (\Exception $e) {
// Should not throw unhandled exceptions.
$this->fail('Malicious payload caused unhandled exception: ' . $e->getMessage());
}
}
}
/**
* Creates an API request with given payload and headers.
*/
private function createApiRequest(array $payload, array $headers = []): Request {
$request = Request::create(
'/media-api/swap-file-to-media/file-uuid',
'POST',
[],
[],
[],
['HTTP_HOST' => 'example.com'],
json_encode($payload)
);
$default_headers = [
'Content-Type' => 'application/json',
'Origin' => 'http://example.com',
];
foreach (array_merge($default_headers, $headers) as $name => $value) {
$request->headers->set($name, $value);
}
return $request;
}
}
