123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- <?php
-
- namespace Illuminate\Broadcasting\Broadcasters;
-
- use Exception;
- use ReflectionClass;
- use ReflectionFunction;
- use Illuminate\Support\Str;
- use Illuminate\Container\Container;
- use Illuminate\Contracts\Routing\UrlRoutable;
- use Illuminate\Contracts\Routing\BindingRegistrar;
- use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
- use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
-
- abstract class Broadcaster implements BroadcasterContract
- {
- /**
- * The registered channel authenticators.
- *
- * @var array
- */
- protected $channels = [];
-
- /**
- * The binding registrar instance.
- *
- * @var BindingRegistrar
- */
- protected $bindingRegistrar;
-
- /**
- * Register a channel authenticator.
- *
- * @param string $channel
- * @param callable|string $callback
- * @return $this
- */
- public function channel($channel, $callback)
- {
- $this->channels[$channel] = $callback;
-
- return $this;
- }
-
- /**
- * Authenticate the incoming request for a given channel.
- *
- * @param \Illuminate\Http\Request $request
- * @param string $channel
- * @return mixed
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- */
- protected function verifyUserCanAccessChannel($request, $channel)
- {
- foreach ($this->channels as $pattern => $callback) {
- if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
- continue;
- }
-
- $parameters = $this->extractAuthParameters($pattern, $channel, $callback);
-
- $handler = $this->normalizeChannelHandlerToCallable($callback);
-
- if ($result = $handler($request->user(), ...$parameters)) {
- return $this->validAuthenticationResponse($request, $result);
- }
- }
-
- throw new AccessDeniedHttpException;
- }
-
- /**
- * Extract the parameters from the given pattern and channel.
- *
- * @param string $pattern
- * @param string $channel
- * @param callable|string $callback
- * @return array
- */
- protected function extractAuthParameters($pattern, $channel, $callback)
- {
- $callbackParameters = $this->extractParameters($callback);
-
- return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
- return is_numeric($key);
- })->map(function ($value, $key) use ($callbackParameters) {
- return $this->resolveBinding($key, $value, $callbackParameters);
- })->values()->all();
- }
-
- /**
- * Extracts the parameters out of what the user passed to handle the channel authentication.
- *
- * @param callable|string $callback
- * @return \ReflectionParameter[]
- * @throws \Exception
- */
- protected function extractParameters($callback)
- {
- if (is_callable($callback)) {
- return (new ReflectionFunction($callback))->getParameters();
- } elseif (is_string($callback)) {
- return $this->extractParametersFromClass($callback);
- }
-
- throw new Exception('Given channel handler is an unknown type.');
- }
-
- /**
- * Extracts the parameters out of a class channel's "join" method.
- *
- * @param string $callback
- * @return \ReflectionParameter[]
- * @throws \Exception
- */
- protected function extractParametersFromClass($callback)
- {
- $reflection = new ReflectionClass($callback);
-
- if (! $reflection->hasMethod('join')) {
- throw new Exception('Class based channel must define a "join" method.');
- }
-
- return $reflection->getMethod('join')->getParameters();
- }
-
- /**
- * Extract the channel keys from the incoming channel name.
- *
- * @param string $pattern
- * @param string $channel
- * @return array
- */
- protected function extractChannelKeys($pattern, $channel)
- {
- preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
-
- return $keys;
- }
-
- /**
- * Resolve the given parameter binding.
- *
- * @param string $key
- * @param string $value
- * @param array $callbackParameters
- * @return mixed
- */
- protected function resolveBinding($key, $value, $callbackParameters)
- {
- $newValue = $this->resolveExplicitBindingIfPossible($key, $value);
-
- return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
- $key, $value, $callbackParameters
- ) : $newValue;
- }
-
- /**
- * Resolve an explicit parameter binding if applicable.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function resolveExplicitBindingIfPossible($key, $value)
- {
- $binder = $this->binder();
-
- if ($binder && $binder->getBindingCallback($key)) {
- return call_user_func($binder->getBindingCallback($key), $value);
- }
-
- return $value;
- }
-
- /**
- * Resolve an implicit parameter binding if applicable.
- *
- * @param string $key
- * @param mixed $value
- * @param array $callbackParameters
- * @return mixed
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- */
- protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
- {
- foreach ($callbackParameters as $parameter) {
- if (! $this->isImplicitlyBindable($key, $parameter)) {
- continue;
- }
-
- $instance = $parameter->getClass()->newInstance();
-
- if (! $model = $instance->resolveRouteBinding($value)) {
- throw new AccessDeniedHttpException;
- }
-
- return $model;
- }
-
- return $value;
- }
-
- /**
- * Determine if a given key and parameter is implicitly bindable.
- *
- * @param string $key
- * @param \ReflectionParameter $parameter
- * @return bool
- */
- protected function isImplicitlyBindable($key, $parameter)
- {
- return $parameter->name === $key && $parameter->getClass() &&
- $parameter->getClass()->isSubclassOf(UrlRoutable::class);
- }
-
- /**
- * Format the channel array into an array of strings.
- *
- * @param array $channels
- * @return array
- */
- protected function formatChannels(array $channels)
- {
- return array_map(function ($channel) {
- return (string) $channel;
- }, $channels);
- }
-
- /**
- * Get the model binding registrar instance.
- *
- * @return \Illuminate\Contracts\Routing\BindingRegistrar
- */
- protected function binder()
- {
- if (! $this->bindingRegistrar) {
- $this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
- ? Container::getInstance()->make(BindingRegistrar::class) : null;
- }
-
- return $this->bindingRegistrar;
- }
-
- /**
- * Normalize the given callback into a callable.
- *
- * @param mixed $callback
- * @return callable|\Closure
- */
- protected function normalizeChannelHandlerToCallable($callback)
- {
- return is_callable($callback) ? $callback : function (...$args) use ($callback) {
- return Container::getInstance()
- ->make($callback)
- ->join(...$args);
- };
- }
- }
|