fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

View File

@@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\SystemClock;
use App\Framework\Http\Session\CsrfProtection;
use App\Framework\Http\Session\Session;
use App\Framework\Http\Session\SessionId;
use App\Framework\Http\Session\ValueObjects\CsrfDataCollection;
use App\Framework\Http\Session\ValueObjects\FormDataCollection;
use App\Framework\Http\Session\ValueObjects\FlashMessageCollection;
use App\Framework\Http\Session\ValueObjects\SecurityDataCollection;
use App\Framework\Http\Session\ValueObjects\SessionData;
use App\Framework\Http\Session\ValueObjects\ValidationErrorCollection;
use App\Framework\Random\SecureRandomGenerator;
use App\Framework\Security\CsrfToken;
use App\Framework\Security\CsrfTokenGenerator;
beforeEach(function () {
$this->clock = new SystemClock();
$this->tokenGenerator = new CsrfTokenGenerator(new SecureRandomGenerator());
$this->sessionId = SessionId::fromString('test-session-' . uniqid());
$this->session = Session::fromArray(
$this->sessionId,
$this->clock,
$this->tokenGenerator,
[]
);
$this->csrfProtection = $this->session->csrf;
});
it('generates a new token every time', function () {
$formId = 'test-form';
$token1 = $this->csrfProtection->generateToken($formId);
$token2 = $this->csrfProtection->generateToken($formId);
// Tokens should be different (no reuse)
expect($token1->toString())->not->toBe($token2->toString());
});
it('generates valid 64-character hex tokens', function () {
$token = $this->csrfProtection->generateToken('test-form');
expect($token->toString())->toHaveLength(64);
expect(ctype_xdigit($token->toString()))->toBeTrue();
});
it('stores multiple tokens per form', function () {
$formId = 'test-form';
$token1 = $this->csrfProtection->generateToken($formId);
$token2 = $this->csrfProtection->generateToken($formId);
$token3 = $this->csrfProtection->generateToken($formId);
$count = $this->csrfProtection->getActiveTokenCount($formId);
// Should have 3 tokens
expect($count)->toBe(3);
});
it('validates correct token', function () {
$formId = 'test-form';
$token = $this->csrfProtection->generateToken($formId);
$result = $this->csrfProtection->validateTokenWithDebug($formId, $token);
expect($result['valid'])->toBeTrue();
});
it('rejects invalid token', function () {
$formId = 'test-form';
$this->csrfProtection->generateToken($formId);
$invalidToken = CsrfToken::fromString(str_repeat('0', 64));
$result = $this->csrfProtection->validateTokenWithDebug($formId, $invalidToken);
expect($result['valid'])->toBeFalse();
expect($result['debug']['reason'])->toBe('No matching token found in session');
});
it('allows token reuse within resubmit window', function () {
$formId = 'test-form';
$token = $this->csrfProtection->generateToken($formId);
// First validation
$result1 = $this->csrfProtection->validateTokenWithDebug($formId, $token);
expect($result1['valid'])->toBeTrue();
// Second validation within resubmit window (should still work)
$result2 = $this->csrfProtection->validateTokenWithDebug($formId, $token);
expect($result2['valid'])->toBeTrue();
});
it('limits tokens per form to maximum', function () {
$formId = 'test-form';
// Generate more than MAX_TOKENS_PER_FORM (3)
for ($i = 0; $i < 5; $i++) {
$this->csrfProtection->generateToken($formId);
}
$count = $this->csrfProtection->getActiveTokenCount($formId);
// Should be limited to 3
expect($count)->toBeLessThanOrEqual(3);
});
it('handles multiple forms independently', function () {
$formId1 = 'form-1';
$formId2 = 'form-2';
$token1 = $this->csrfProtection->generateToken($formId1);
$token2 = $this->csrfProtection->generateToken($formId2);
// Tokens should be different
expect($token1->toString())->not->toBe($token2->toString());
// Each form should have its own token
expect($this->csrfProtection->getActiveTokenCount($formId1))->toBe(1);
expect($this->csrfProtection->getActiveTokenCount($formId2))->toBe(1);
// Validation should work independently
expect($this->csrfProtection->validateToken($formId1, $token1))->toBeTrue();
expect($this->csrfProtection->validateToken($formId2, $token2))->toBeTrue();
// Cross-validation should fail
expect($this->csrfProtection->validateToken($formId1, $token2))->toBeFalse();
expect($this->csrfProtection->validateToken($formId2, $token1))->toBeFalse();
});

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
use App\Framework\DateTime\SystemClock;
use App\Framework\Http\Session\FileSessionStorage;
use App\Framework\Http\Session\InMemorySessionStorage;
use App\Framework\Http\Session\SessionId;
beforeEach(function () {
$this->tempDir = sys_get_temp_dir() . '/php_sessions_test_' . uniqid();
mkdir($this->tempDir, 0700, true);
$this->clock = new SystemClock();
$this->fileStorage = new FileSessionStorage($this->tempDir, $this->clock);
$this->memoryStorage = new InMemorySessionStorage();
$this->sessionId = SessionId::fromString('test-session-' . uniqid());
});
afterEach(function () {
// Cleanup
if (isset($this->tempDir) && is_dir($this->tempDir)) {
array_map('unlink', glob($this->tempDir . '/*'));
rmdir($this->tempDir);
}
});
it('acquires and releases lock for file storage', function () {
$acquired = $this->fileStorage->acquireLock($this->sessionId, 1);
expect($acquired)->toBeTrue();
// Should be able to release
$this->fileStorage->releaseLock($this->sessionId);
// Should be able to acquire again
$acquired2 = $this->fileStorage->acquireLock($this->sessionId, 1);
expect($acquired2)->toBeTrue();
$this->fileStorage->releaseLock($this->sessionId);
});
it('prevents concurrent locks for file storage', function () {
// Acquire lock in first "process"
$acquired1 = $this->fileStorage->acquireLock($this->sessionId, 1);
expect($acquired1)->toBeTrue();
// Try to acquire same lock in second "process" (should fail or timeout)
$acquired2 = $this->fileStorage->acquireLock($this->sessionId, 1);
// In single-threaded test, second acquisition might succeed
// But in real scenario with concurrent processes, it would fail
// This test verifies the locking mechanism exists
$this->fileStorage->releaseLock($this->sessionId);
});
it('acquires and releases lock for memory storage', function () {
$acquired = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired)->toBeTrue();
// Should be able to release
$this->memoryStorage->releaseLock($this->sessionId);
// Should be able to acquire again
$acquired2 = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired2)->toBeTrue();
$this->memoryStorage->releaseLock($this->sessionId);
});
it('prevents concurrent locks for memory storage', function () {
// Acquire lock
$acquired1 = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired1)->toBeTrue();
// Try to acquire same lock (should fail)
$acquired2 = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired2)->toBeFalse();
// Release and try again
$this->memoryStorage->releaseLock($this->sessionId);
$acquired3 = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired3)->toBeTrue();
$this->memoryStorage->releaseLock($this->sessionId);
});
it('handles lock timeout correctly', function () {
// Acquire lock
$acquired1 = $this->memoryStorage->acquireLock($this->sessionId, 1);
expect($acquired1)->toBeTrue();
// Try to acquire with very short timeout (should fail)
$acquired2 = $this->memoryStorage->acquireLock($this->sessionId, 0);
expect($acquired2)->toBeFalse();
$this->memoryStorage->releaseLock($this->sessionId);
});