87 lines
3.7 KiB
PHP
87 lines
3.7 KiB
PHP
<?php
|
|
|
|
use App\Models\Operator;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
/**
|
|
* routes/channels.php's admin.runs channel — the console's live provisioning
|
|
* feed. Its own callback checks Auth::guard('operator') correctly, but that
|
|
* is not what this file is about: it is about whether the callback ever runs
|
|
* at all.
|
|
*
|
|
* BROADCAST_CONNECTION=null (this suite's forced default, phpunit.xml) backs
|
|
* onto NullBroadcaster, whose auth() is a bare no-op — it never calls a
|
|
* channel's own callback, so it cannot exercise this bug or its fix either
|
|
* way. A real Pusher-protocol broadcaster (Reverb uses PusherBroadcaster
|
|
* under the hood, BroadcastManager::createReverbDriver()) is switched in
|
|
* instead, via config rather than a container binding: Broadcast::auth()
|
|
* resolves through BroadcastManager::__call() -> driver(), which reads
|
|
* config('broadcasting.default') and builds its OWN instance — a container
|
|
* binding for BroadcasterContract is a separate, unrelated seam that
|
|
* driver() never consults. No live Reverb server is needed: auth() is pure
|
|
* PHP — channel authorization is an HMAC signature computed locally, never a
|
|
* call over the network.
|
|
*/
|
|
function bindRealBroadcaster(): void
|
|
{
|
|
config()->set('broadcasting.default', 'reverb');
|
|
config()->set('broadcasting.connections.reverb.key', 'test-key');
|
|
config()->set('broadcasting.connections.reverb.secret', 'test-secret');
|
|
config()->set('broadcasting.connections.reverb.app_id', 'test-app-id');
|
|
config()->set('broadcasting.connections.reverb.options', [
|
|
'host' => '127.0.0.1', 'port' => 6001, 'scheme' => 'http', 'useTLS' => false,
|
|
]);
|
|
|
|
// routes/channels.php ran once already, at boot, registering admin.runs
|
|
// against whichever broadcaster 'null' (this suite's forced default)
|
|
// resolved to at the time. Switching the default config above builds a
|
|
// brand new driver instance on next use — it does not retroactively
|
|
// register anything on it. Re-requiring the routes file does.
|
|
require base_path('routes/channels.php');
|
|
}
|
|
|
|
function authorizeAdminRunsChannel()
|
|
{
|
|
return test()->post('/broadcasting/auth', [
|
|
'channel_name' => 'private-admin.runs',
|
|
'socket_id' => '1234.1234',
|
|
]);
|
|
}
|
|
|
|
it('lets an operator authorize the console live-feed channel on the operator guard', function () {
|
|
bindRealBroadcaster();
|
|
$operator = Operator::factory()->role('Owner')->create();
|
|
|
|
$this->actingAs($operator, 'operator');
|
|
// actingAs() also flips Auth's DEFAULT guard to 'operator' (Auth::
|
|
// shouldUse()) — nothing does that for a real request to this route in
|
|
// production (/broadcasting/auth carries only the 'web' middleware
|
|
// group, never auth:operator,web), so left alone this would make the
|
|
// bare/default guard resolve the operator too and mask exactly the bug
|
|
// this test exists to catch. Reset so only the 'operator' GUARD INSTANCE
|
|
// (already given its user by actingAs() above, untouched by shouldUse())
|
|
// has the operator signed in — matching a real session.
|
|
Auth::shouldUse('web');
|
|
|
|
authorizeAdminRunsChannel()->assertOk();
|
|
});
|
|
|
|
it('refuses a guest on the console live-feed channel', function () {
|
|
bindRealBroadcaster();
|
|
|
|
authorizeAdminRunsChannel()->assertForbidden();
|
|
});
|
|
|
|
it('refuses a signed-in portal customer who is not an operator', function () {
|
|
bindRealBroadcaster();
|
|
$user = User::factory()->create();
|
|
|
|
$this->actingAs($user);
|
|
|
|
// Signed in on 'web', not 'operator' — retrieveUser() now finds THIS
|
|
// user (guards => ['operator', 'web']) and reaches the channel's own
|
|
// callback, which must still refuse them: they hold no operator role.
|
|
authorizeAdminRunsChannel()->assertForbidden();
|
|
});
|