Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
281
tests/Framework/Security/SecurityHeadersTest.php
Normal file
281
tests/Framework/Security/SecurityHeadersTest.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Framework\Security;
|
||||
|
||||
use App\Framework\Http\HttpResponse;
|
||||
use App\Framework\Http\MiddlewareContext;
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderConfig;
|
||||
use App\Framework\Http\Middlewares\SecurityHeaderMiddleware;
|
||||
use App\Framework\Http\Next;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Http\RequestStateManager;
|
||||
use App\Framework\Http\Status;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Critical Security Tests for HTTP Security Headers
|
||||
* Tests gegen OWASP A05:2021 – Security Misconfiguration
|
||||
*/
|
||||
final class SecurityHeadersTest extends TestCase
|
||||
{
|
||||
private SecurityHeaderConfig $config;
|
||||
|
||||
private SecurityHeaderMiddleware $middleware;
|
||||
|
||||
private RequestStateManager $stateManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->config = new SecurityHeaderConfig(
|
||||
hsts: true,
|
||||
hstsMaxAge: 31536000,
|
||||
hstsIncludeSubdomains: true,
|
||||
hstsPreload: true,
|
||||
csp: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
|
||||
xFrameOptions: 'DENY',
|
||||
xContentTypeOptions: true,
|
||||
referrerPolicy: 'strict-origin-when-cross-origin',
|
||||
permissionsPolicy: "camera=(), microphone=(), geolocation=()"
|
||||
);
|
||||
$this->middleware = new SecurityHeaderMiddleware($this->config);
|
||||
$this->stateManager = $this->createMock(RequestStateManager::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: HSTS Header wird korrekt gesetzt
|
||||
* Verhindert SSL-Stripping Attacks
|
||||
*/
|
||||
public function test_hsts_header_set_correctly(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createHttpsRequest();
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$this->assertEquals(
|
||||
'max-age=31536000; includeSubDomains; preload',
|
||||
$headers->getFirst('Strict-Transport-Security')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: CSP Header verhindert XSS
|
||||
* OWASP A03:2021 – Injection
|
||||
*/
|
||||
public function test_csp_header_prevents_xss(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$cspHeader = $headers->getFirst('Content-Security-Policy');
|
||||
|
||||
$this->assertStringContains("default-src 'self'", $cspHeader);
|
||||
$this->assertStringContains("script-src 'self'", $cspHeader);
|
||||
$this->assertStringNotContains("'unsafe-eval'", $cspHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: X-Frame-Options verhindert Clickjacking
|
||||
* OWASP A05:2021 – Security Misconfiguration
|
||||
*/
|
||||
public function test_x_frame_options_prevents_clickjacking(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$this->assertEquals('DENY', $headers->getFirst('X-Frame-Options'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: X-Content-Type-Options verhindert MIME-Type Confusion
|
||||
*/
|
||||
public function test_x_content_type_options_prevents_mime_sniffing(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$this->assertEquals('nosniff', $headers->getFirst('X-Content-Type-Options'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Referrer Policy schützt vor Information Leakage
|
||||
*/
|
||||
public function test_referrer_policy_prevents_info_leakage(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$this->assertEquals(
|
||||
'strict-origin-when-cross-origin',
|
||||
$headers->getFirst('Referrer-Policy')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Permissions Policy beschränkt Browser-Features
|
||||
*/
|
||||
public function test_permissions_policy_restricts_features(): void
|
||||
{
|
||||
// Arrange
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert
|
||||
$headers = $result->response->headers;
|
||||
$permissionsPolicy = $headers->getFirst('Permissions-Policy');
|
||||
|
||||
$this->assertStringContains('camera=()', $permissionsPolicy);
|
||||
$this->assertStringContains('microphone=()', $permissionsPolicy);
|
||||
$this->assertStringContains('geolocation=()', $permissionsPolicy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: HSTS wird nur über HTTPS gesetzt
|
||||
* Verhindert Mixed Content Probleme
|
||||
*/
|
||||
public function test_hsts_only_over_https(): void
|
||||
{
|
||||
// Arrange: HTTP Request
|
||||
$request = $this->createHttpRequest();
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert: Kein HSTS Header über HTTP
|
||||
$headers = $result->response->headers;
|
||||
$this->assertNull($headers->getFirst('Strict-Transport-Security'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Security Headers werden nicht überschrieben
|
||||
* Respektiert explizit gesetzte Headers
|
||||
*/
|
||||
public function test_existing_security_headers_not_overridden(): void
|
||||
{
|
||||
// Arrange: Response mit bereits gesetztem CSP Header
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$existingHeaders = ['Content-Security-Policy' => ["default-src 'none'"]];
|
||||
$response = new HttpResponse(Status::OK, $existingHeaders, 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $this->middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert: Ursprünglicher CSP Header bleibt erhalten
|
||||
$headers = $result->response->headers;
|
||||
$this->assertEquals("default-src 'none'", $headers->getFirst('Content-Security-Policy'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Development-spezifische CSP Policy
|
||||
* Erlaubt eval() und inline styles in Development
|
||||
*/
|
||||
public function test_development_csp_policy(): void
|
||||
{
|
||||
// Arrange: Development Config
|
||||
$devConfig = new SecurityHeaderConfig(
|
||||
csp: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
|
||||
);
|
||||
$middleware = new SecurityHeaderMiddleware($devConfig);
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$context = new MiddlewareContext($request);
|
||||
$response = new HttpResponse(Status::OK, [], 'content');
|
||||
|
||||
$next = $this->createMock(Next::class);
|
||||
$next->method('__invoke')->willReturn(new MiddlewareContext($request, $response));
|
||||
|
||||
// Act
|
||||
$result = $middleware->__invoke($context, $next, $this->stateManager);
|
||||
|
||||
// Assert: Development CSP erlaubt unsafe-eval
|
||||
$headers = $result->response->headers;
|
||||
$cspHeader = $headers->getFirst('Content-Security-Policy');
|
||||
|
||||
$this->assertStringContains("'unsafe-eval'", $cspHeader);
|
||||
$this->assertStringContains("'unsafe-inline'", $cspHeader);
|
||||
}
|
||||
|
||||
// Helper Methods
|
||||
|
||||
private function createHttpsRequest(): Request
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$request->method('isSecure')->willReturn(true);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
private function createHttpRequest(): Request
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$request->method('isSecure')->willReturn(false);
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user