Skip to content

Commit fe7468a

Browse files
committed
Copy the broadcaster auth state directly
Instead of re-registering the central channels on the tenant broadcaster one by one, copy the properties directly. Also the authenticated user callback wasn't copied before, so user authentication (/broadcasting/user-auth) that worked in central context would 403 in tenant context if the callback was registered in central context.
1 parent 50afc49 commit fe7468a

1 file changed

Lines changed: 28 additions & 18 deletions

File tree

src/Bootstrappers/BroadcastingConfigBootstrapper.php

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,27 +91,11 @@ public function bootstrap(Tenant $tenant): void
9191
// contract gets the same tenant broadcaster that the manager uses, instead of the stale central one.
9292
// The closure runs immediately (the extended singleton is already resolved), and it's also what makes
9393
// channel auth work in tenant context -- the broadcaster resolved here gets cached as the tenant
94-
// manager's default driver and receives the central channel auth closures (see below).
94+
// manager's default driver and receives the central broadcaster's auth state (see copyAuthState()).
9595
$this->app->extend(BroadcasterContract::class, function (BroadcasterContract $centralBroadcaster) {
9696
$tenantBroadcaster = $this->app->make(BroadcastManager::class)->connection();
9797

98-
// The newly resolved broadcaster doesn't have any channel auth closures registered, so the
99-
// closures registered in central context (e.g. in routes/channels.php) have to be passed to it
100-
// manually, otherwise, Broadcast::auth() would throw a 403 for those channels.
101-
// Since Laravel only ever uses the default broadcaster's channel auth closures for broadcasting auth,
102-
// we only have to pass the channel closures to the default broadcaster.
103-
//
104-
// The channel() and getChannels() methods aren't part of the Broadcaster contract -- they come
105-
// from the abstract Broadcaster class, so the closures can only be copied between broadcasters extending it
106-
// (which all of Laravel's default broadcasters, e.g. PusherBroadcaster, do).
107-
if ($centralBroadcaster instanceof Broadcaster && $tenantBroadcaster instanceof Broadcaster) {
108-
// invade() because the channel options can't be retrieved through any of the broadcaster's public methods
109-
$channelOptions = invade($centralBroadcaster)->channelOptions;
110-
111-
foreach ($centralBroadcaster->getChannels() as $channel => $callback) {
112-
$tenantBroadcaster->channel($channel, $callback, $channelOptions[$channel] ?? []);
113-
}
114-
}
98+
$this->copyAuthState($centralBroadcaster, $tenantBroadcaster);
11599

116100
return $tenantBroadcaster;
117101
});
@@ -122,6 +106,32 @@ public function bootstrap(Tenant $tenant): void
122106
Broadcast::clearResolvedInstance();
123107
}
124108

109+
/**
110+
* Copy the auth state (the channel auth closures, their options, and the authenticated user
111+
* callback) from one broadcaster to another. A freshly resolved broadcaster has no auth state,
112+
* so without the copying, channel auth and user auth would stop working (403) in tenant context.
113+
*
114+
* The auth state is stored on the abstract Broadcaster class, not in the Broadcaster
115+
* contract, and it's stored in protected properties. Because of that, we have
116+
* to check that both broadcasters are instances of the abstract Broadcaster class and
117+
* use invade() to access the protected properties (for the $channels property, there
118+
* is a public accessor -- getChannels() -- but since invade is already used here,
119+
* we access the property directly for consistency).
120+
*/
121+
protected function copyAuthState(BroadcasterContract $from, BroadcasterContract $to): void
122+
{
123+
if (! $from instanceof Broadcaster || ! $to instanceof Broadcaster) {
124+
return;
125+
}
126+
127+
$fromState = invade($from);
128+
$toState = invade($to);
129+
130+
$toState->channels = $fromState->channels;
131+
$toState->channelOptions = $fromState->channelOptions;
132+
$toState->authenticatedUserCallback = $fromState->authenticatedUserCallback;
133+
}
134+
125135
public function revert(): void
126136
{
127137
// Revert the bound BroadcastManager and Broadcaster singletons back to their original state

0 commit comments

Comments
 (0)