133 lines
5.3 KiB
PHP
133 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use Fruitcake\Cors\CorsService;
|
|
use Illuminate\Http\Middleware\HandleCors;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Tests\TestCase;
|
|
|
|
class SecurityHeadersTest extends TestCase
|
|
{
|
|
public function test_marketing_and_auth_responses_have_security_headers_and_csrf_cookie(): void
|
|
{
|
|
$originalEnv = app()->environment();
|
|
app()->detectEnvironment(fn () => 'production');
|
|
config([
|
|
'app.debug' => false,
|
|
'app.url' => 'https://test-y0k0.fotospiel.app',
|
|
'security_headers.force_hsts' => true,
|
|
'cors.allowed_origins' => ['https://test-y0k0.fotospiel.app'],
|
|
'cors.paths' => ['*'],
|
|
]);
|
|
|
|
Route::middleware('web')->get('/__test/marketing', fn () => response('ok'));
|
|
Route::middleware('web')->get('/__test/auth', fn () => response('ok'));
|
|
|
|
try {
|
|
$response = $this->withServerVariables([
|
|
'HTTPS' => 'on',
|
|
'HTTP_ORIGIN' => 'https://test-y0k0.fotospiel.app',
|
|
])->get('/__test/marketing');
|
|
|
|
$response->assertOk();
|
|
$response->assertHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
$response->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
$response->assertHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
|
$response->assertHeader('Content-Security-Policy');
|
|
$response->assertHeaderContains('Content-Security-Policy', "style-src-elem 'self'");
|
|
$response->assertHeaderContains('Content-Security-Policy', "style-src-elem 'self' https: data: 'unsafe-inline'");
|
|
$response->assertHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
$response->assertCookie('XSRF-TOKEN');
|
|
|
|
$login = $this->withServerVariables([
|
|
'HTTPS' => 'on',
|
|
'HTTP_ORIGIN' => 'https://test-y0k0.fotospiel.app',
|
|
])->get('/__test/auth');
|
|
|
|
$login->assertOk();
|
|
$login->assertHeader('Content-Security-Policy');
|
|
$login->assertHeaderContains('Content-Security-Policy', "style-src-elem 'self'");
|
|
$login->assertHeaderContains('Content-Security-Policy', "style-src-elem 'self' https: data: 'unsafe-inline'");
|
|
$login->assertHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
$login->assertCookie('XSRF-TOKEN');
|
|
} finally {
|
|
app()->detectEnvironment(fn () => $originalEnv);
|
|
}
|
|
}
|
|
|
|
public function test_cors_headers_present_for_api_requests_and_error_responses_keep_headers(): void
|
|
{
|
|
$originalEnv = app()->environment();
|
|
app()->detectEnvironment(fn () => 'production');
|
|
$corsConfig = config('cors');
|
|
$corsConfig['paths'] = ['*'];
|
|
$corsConfig['allowed_origins'] = ['https://test-y0k0.fotospiel.app'];
|
|
|
|
config([
|
|
'app.debug' => false,
|
|
'app.url' => 'https://test-y0k0.fotospiel.app',
|
|
'cors' => $corsConfig,
|
|
]);
|
|
|
|
Route::middleware('web')->get('/__test/error', fn () => throw new \RuntimeException('boom'));
|
|
|
|
try {
|
|
$corsMiddleware = new HandleCors(app(), new CorsService(config('cors')));
|
|
$this->assertContains('*', config('cors.paths'));
|
|
$this->assertContains('https://test-y0k0.fotospiel.app', config('cors.allowed_origins'));
|
|
$corsRequest = Request::create(
|
|
uri: '/api/__test/cors',
|
|
method: 'OPTIONS',
|
|
server: [
|
|
'HTTP_ORIGIN' => 'https://test-y0k0.fotospiel.app',
|
|
'HTTPS' => 'on',
|
|
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET',
|
|
],
|
|
);
|
|
|
|
$this->assertSame('https://test-y0k0.fotospiel.app', $corsRequest->headers->get('Origin'));
|
|
|
|
$corsResponse = $corsMiddleware->handle($corsRequest, fn () => response()->noContent());
|
|
|
|
$this->assertSame('https://test-y0k0.fotospiel.app', $corsResponse->headers->get('Access-Control-Allow-Origin'));
|
|
|
|
$error = $this->withServerVariables([
|
|
'HTTPS' => 'on',
|
|
])->get('/__test/error');
|
|
|
|
$error->assertStatus(500);
|
|
$error->assertHeader('Content-Security-Policy');
|
|
$error->assertHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
} finally {
|
|
app()->detectEnvironment(fn () => $originalEnv);
|
|
}
|
|
}
|
|
|
|
public function test_guest_pwa_allows_camera_via_permissions_policy(): void
|
|
{
|
|
$originalEnv = app()->environment();
|
|
app()->detectEnvironment(fn () => 'production');
|
|
config([
|
|
'app.debug' => false,
|
|
'app.url' => 'https://test-y0k0.fotospiel.app',
|
|
'security_headers.force_hsts' => true,
|
|
]);
|
|
|
|
Route::middleware('web')->get('/e/test-token', fn () => response('ok'));
|
|
|
|
try {
|
|
$response = $this->withServerVariables([
|
|
'HTTPS' => 'on',
|
|
'HTTP_ORIGIN' => 'https://test-y0k0.fotospiel.app',
|
|
])->get('/e/test-token');
|
|
|
|
$response->assertOk();
|
|
$response->assertHeader('Permissions-Policy', 'camera=(self), microphone=(), geolocation=()');
|
|
} finally {
|
|
app()->detectEnvironment(fn () => $originalEnv);
|
|
}
|
|
}
|
|
}
|