Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Broadcasting\Broadcasters;
4
 
5
use Closure;
6
use Exception;
7
use Illuminate\Container\Container;
8
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
9
use Illuminate\Contracts\Broadcasting\HasBroadcastChannel;
10
use Illuminate\Contracts\Routing\BindingRegistrar;
11
use Illuminate\Contracts\Routing\UrlRoutable;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Reflector;
14
use ReflectionClass;
15
use ReflectionFunction;
16
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
17
 
18
abstract class Broadcaster implements BroadcasterContract
19
{
20
    /**
21
     * The callback to resolve the authenticated user information.
22
     *
23
     * @var \Closure|null
24
     */
25
    protected $authenticatedUserCallback = null;
26
 
27
    /**
28
     * The registered channel authenticators.
29
     *
30
     * @var array
31
     */
32
    protected $channels = [];
33
 
34
    /**
35
     * The registered channel options.
36
     *
37
     * @var array
38
     */
39
    protected $channelOptions = [];
40
 
41
    /**
42
     * The binding registrar instance.
43
     *
44
     * @var \Illuminate\Contracts\Routing\BindingRegistrar
45
     */
46
    protected $bindingRegistrar;
47
 
48
    /**
49
     * Resolve the authenticated user payload for the incoming connection request.
50
     *
51
     * See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
52
     *
53
     * @param  \Illuminate\Http\Request  $request
54
     * @return array|null
55
     */
56
    public function resolveAuthenticatedUser($request)
57
    {
58
        if ($this->authenticatedUserCallback) {
59
            return $this->authenticatedUserCallback->__invoke($request);
60
        }
61
    }
62
 
63
    /**
64
     * Register the user retrieval callback used to authenticate connections.
65
     *
66
     * See: https://pusher.com/docs/channels/library_auth_reference/auth-signatures/#user-authentication.
67
     *
68
     * @param  \Closure  $callback
69
     * @return void
70
     */
71
    public function resolveAuthenticatedUserUsing(Closure $callback)
72
    {
73
        $this->authenticatedUserCallback = $callback;
74
    }
75
 
76
    /**
77
     * Register a channel authenticator.
78
     *
79
     * @param  \Illuminate\Contracts\Broadcasting\HasBroadcastChannel|string  $channel
80
     * @param  callable|string  $callback
81
     * @param  array  $options
82
     * @return $this
83
     */
84
    public function channel($channel, $callback, $options = [])
85
    {
86
        if ($channel instanceof HasBroadcastChannel) {
87
            $channel = $channel->broadcastChannelRoute();
88
        } elseif (is_string($channel) && class_exists($channel) && is_a($channel, HasBroadcastChannel::class, true)) {
89
            $channel = (new $channel)->broadcastChannelRoute();
90
        }
91
 
92
        $this->channels[$channel] = $callback;
93
 
94
        $this->channelOptions[$channel] = $options;
95
 
96
        return $this;
97
    }
98
 
99
    /**
100
     * Authenticate the incoming request for a given channel.
101
     *
102
     * @param  \Illuminate\Http\Request  $request
103
     * @param  string  $channel
104
     * @return mixed
105
     *
106
     * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
107
     */
108
    protected function verifyUserCanAccessChannel($request, $channel)
109
    {
110
        foreach ($this->channels as $pattern => $callback) {
111
            if (! $this->channelNameMatchesPattern($channel, $pattern)) {
112
                continue;
113
            }
114
 
115
            $parameters = $this->extractAuthParameters($pattern, $channel, $callback);
116
 
117
            $handler = $this->normalizeChannelHandlerToCallable($callback);
118
 
119
            $result = $handler($this->retrieveUser($request, $channel), ...$parameters);
120
 
121
            if ($result === false) {
122
                throw new AccessDeniedHttpException;
123
            } elseif ($result) {
124
                return $this->validAuthenticationResponse($request, $result);
125
            }
126
        }
127
 
128
        throw new AccessDeniedHttpException;
129
    }
130
 
131
    /**
132
     * Extract the parameters from the given pattern and channel.
133
     *
134
     * @param  string  $pattern
135
     * @param  string  $channel
136
     * @param  callable|string  $callback
137
     * @return array
138
     */
139
    protected function extractAuthParameters($pattern, $channel, $callback)
140
    {
141
        $callbackParameters = $this->extractParameters($callback);
142
 
143
        return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
144
            return is_numeric($key);
145
        })->map(function ($value, $key) use ($callbackParameters) {
146
            return $this->resolveBinding($key, $value, $callbackParameters);
147
        })->values()->all();
148
    }
149
 
150
    /**
151
     * Extracts the parameters out of what the user passed to handle the channel authentication.
152
     *
153
     * @param  callable|string  $callback
154
     * @return \ReflectionParameter[]
155
     *
156
     * @throws \Exception
157
     */
158
    protected function extractParameters($callback)
159
    {
160
        if (is_callable($callback)) {
161
            return (new ReflectionFunction($callback))->getParameters();
162
        } elseif (is_string($callback)) {
163
            return $this->extractParametersFromClass($callback);
164
        }
165
 
166
        throw new Exception('Given channel handler is an unknown type.');
167
    }
168
 
169
    /**
170
     * Extracts the parameters out of a class channel's "join" method.
171
     *
172
     * @param  string  $callback
173
     * @return \ReflectionParameter[]
174
     *
175
     * @throws \Exception
176
     */
177
    protected function extractParametersFromClass($callback)
178
    {
179
        $reflection = new ReflectionClass($callback);
180
 
181
        if (! $reflection->hasMethod('join')) {
182
            throw new Exception('Class based channel must define a "join" method.');
183
        }
184
 
185
        return $reflection->getMethod('join')->getParameters();
186
    }
187
 
188
    /**
189
     * Extract the channel keys from the incoming channel name.
190
     *
191
     * @param  string  $pattern
192
     * @param  string  $channel
193
     * @return array
194
     */
195
    protected function extractChannelKeys($pattern, $channel)
196
    {
197
        preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
198
 
199
        return $keys;
200
    }
201
 
202
    /**
203
     * Resolve the given parameter binding.
204
     *
205
     * @param  string  $key
206
     * @param  string  $value
207
     * @param  array  $callbackParameters
208
     * @return mixed
209
     */
210
    protected function resolveBinding($key, $value, $callbackParameters)
211
    {
212
        $newValue = $this->resolveExplicitBindingIfPossible($key, $value);
213
 
214
        return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
215
            $key, $value, $callbackParameters
216
        ) : $newValue;
217
    }
218
 
219
    /**
220
     * Resolve an explicit parameter binding if applicable.
221
     *
222
     * @param  string  $key
223
     * @param  mixed  $value
224
     * @return mixed
225
     */
226
    protected function resolveExplicitBindingIfPossible($key, $value)
227
    {
228
        $binder = $this->binder();
229
 
230
        if ($binder && $binder->getBindingCallback($key)) {
231
            return call_user_func($binder->getBindingCallback($key), $value);
232
        }
233
 
234
        return $value;
235
    }
236
 
237
    /**
238
     * Resolve an implicit parameter binding if applicable.
239
     *
240
     * @param  string  $key
241
     * @param  mixed  $value
242
     * @param  array  $callbackParameters
243
     * @return mixed
244
     *
245
     * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
246
     */
247
    protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
248
    {
249
        foreach ($callbackParameters as $parameter) {
250
            if (! $this->isImplicitlyBindable($key, $parameter)) {
251
                continue;
252
            }
253
 
254
            $className = Reflector::getParameterClassName($parameter);
255
 
256
            if (is_null($model = (new $className)->resolveRouteBinding($value))) {
257
                throw new AccessDeniedHttpException;
258
            }
259
 
260
            return $model;
261
        }
262
 
263
        return $value;
264
    }
265
 
266
    /**
267
     * Determine if a given key and parameter is implicitly bindable.
268
     *
269
     * @param  string  $key
270
     * @param  \ReflectionParameter  $parameter
271
     * @return bool
272
     */
273
    protected function isImplicitlyBindable($key, $parameter)
274
    {
275
        return $parameter->getName() === $key &&
276
                        Reflector::isParameterSubclassOf($parameter, UrlRoutable::class);
277
    }
278
 
279
    /**
280
     * Format the channel array into an array of strings.
281
     *
282
     * @param  array  $channels
283
     * @return array
284
     */
285
    protected function formatChannels(array $channels)
286
    {
287
        return array_map(function ($channel) {
288
            return (string) $channel;
289
        }, $channels);
290
    }
291
 
292
    /**
293
     * Get the model binding registrar instance.
294
     *
295
     * @return \Illuminate\Contracts\Routing\BindingRegistrar
296
     */
297
    protected function binder()
298
    {
299
        if (! $this->bindingRegistrar) {
300
            $this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
301
                        ? Container::getInstance()->make(BindingRegistrar::class) : null;
302
        }
303
 
304
        return $this->bindingRegistrar;
305
    }
306
 
307
    /**
308
     * Normalize the given callback into a callable.
309
     *
310
     * @param  mixed  $callback
311
     * @return callable
312
     */
313
    protected function normalizeChannelHandlerToCallable($callback)
314
    {
315
        return is_callable($callback) ? $callback : function (...$args) use ($callback) {
316
            return Container::getInstance()
317
                ->make($callback)
318
                ->join(...$args);
319
        };
320
    }
321
 
322
    /**
323
     * Retrieve the authenticated user using the configured guard (if any).
324
     *
325
     * @param  \Illuminate\Http\Request  $request
326
     * @param  string  $channel
327
     * @return mixed
328
     */
329
    protected function retrieveUser($request, $channel)
330
    {
331
        $options = $this->retrieveChannelOptions($channel);
332
 
333
        $guards = $options['guards'] ?? null;
334
 
335
        if (is_null($guards)) {
336
            return $request->user();
337
        }
338
 
339
        foreach (Arr::wrap($guards) as $guard) {
340
            if ($user = $request->user($guard)) {
341
                return $user;
342
            }
343
        }
344
    }
345
 
346
    /**
347
     * Retrieve options for a certain channel.
348
     *
349
     * @param  string  $channel
350
     * @return array
351
     */
352
    protected function retrieveChannelOptions($channel)
353
    {
354
        foreach ($this->channelOptions as $pattern => $options) {
355
            if (! $this->channelNameMatchesPattern($channel, $pattern)) {
356
                continue;
357
            }
358
 
359
            return $options;
360
        }
361
 
362
        return [];
363
    }
364
 
365
    /**
366
     * Check if the channel name from the request matches a pattern from registered channels.
367
     *
368
     * @param  string  $channel
369
     * @param  string  $pattern
370
     * @return bool
371
     */
372
    protected function channelNameMatchesPattern($channel, $pattern)
373
    {
399 lars 374
        return preg_match('/^'.preg_replace('/\{(.*?)\}/', '([^\.]+)', $pattern).'$/', $channel);
148 lars 375
    }
376
}