- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
232 lines
7.0 KiB
PHP
232 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Application\Security\Services;
|
|
|
|
use App\Application\Security\Events\File\SuspiciousFileUploadEvent;
|
|
use App\Application\Security\Services\FileUploadSecurityService;
|
|
use App\Framework\Core\Events\EventDispatcher;
|
|
use App\Framework\Http\UploadedFile;
|
|
use App\Framework\Http\UploadError;
|
|
use Mockery;
|
|
|
|
describe('FileUploadSecurityService', function () {
|
|
beforeEach(function () {
|
|
$this->eventDispatcher = Mockery::mock(EventDispatcher::class);
|
|
$this->service = new FileUploadSecurityService($this->eventDispatcher);
|
|
});
|
|
|
|
afterEach(function () {
|
|
Mockery::close();
|
|
});
|
|
|
|
it('validates a safe uploaded file', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'test.jpg',
|
|
type: 'image/jpeg',
|
|
size: 1024 * 1024, // 1MB
|
|
tmpName: '/tmp/test_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
// Mock the file system
|
|
file_put_contents('/tmp/test_upload', 'fake image content');
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeTrue();
|
|
unlink('/tmp/test_upload');
|
|
});
|
|
|
|
it('rejects files with upload errors', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'test.jpg',
|
|
type: 'image/jpeg',
|
|
size: 1024,
|
|
tmpName: '/tmp/test_upload',
|
|
error: UploadError::PARTIAL
|
|
);
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
});
|
|
|
|
it('rejects files that are too large', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'large.jpg',
|
|
type: 'image/jpeg',
|
|
size: 20 * 1024 * 1024, // 20MB (exceeds 10MB limit)
|
|
tmpName: '/tmp/large_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
});
|
|
|
|
it('rejects files with dangerous extensions', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'malicious.php',
|
|
type: 'application/x-php',
|
|
size: 1024,
|
|
tmpName: '/tmp/malicious_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
});
|
|
|
|
it('rejects files with forbidden MIME types', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'test.txt',
|
|
type: 'application/x-executable',
|
|
size: 1024,
|
|
tmpName: '/tmp/executable_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
// Create mock file content
|
|
file_put_contents('/tmp/executable_upload', 'fake executable');
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
unlink('/tmp/executable_upload');
|
|
});
|
|
|
|
it('rejects files with malware signatures', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'suspicious.txt',
|
|
type: 'text/plain',
|
|
size: 1024,
|
|
tmpName: '/tmp/suspicious_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
// Create file with malware signature
|
|
file_put_contents('/tmp/suspicious_upload', 'normal content <?php eval($_POST["cmd"]); ?>');
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
unlink('/tmp/suspicious_upload');
|
|
});
|
|
|
|
it('rejects files with double extensions', function () {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'image.jpg.php',
|
|
type: 'image/jpeg',
|
|
size: 1024,
|
|
tmpName: '/tmp/double_ext_upload',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
// Create mock file
|
|
file_put_contents('/tmp/double_ext_upload', 'fake image content');
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse();
|
|
unlink('/tmp/double_ext_upload');
|
|
});
|
|
|
|
it('accepts various safe file types', function () {
|
|
$safeFiles = [
|
|
['test.jpg', 'image/jpeg'],
|
|
['test.png', 'image/png'],
|
|
['test.gif', 'image/gif'],
|
|
['test.webp', 'image/webp'],
|
|
['document.pdf', 'application/pdf'],
|
|
['data.csv', 'text/csv'],
|
|
];
|
|
|
|
foreach ($safeFiles as [$filename, $mimeType]) {
|
|
$file = UploadedFile::createForTesting(
|
|
name: $filename,
|
|
type: $mimeType,
|
|
size: 1024,
|
|
tmpName: '/tmp/safe_upload_' . $filename,
|
|
error: UploadError::OK
|
|
);
|
|
|
|
file_put_contents('/tmp/safe_upload_' . $filename, 'safe content');
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeTrue("File $filename should be valid");
|
|
unlink('/tmp/safe_upload_' . $filename);
|
|
}
|
|
});
|
|
|
|
it('handles multiple malware signatures', function () {
|
|
$malwareSignatures = [
|
|
'eval(',
|
|
'base64_decode(',
|
|
'system(',
|
|
'exec(',
|
|
'shell_exec(',
|
|
'<?php',
|
|
'<%',
|
|
'<script',
|
|
'javascript:',
|
|
];
|
|
|
|
foreach ($malwareSignatures as $signature) {
|
|
$file = UploadedFile::createForTesting(
|
|
name: 'test.txt',
|
|
type: 'text/plain',
|
|
size: 1024,
|
|
tmpName: '/tmp/malware_test',
|
|
error: UploadError::OK
|
|
);
|
|
|
|
file_put_contents('/tmp/malware_test', 'Normal content ' . $signature . ' more content');
|
|
|
|
$this->eventDispatcher
|
|
->shouldReceive('dispatch')
|
|
->once()
|
|
->with(Mockery::type(SuspiciousFileUploadEvent::class));
|
|
|
|
$result = $this->service->validateUpload($file);
|
|
|
|
expect($result)->toBeFalse("Signature '$signature' should be detected");
|
|
unlink('/tmp/malware_test');
|
|
}
|
|
});
|
|
});
|