Gate.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <?php
  2. namespace Illuminate\Auth\Access;
  3. use Illuminate\Support\Arr;
  4. use Illuminate\Support\Str;
  5. use InvalidArgumentException;
  6. use Illuminate\Contracts\Container\Container;
  7. use Illuminate\Contracts\Auth\Access\Gate as GateContract;
  8. class Gate implements GateContract
  9. {
  10. use HandlesAuthorization;
  11. /**
  12. * The container instance.
  13. *
  14. * @var \Illuminate\Contracts\Container\Container
  15. */
  16. protected $container;
  17. /**
  18. * The user resolver callable.
  19. *
  20. * @var callable
  21. */
  22. protected $userResolver;
  23. /**
  24. * All of the defined abilities.
  25. *
  26. * @var array
  27. */
  28. protected $abilities = [];
  29. /**
  30. * All of the defined policies.
  31. *
  32. * @var array
  33. */
  34. protected $policies = [];
  35. /**
  36. * All of the registered before callbacks.
  37. *
  38. * @var array
  39. */
  40. protected $beforeCallbacks = [];
  41. /**
  42. * All of the registered after callbacks.
  43. *
  44. * @var array
  45. */
  46. protected $afterCallbacks = [];
  47. /**
  48. * Create a new gate instance.
  49. *
  50. * @param \Illuminate\Contracts\Container\Container $container
  51. * @param callable $userResolver
  52. * @param array $abilities
  53. * @param array $policies
  54. * @param array $beforeCallbacks
  55. * @param array $afterCallbacks
  56. * @return void
  57. */
  58. public function __construct(Container $container, callable $userResolver, array $abilities = [],
  59. array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [])
  60. {
  61. $this->policies = $policies;
  62. $this->container = $container;
  63. $this->abilities = $abilities;
  64. $this->userResolver = $userResolver;
  65. $this->afterCallbacks = $afterCallbacks;
  66. $this->beforeCallbacks = $beforeCallbacks;
  67. }
  68. /**
  69. * Determine if a given ability has been defined.
  70. *
  71. * @param string|array $ability
  72. * @return bool
  73. */
  74. public function has($ability)
  75. {
  76. $abilities = is_array($ability) ? $ability : func_get_args();
  77. foreach ($abilities as $ability) {
  78. if (! isset($this->abilities[$ability])) {
  79. return false;
  80. }
  81. }
  82. return true;
  83. }
  84. /**
  85. * Define a new ability.
  86. *
  87. * @param string $ability
  88. * @param callable|string $callback
  89. * @return $this
  90. *
  91. * @throws \InvalidArgumentException
  92. */
  93. public function define($ability, $callback)
  94. {
  95. if (is_callable($callback)) {
  96. $this->abilities[$ability] = $callback;
  97. } elseif (is_string($callback) && Str::contains($callback, '@')) {
  98. $this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback);
  99. } else {
  100. throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string.");
  101. }
  102. return $this;
  103. }
  104. /**
  105. * Define abilities for a resource.
  106. *
  107. * @param string $name
  108. * @param string $class
  109. * @param array|null $abilities
  110. * @return $this
  111. */
  112. public function resource($name, $class, array $abilities = null)
  113. {
  114. $abilities = $abilities ?: [
  115. 'view' => 'view',
  116. 'create' => 'create',
  117. 'update' => 'update',
  118. 'delete' => 'delete',
  119. ];
  120. foreach ($abilities as $ability => $method) {
  121. $this->define($name.'.'.$ability, $class.'@'.$method);
  122. }
  123. return $this;
  124. }
  125. /**
  126. * Create the ability callback for a callback string.
  127. *
  128. * @param string $ability
  129. * @param string $callback
  130. * @return \Closure
  131. */
  132. protected function buildAbilityCallback($ability, $callback)
  133. {
  134. return function () use ($ability, $callback) {
  135. list($class, $method) = Str::parseCallback($callback);
  136. $policy = $this->resolvePolicy($class);
  137. $arguments = func_get_args();
  138. $user = array_shift($arguments);
  139. $result = $this->callPolicyBefore(
  140. $policy, $user, $ability, $arguments
  141. );
  142. if (! is_null($result)) {
  143. return $result;
  144. }
  145. return $policy->{$method}(...func_get_args());
  146. };
  147. }
  148. /**
  149. * Define a policy class for a given class type.
  150. *
  151. * @param string $class
  152. * @param string $policy
  153. * @return $this
  154. */
  155. public function policy($class, $policy)
  156. {
  157. $this->policies[$class] = $policy;
  158. return $this;
  159. }
  160. /**
  161. * Register a callback to run before all Gate checks.
  162. *
  163. * @param callable $callback
  164. * @return $this
  165. */
  166. public function before(callable $callback)
  167. {
  168. $this->beforeCallbacks[] = $callback;
  169. return $this;
  170. }
  171. /**
  172. * Register a callback to run after all Gate checks.
  173. *
  174. * @param callable $callback
  175. * @return $this
  176. */
  177. public function after(callable $callback)
  178. {
  179. $this->afterCallbacks[] = $callback;
  180. return $this;
  181. }
  182. /**
  183. * Determine if the given ability should be granted for the current user.
  184. *
  185. * @param string $ability
  186. * @param array|mixed $arguments
  187. * @return bool
  188. */
  189. public function allows($ability, $arguments = [])
  190. {
  191. return $this->check($ability, $arguments);
  192. }
  193. /**
  194. * Determine if the given ability should be denied for the current user.
  195. *
  196. * @param string $ability
  197. * @param array|mixed $arguments
  198. * @return bool
  199. */
  200. public function denies($ability, $arguments = [])
  201. {
  202. return ! $this->allows($ability, $arguments);
  203. }
  204. /**
  205. * Determine if all of the given abilities should be granted for the current user.
  206. *
  207. * @param iterable|string $abilities
  208. * @param array|mixed $arguments
  209. * @return bool
  210. */
  211. public function check($abilities, $arguments = [])
  212. {
  213. return collect($abilities)->every(function ($ability) use ($arguments) {
  214. try {
  215. return (bool) $this->raw($ability, $arguments);
  216. } catch (AuthorizationException $e) {
  217. return false;
  218. }
  219. });
  220. }
  221. /**
  222. * Determine if any one of the given abilities should be granted for the current user.
  223. *
  224. * @param iterable|string $abilities
  225. * @param array|mixed $arguments
  226. * @return bool
  227. */
  228. public function any($abilities, $arguments = [])
  229. {
  230. return collect($abilities)->contains(function ($ability) use ($arguments) {
  231. return $this->check($ability, $arguments);
  232. });
  233. }
  234. /**
  235. * Determine if the given ability should be granted for the current user.
  236. *
  237. * @param string $ability
  238. * @param array|mixed $arguments
  239. * @return \Illuminate\Auth\Access\Response
  240. *
  241. * @throws \Illuminate\Auth\Access\AuthorizationException
  242. */
  243. public function authorize($ability, $arguments = [])
  244. {
  245. $result = $this->raw($ability, $arguments);
  246. if ($result instanceof Response) {
  247. return $result;
  248. }
  249. return $result ? $this->allow() : $this->deny();
  250. }
  251. /**
  252. * Get the raw result from the authorization callback.
  253. *
  254. * @param string $ability
  255. * @param array|mixed $arguments
  256. * @return mixed
  257. */
  258. protected function raw($ability, $arguments = [])
  259. {
  260. if (! $user = $this->resolveUser()) {
  261. return false;
  262. }
  263. $arguments = Arr::wrap($arguments);
  264. // First we will call the "before" callbacks for the Gate. If any of these give
  265. // back a non-null response, we will immediately return that result in order
  266. // to let the developers override all checks for some authorization cases.
  267. $result = $this->callBeforeCallbacks(
  268. $user, $ability, $arguments
  269. );
  270. if (is_null($result)) {
  271. $result = $this->callAuthCallback($user, $ability, $arguments);
  272. }
  273. // After calling the authorization callback, we will call the "after" callbacks
  274. // that are registered with the Gate, which allows a developer to do logging
  275. // if that is required for this application. Then we'll return the result.
  276. $this->callAfterCallbacks(
  277. $user, $ability, $arguments, $result
  278. );
  279. return $result;
  280. }
  281. /**
  282. * Resolve and call the appropriate authorization callback.
  283. *
  284. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  285. * @param string $ability
  286. * @param array $arguments
  287. * @return bool
  288. */
  289. protected function callAuthCallback($user, $ability, array $arguments)
  290. {
  291. $callback = $this->resolveAuthCallback($user, $ability, $arguments);
  292. return $callback($user, ...$arguments);
  293. }
  294. /**
  295. * Call all of the before callbacks and return if a result is given.
  296. *
  297. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  298. * @param string $ability
  299. * @param array $arguments
  300. * @return bool|null
  301. */
  302. protected function callBeforeCallbacks($user, $ability, array $arguments)
  303. {
  304. $arguments = array_merge([$user, $ability], [$arguments]);
  305. foreach ($this->beforeCallbacks as $before) {
  306. if (! is_null($result = $before(...$arguments))) {
  307. return $result;
  308. }
  309. }
  310. }
  311. /**
  312. * Call all of the after callbacks with check result.
  313. *
  314. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  315. * @param string $ability
  316. * @param array $arguments
  317. * @param bool $result
  318. * @return void
  319. */
  320. protected function callAfterCallbacks($user, $ability, array $arguments, $result)
  321. {
  322. $arguments = array_merge([$user, $ability, $result], [$arguments]);
  323. foreach ($this->afterCallbacks as $after) {
  324. $after(...$arguments);
  325. }
  326. }
  327. /**
  328. * Resolve the callable for the given ability and arguments.
  329. *
  330. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  331. * @param string $ability
  332. * @param array $arguments
  333. * @return callable
  334. */
  335. protected function resolveAuthCallback($user, $ability, array $arguments)
  336. {
  337. if (isset($arguments[0]) &&
  338. ! is_null($policy = $this->getPolicyFor($arguments[0])) &&
  339. $callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) {
  340. return $callback;
  341. }
  342. if (isset($this->abilities[$ability])) {
  343. return $this->abilities[$ability];
  344. }
  345. return function () {
  346. return false;
  347. };
  348. }
  349. /**
  350. * Get a policy instance for a given class.
  351. *
  352. * @param object|string $class
  353. * @return mixed
  354. */
  355. public function getPolicyFor($class)
  356. {
  357. if (is_object($class)) {
  358. $class = get_class($class);
  359. }
  360. if (! is_string($class)) {
  361. return;
  362. }
  363. if (isset($this->policies[$class])) {
  364. return $this->resolvePolicy($this->policies[$class]);
  365. }
  366. foreach ($this->policies as $expected => $policy) {
  367. if (is_subclass_of($class, $expected)) {
  368. return $this->resolvePolicy($policy);
  369. }
  370. }
  371. }
  372. /**
  373. * Build a policy class instance of the given type.
  374. *
  375. * @param object|string $class
  376. * @return mixed
  377. */
  378. public function resolvePolicy($class)
  379. {
  380. return $this->container->make($class);
  381. }
  382. /**
  383. * Resolve the callback for a policy check.
  384. *
  385. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  386. * @param string $ability
  387. * @param array $arguments
  388. * @param mixed $policy
  389. * @return bool|callable
  390. */
  391. protected function resolvePolicyCallback($user, $ability, array $arguments, $policy)
  392. {
  393. if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) {
  394. return false;
  395. }
  396. return function () use ($user, $ability, $arguments, $policy) {
  397. // This callback will be responsible for calling the policy's before method and
  398. // running this policy method if necessary. This is used to when objects are
  399. // mapped to policy objects in the user's configurations or on this class.
  400. $result = $this->callPolicyBefore(
  401. $policy, $user, $ability, $arguments
  402. );
  403. // When we receive a non-null result from this before method, we will return it
  404. // as the "final" results. This will allow developers to override the checks
  405. // in this policy to return the result for all rules defined in the class.
  406. if (! is_null($result)) {
  407. return $result;
  408. }
  409. $ability = $this->formatAbilityToMethod($ability);
  410. // If this first argument is a string, that means they are passing a class name
  411. // to the policy. We will remove the first argument from this argument array
  412. // because this policy already knows what type of models it can authorize.
  413. if (isset($arguments[0]) && is_string($arguments[0])) {
  414. array_shift($arguments);
  415. }
  416. return is_callable([$policy, $ability])
  417. ? $policy->{$ability}($user, ...$arguments)
  418. : false;
  419. };
  420. }
  421. /**
  422. * Call the "before" method on the given policy, if applicable.
  423. *
  424. * @param mixed $policy
  425. * @param \Illuminate\Contracts\Auth\Authenticatable $user
  426. * @param string $ability
  427. * @param array $arguments
  428. * @return mixed
  429. */
  430. protected function callPolicyBefore($policy, $user, $ability, $arguments)
  431. {
  432. if (method_exists($policy, 'before')) {
  433. return $policy->before($user, $ability, ...$arguments);
  434. }
  435. }
  436. /**
  437. * Format the policy ability into a method name.
  438. *
  439. * @param string $ability
  440. * @return string
  441. */
  442. protected function formatAbilityToMethod($ability)
  443. {
  444. return strpos($ability, '-') !== false ? Str::camel($ability) : $ability;
  445. }
  446. /**
  447. * Get a gate instance for the given user.
  448. *
  449. * @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user
  450. * @return static
  451. */
  452. public function forUser($user)
  453. {
  454. $callback = function () use ($user) {
  455. return $user;
  456. };
  457. return new static(
  458. $this->container, $callback, $this->abilities,
  459. $this->policies, $this->beforeCallbacks, $this->afterCallbacks
  460. );
  461. }
  462. /**
  463. * Resolve the user from the user resolver.
  464. *
  465. * @return mixed
  466. */
  467. protected function resolveUser()
  468. {
  469. return call_user_func($this->userResolver);
  470. }
  471. /**
  472. * Get all of the defined abilities.
  473. *
  474. * @return array
  475. */
  476. public function abilities()
  477. {
  478. return $this->abilities;
  479. }
  480. /**
  481. * Get all of the defined policies.
  482. *
  483. * @return array
  484. */
  485. public function policies()
  486. {
  487. return $this->policies;
  488. }
  489. }