Files
michaelschiemer/tests/Framework/Security/SecurityHeadersTest.php
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

282 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
}