Broadcaster.php 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. namespace Illuminate\Broadcasting\Broadcasters;
  3. use Exception;
  4. use ReflectionClass;
  5. use ReflectionFunction;
  6. use Illuminate\Support\Str;
  7. use Illuminate\Container\Container;
  8. use Illuminate\Contracts\Routing\UrlRoutable;
  9. use Illuminate\Contracts\Routing\BindingRegistrar;
  10. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  11. use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
  12. abstract class Broadcaster implements BroadcasterContract
  13. {
  14. /**
  15. * The registered channel authenticators.
  16. *
  17. * @var array
  18. */
  19. protected $channels = [];
  20. /**
  21. * The binding registrar instance.
  22. *
  23. * @var BindingRegistrar
  24. */
  25. protected $bindingRegistrar;
  26. /**
  27. * Register a channel authenticator.
  28. *
  29. * @param string $channel
  30. * @param callable|string $callback
  31. * @return $this
  32. */
  33. public function channel($channel, $callback)
  34. {
  35. $this->channels[$channel] = $callback;
  36. return $this;
  37. }
  38. /**
  39. * Authenticate the incoming request for a given channel.
  40. *
  41. * @param \Illuminate\Http\Request $request
  42. * @param string $channel
  43. * @return mixed
  44. * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
  45. */
  46. protected function verifyUserCanAccessChannel($request, $channel)
  47. {
  48. foreach ($this->channels as $pattern => $callback) {
  49. if (! Str::is(preg_replace('/\{(.*?)\}/', '*', $pattern), $channel)) {
  50. continue;
  51. }
  52. $parameters = $this->extractAuthParameters($pattern, $channel, $callback);
  53. $handler = $this->normalizeChannelHandlerToCallable($callback);
  54. if ($result = $handler($request->user(), ...$parameters)) {
  55. return $this->validAuthenticationResponse($request, $result);
  56. }
  57. }
  58. throw new AccessDeniedHttpException;
  59. }
  60. /**
  61. * Extract the parameters from the given pattern and channel.
  62. *
  63. * @param string $pattern
  64. * @param string $channel
  65. * @param callable|string $callback
  66. * @return array
  67. */
  68. protected function extractAuthParameters($pattern, $channel, $callback)
  69. {
  70. $callbackParameters = $this->extractParameters($callback);
  71. return collect($this->extractChannelKeys($pattern, $channel))->reject(function ($value, $key) {
  72. return is_numeric($key);
  73. })->map(function ($value, $key) use ($callbackParameters) {
  74. return $this->resolveBinding($key, $value, $callbackParameters);
  75. })->values()->all();
  76. }
  77. /**
  78. * Extracts the parameters out of what the user passed to handle the channel authentication.
  79. *
  80. * @param callable|string $callback
  81. * @return \ReflectionParameter[]
  82. * @throws \Exception
  83. */
  84. protected function extractParameters($callback)
  85. {
  86. if (is_callable($callback)) {
  87. return (new ReflectionFunction($callback))->getParameters();
  88. } elseif (is_string($callback)) {
  89. return $this->extractParametersFromClass($callback);
  90. }
  91. throw new Exception('Given channel handler is an unknown type.');
  92. }
  93. /**
  94. * Extracts the parameters out of a class channel's "join" method.
  95. *
  96. * @param string $callback
  97. * @return \ReflectionParameter[]
  98. * @throws \Exception
  99. */
  100. protected function extractParametersFromClass($callback)
  101. {
  102. $reflection = new ReflectionClass($callback);
  103. if (! $reflection->hasMethod('join')) {
  104. throw new Exception('Class based channel must define a "join" method.');
  105. }
  106. return $reflection->getMethod('join')->getParameters();
  107. }
  108. /**
  109. * Extract the channel keys from the incoming channel name.
  110. *
  111. * @param string $pattern
  112. * @param string $channel
  113. * @return array
  114. */
  115. protected function extractChannelKeys($pattern, $channel)
  116. {
  117. preg_match('/^'.preg_replace('/\{(.*?)\}/', '(?<$1>[^\.]+)', $pattern).'/', $channel, $keys);
  118. return $keys;
  119. }
  120. /**
  121. * Resolve the given parameter binding.
  122. *
  123. * @param string $key
  124. * @param string $value
  125. * @param array $callbackParameters
  126. * @return mixed
  127. */
  128. protected function resolveBinding($key, $value, $callbackParameters)
  129. {
  130. $newValue = $this->resolveExplicitBindingIfPossible($key, $value);
  131. return $newValue === $value ? $this->resolveImplicitBindingIfPossible(
  132. $key, $value, $callbackParameters
  133. ) : $newValue;
  134. }
  135. /**
  136. * Resolve an explicit parameter binding if applicable.
  137. *
  138. * @param string $key
  139. * @param mixed $value
  140. * @return mixed
  141. */
  142. protected function resolveExplicitBindingIfPossible($key, $value)
  143. {
  144. $binder = $this->binder();
  145. if ($binder && $binder->getBindingCallback($key)) {
  146. return call_user_func($binder->getBindingCallback($key), $value);
  147. }
  148. return $value;
  149. }
  150. /**
  151. * Resolve an implicit parameter binding if applicable.
  152. *
  153. * @param string $key
  154. * @param mixed $value
  155. * @param array $callbackParameters
  156. * @return mixed
  157. * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
  158. */
  159. protected function resolveImplicitBindingIfPossible($key, $value, $callbackParameters)
  160. {
  161. foreach ($callbackParameters as $parameter) {
  162. if (! $this->isImplicitlyBindable($key, $parameter)) {
  163. continue;
  164. }
  165. $instance = $parameter->getClass()->newInstance();
  166. if (! $model = $instance->resolveRouteBinding($value)) {
  167. throw new AccessDeniedHttpException;
  168. }
  169. return $model;
  170. }
  171. return $value;
  172. }
  173. /**
  174. * Determine if a given key and parameter is implicitly bindable.
  175. *
  176. * @param string $key
  177. * @param \ReflectionParameter $parameter
  178. * @return bool
  179. */
  180. protected function isImplicitlyBindable($key, $parameter)
  181. {
  182. return $parameter->name === $key && $parameter->getClass() &&
  183. $parameter->getClass()->isSubclassOf(UrlRoutable::class);
  184. }
  185. /**
  186. * Format the channel array into an array of strings.
  187. *
  188. * @param array $channels
  189. * @return array
  190. */
  191. protected function formatChannels(array $channels)
  192. {
  193. return array_map(function ($channel) {
  194. return (string) $channel;
  195. }, $channels);
  196. }
  197. /**
  198. * Get the model binding registrar instance.
  199. *
  200. * @return \Illuminate\Contracts\Routing\BindingRegistrar
  201. */
  202. protected function binder()
  203. {
  204. if (! $this->bindingRegistrar) {
  205. $this->bindingRegistrar = Container::getInstance()->bound(BindingRegistrar::class)
  206. ? Container::getInstance()->make(BindingRegistrar::class) : null;
  207. }
  208. return $this->bindingRegistrar;
  209. }
  210. /**
  211. * Normalize the given callback into a callable.
  212. *
  213. * @param mixed $callback
  214. * @return callable|\Closure
  215. */
  216. protected function normalizeChannelHandlerToCallable($callback)
  217. {
  218. return is_callable($callback) ? $callback : function (...$args) use ($callback) {
  219. return Container::getInstance()
  220. ->make($callback)
  221. ->join(...$args);
  222. };
  223. }
  224. }