113 lines
4.1 KiB
PHP
113 lines
4.1 KiB
PHP
<?php
|
|
|
|
use App\Http\Middleware\RestrictConsoleNetwork;
|
|
use App\Livewire\Admin\Settings as AdminSettings;
|
|
use App\Models\User;
|
|
use App\Support\Settings;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* Who may reach the console, by network address.
|
|
*
|
|
* The hostname guard answers under which NAME the console responds, and the
|
|
* caller picks the Host header — so it can never answer who is asking. This
|
|
* can, and the owner keeps the list themselves.
|
|
*/
|
|
/** Livewire's test harness has no withServerVariables(); set it on the request. */
|
|
function fromIp(string $ip): void
|
|
{
|
|
request()->server->set('REMOTE_ADDR', $ip);
|
|
}
|
|
|
|
beforeEach(function () {
|
|
config()->set('admin_access.hosts', []);
|
|
config()->set('admin_access.trusted_ranges', ['10.66.0.0/24']);
|
|
Settings::set('console.network_restricted', false);
|
|
Settings::set('console.allowed_ips', []);
|
|
});
|
|
|
|
it('changes nothing until the owner switches it on', function () {
|
|
$this->actingAs(operator('Owner'))
|
|
->withServerVariables(['REMOTE_ADDR' => '203.0.113.9'])
|
|
->get('/admin')
|
|
->assertOk();
|
|
});
|
|
|
|
it('lets the management VPN in and turns everyone else away', function () {
|
|
Settings::set('console.network_restricted', true);
|
|
|
|
$this->actingAs(operator('Owner'))
|
|
->withServerVariables(['REMOTE_ADDR' => '10.66.0.5'])
|
|
->get('/admin')
|
|
->assertOk();
|
|
|
|
// 404, never 403: a stranger must not learn a console lives here.
|
|
$this->actingAs(operator('Owner'))
|
|
->withServerVariables(['REMOTE_ADDR' => '203.0.113.9'])
|
|
->get('/admin')
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('lets in an address the owner has listed, without the VPN', function () {
|
|
Settings::set('console.network_restricted', true);
|
|
Settings::set('console.allowed_ips', ['203.0.113.0/24']);
|
|
|
|
$this->actingAs(operator('Owner'))
|
|
->withServerVariables(['REMOTE_ADDR' => '203.0.113.9'])
|
|
->get('/admin')
|
|
->assertOk();
|
|
|
|
// A neighbouring range is still nobody.
|
|
$this->actingAs(operator('Owner'))
|
|
->withServerVariables(['REMOTE_ADDR' => '198.51.100.4'])
|
|
->get('/admin')
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('knows when a change would lock the owner out', function () {
|
|
// The one check whose failure mode is that nobody can reach the page that
|
|
// would fix it — so it is decided without a request, and tested that way.
|
|
expect(RestrictConsoleNetwork::wouldStillAllow('10.66.0.5', []))->toBeTrue()
|
|
->and(RestrictConsoleNetwork::wouldStillAllow('198.51.100.4', []))->toBeFalse()
|
|
->and(RestrictConsoleNetwork::wouldStillAllow('198.51.100.4', ['198.51.100.0/24']))->toBeTrue()
|
|
// Removing the entry you are sitting behind leaves you outside.
|
|
->and(RestrictConsoleNetwork::wouldStillAllow('203.0.113.9', []))->toBeFalse();
|
|
});
|
|
|
|
it('rejects an entry that is not an address or a range', function () {
|
|
// A typo that silently matches nothing is how someone locks themselves out
|
|
// while believing they have not.
|
|
foreach (['nonsense', '203.0.113.', '203.0.113.9/99', ''] as $bad) {
|
|
Livewire::actingAs(operator('Owner'))
|
|
->test(AdminSettings::class)
|
|
->set('consoleIp', $bad)
|
|
->call('addConsoleIp')
|
|
->assertHasErrors('consoleIp');
|
|
}
|
|
|
|
expect(Settings::get('console.allowed_ips'))->toBe([]);
|
|
|
|
Livewire::actingAs(operator('Owner'))
|
|
->test(AdminSettings::class)
|
|
->set('consoleIp', '203.0.113.0/24')
|
|
->call('addConsoleIp')
|
|
->assertHasNoErrors();
|
|
|
|
expect(Settings::get('console.allowed_ips'))->toBe(['203.0.113.0/24']);
|
|
});
|
|
|
|
it('needs the capability, not merely an operator account', function () {
|
|
Livewire::actingAs(operator('Support'))
|
|
->test(AdminSettings::class)
|
|
->call('addConsoleIp')
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('is re-checked on every Livewire action, not only on the page', function () {
|
|
// The page is not where the actions run.
|
|
$persistent = app(\Livewire\Mechanisms\PersistentMiddleware\PersistentMiddleware::class)
|
|
->getPersistentMiddleware();
|
|
|
|
expect($persistent)->toContain(RestrictConsoleNetwork::class);
|
|
});
|