CluPilotCloud/tests/Feature/Admin/AdminHostRestrictionTest.php

102 lines
3.8 KiB
PHP

<?php
use App\Models\User;
// operator() helper is defined in RbacTest.
it('serves the console on any host when ADMIN_HOSTS is empty (default)', function () {
config()->set('admin_access.hosts', []);
$this->actingAs(operator('Owner'))
->get('http://app.dev.clupilot.com/admin')
->assertOk();
});
it('404s the console on a hostname that is not allowed', function () {
config()->set('admin_access.hosts', ['admin.dev.clupilot.com']);
// A PUBLIC hostname must not even hint that a console exists here.
$this->actingAs(operator('Owner'))
->get('http://app.dev.clupilot.com/admin')
->assertNotFound();
$this->actingAs(operator('Owner'))
->get('http://www.dev.clupilot.com/admin/hosts')
->assertNotFound();
});
it('serves the console on an allowed hostname', function () {
config()->set('admin_access.hosts', ['admin.dev.clupilot.com', '10.10.90.185']);
$this->actingAs(operator('Owner'))
->get('http://admin.dev.clupilot.com/admin')
->assertOk();
$this->actingAs(operator('Owner'))
->get('http://10.10.90.185/admin')
->assertOk();
});
it('404s before revealing a login redirect to guests on a public host', function () {
config()->set('admin_access.hosts', ['admin.dev.clupilot.com']);
// Not a redirect to /login — that would confirm the console's existence.
$this->get('http://www.dev.clupilot.com/admin')->assertNotFound();
});
it('blocks admin Livewire actions posted through a public hostname', function () {
// Admin actions go to /livewire/update, which a path-based guard would skip.
// Livewire must re-apply the host restriction from the component's route.
config()->set('admin_access.hosts', ['admin.dev.clupilot.com']);
$owner = operator('Owner');
// Render the real page on an ALLOWED host so Livewire records the admin
// route's middleware on the snapshot (Livewire::test() skips HTTP routing
// and would not, which would make this test pass for the wrong reason).
$html = $this->actingAs($owner)
->get('http://admin.dev.clupilot.com/admin/datacenters')
->assertOk()
->getContent();
expect($html)->toMatch('/wire:snapshot=/');
preg_match('/wire:snapshot="([^"]+)"/', $html, $m);
$snapshot = html_entity_decode($m[1], ENT_QUOTES);
$payload = fn () => [
'_token' => csrf_token(),
'components' => [[
'snapshot' => $snapshot,
'updates' => [],
'calls' => [['path' => '', 'method' => 'save', 'params' => []]],
]],
];
// Replayed against a PUBLIC hostname → must not execute.
$this->actingAs($owner)
->post('http://www.dev.clupilot.com/livewire/update', $payload())
->assertNotFound();
// Positive control: the very same payload works on the allowed host, so the
// 404 above is the restriction — not a malformed request.
$this->actingAs($owner)
->post('http://admin.dev.clupilot.com/livewire/update', $payload())
->assertOk();
});
it('registers the host restriction as Livewire-persistent middleware', function () {
// Guards the wiring above: without this, /livewire/update bypasses ADMIN_HOSTS.
$persistent = (new ReflectionClass(\Livewire\Mechanisms\PersistentMiddleware\PersistentMiddleware::class))
->getProperty('persistentMiddleware');
expect($persistent->getValue(app(\Livewire\Mechanisms\PersistentMiddleware\PersistentMiddleware::class)))
->toContain(\App\Http\Middleware\RestrictAdminHost::class);
});
it('keeps the customer portal reachable on public hosts', function () {
config()->set('admin_access.hosts', ['admin.dev.clupilot.com']);
$user = User::factory()->create();
$this->actingAs($user)->get('http://app.dev.clupilot.com/dashboard')->assertOk();
$this->get('http://www.dev.clupilot.com/')->assertOk();
});