Viewing file: Gate.php (24.64 KB) -rwxrwxr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
namespace Illuminate\Auth\Access;
use Closure; use Exception; use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; use InvalidArgumentException; use ReflectionClass; use ReflectionFunction;
class Gate implements GateContract { use HandlesAuthorization;
/** * The container instance. * * @var \Illuminate\Contracts\Container\Container */ protected $container;
/** * The user resolver callable. * * @var callable */ protected $userResolver;
/** * All of the defined abilities. * * @var array */ protected $abilities = [];
/** * All of the defined policies. * * @var array */ protected $policies = [];
/** * All of the registered before callbacks. * * @var array */ protected $beforeCallbacks = [];
/** * All of the registered after callbacks. * * @var array */ protected $afterCallbacks = [];
/** * All of the defined abilities using class@method notation. * * @var array */ protected $stringCallbacks = [];
/** * The callback to be used to guess policy names. * * @var callable|null */ protected $guessPolicyNamesUsingCallback;
/** * Create a new gate instance. * * @param \Illuminate\Contracts\Container\Container $container * @param callable $userResolver * @param array $abilities * @param array $policies * @param array $beforeCallbacks * @param array $afterCallbacks * @param callable|null $guessPolicyNamesUsingCallback * @return void */ public function __construct(Container $container, callable $userResolver, array $abilities = [], array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [], callable $guessPolicyNamesUsingCallback = null) { $this->policies = $policies; $this->container = $container; $this->abilities = $abilities; $this->userResolver = $userResolver; $this->afterCallbacks = $afterCallbacks; $this->beforeCallbacks = $beforeCallbacks; $this->guessPolicyNamesUsingCallback = $guessPolicyNamesUsingCallback; }
/** * Determine if a given ability has been defined. * * @param string|array $ability * @return bool */ public function has($ability) { $abilities = is_array($ability) ? $ability : func_get_args();
foreach ($abilities as $ability) { if (! isset($this->abilities[$ability])) { return false; } }
return true; }
/** * Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is false. * * @param \Illuminate\Auth\Access\Response|\Closure|bool $condition * @param string|null $message * @param string|null $code * @return \Illuminate\Auth\Access\Response * * @throws \Illuminate\Auth\Access\AuthorizationException */ public function allowIf($condition, $message = null, $code = null) { return $this->authorizeOnDemand($condition, $message, $code, true); }
/** * Perform an on-demand authorization check. Throw an authorization exception if the condition or callback is true. * * @param \Illuminate\Auth\Access\Response|\Closure|bool $condition * @param string|null $message * @param string|null $code * @return \Illuminate\Auth\Access\Response * * @throws \Illuminate\Auth\Access\AuthorizationException */ public function denyIf($condition, $message = null, $code = null) { return $this->authorizeOnDemand($condition, $message, $code, false); }
/** * Authorize a given condition or callback. * * @param \Illuminate\Auth\Access\Response|\Closure|bool $condition * @param string|null $message * @param string|null $code * @param bool $allowWhenResponseIs * @return \Illuminate\Auth\Access\Response * * @throws \Illuminate\Auth\Access\AuthorizationException */ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResponseIs) { $user = $this->resolveUser();
if ($condition instanceof Closure) { $response = $this->canBeCalledWithUser($user, $condition) ? $condition($user) : new Response(false, $message, $code); } else { $response = $condition; }
return with($response instanceof Response ? $response : new Response( (bool) $response === $allowWhenResponseIs, $message, $code ))->authorize(); }
/** * Define a new ability. * * @param string $ability * @param callable|string $callback * @return $this * * @throws \InvalidArgumentException */ public function define($ability, $callback) { if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) { $callback = $callback[0].'@'.$callback[1]; }
if (is_callable($callback)) { $this->abilities[$ability] = $callback; } elseif (is_string($callback)) { $this->stringCallbacks[$ability] = $callback;
$this->abilities[$ability] = $this->buildAbilityCallback($ability, $callback); } else { throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string."); }
return $this; }
/** * Define abilities for a resource. * * @param string $name * @param string $class * @param array|null $abilities * @return $this */ public function resource($name, $class, array $abilities = null) { $abilities = $abilities ?: [ 'viewAny' => 'viewAny', 'view' => 'view', 'create' => 'create', 'update' => 'update', 'delete' => 'delete', ];
foreach ($abilities as $ability => $method) { $this->define($name.'.'.$ability, $class.'@'.$method); }
return $this; }
/** * Create the ability callback for a callback string. * * @param string $ability * @param string $callback * @return \Closure */ protected function buildAbilityCallback($ability, $callback) { return function () use ($ability, $callback) { if (str_contains($callback, '@')) { [$class, $method] = Str::parseCallback($callback); } else { $class = $callback; }
$policy = $this->resolvePolicy($class);
$arguments = func_get_args();
$user = array_shift($arguments);
$result = $this->callPolicyBefore( $policy, $user, $ability, $arguments );
if (! is_null($result)) { return $result; }
return isset($method) ? $policy->{$method}(...func_get_args()) : $policy(...func_get_args()); }; }
/** * Define a policy class for a given class type. * * @param string $class * @param string $policy * @return $this */ public function policy($class, $policy) { $this->policies[$class] = $policy;
return $this; }
/** * Register a callback to run before all Gate checks. * * @param callable $callback * @return $this */ public function before(callable $callback) { $this->beforeCallbacks[] = $callback;
return $this; }
/** * Register a callback to run after all Gate checks. * * @param callable $callback * @return $this */ public function after(callable $callback) { $this->afterCallbacks[] = $callback;
return $this; }
/** * Determine if the given ability should be granted for the current user. * * @param string $ability * @param array|mixed $arguments * @return bool */ public function allows($ability, $arguments = []) { return $this->check($ability, $arguments); }
/** * Determine if the given ability should be denied for the current user. * * @param string $ability * @param array|mixed $arguments * @return bool */ public function denies($ability, $arguments = []) { return ! $this->allows($ability, $arguments); }
/** * Determine if all of the given abilities should be granted for the current user. * * @param iterable|string $abilities * @param array|mixed $arguments * @return bool */ public function check($abilities, $arguments = []) { return collect($abilities)->every(function ($ability) use ($arguments) { return $this->inspect($ability, $arguments)->allowed(); }); }
/** * Determine if any one of the given abilities should be granted for the current user. * * @param iterable|string $abilities * @param array|mixed $arguments * @return bool */ public function any($abilities, $arguments = []) { return collect($abilities)->contains(function ($ability) use ($arguments) { return $this->check($ability, $arguments); }); }
/** * Determine if all of the given abilities should be denied for the current user. * * @param iterable|string $abilities * @param array|mixed $arguments * @return bool */ public function none($abilities, $arguments = []) { return ! $this->any($abilities, $arguments); }
/** * Determine if the given ability should be granted for the current user. * * @param string $ability * @param array|mixed $arguments * @return \Illuminate\Auth\Access\Response * * @throws \Illuminate\Auth\Access\AuthorizationException */ public function authorize($ability, $arguments = []) { return $this->inspect($ability, $arguments)->authorize(); }
/** * Inspect the user for the given ability. * * @param string $ability * @param array|mixed $arguments * @return \Illuminate\Auth\Access\Response */ public function inspect($ability, $arguments = []) { try { $result = $this->raw($ability, $arguments);
if ($result instanceof Response) { return $result; }
return $result ? Response::allow() : Response::deny(); } catch (AuthorizationException $e) { return $e->toResponse(); } }
/** * Get the raw result from the authorization callback. * * @param string $ability * @param array|mixed $arguments * @return mixed * * @throws \Illuminate\Auth\Access\AuthorizationException */ public function raw($ability, $arguments = []) { $arguments = Arr::wrap($arguments);
$user = $this->resolveUser();
// First we will call the "before" callbacks for the Gate. If any of these give // back a non-null response, we will immediately return that result in order // to let the developers override all checks for some authorization cases. $result = $this->callBeforeCallbacks( $user, $ability, $arguments );
if (is_null($result)) { $result = $this->callAuthCallback($user, $ability, $arguments); }
// After calling the authorization callback, we will call the "after" callbacks // that are registered with the Gate, which allows a developer to do logging // if that is required for this application. Then we'll return the result. return tap($this->callAfterCallbacks( $user, $ability, $arguments, $result ), function ($result) use ($user, $ability, $arguments) { $this->dispatchGateEvaluatedEvent($user, $ability, $arguments, $result); }); }
/** * Determine whether the callback/method can be called with the given user. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param \Closure|string|array $class * @param string|null $method * @return bool */ protected function canBeCalledWithUser($user, $class, $method = null) { if (! is_null($user)) { return true; }
if (! is_null($method)) { return $this->methodAllowsGuests($class, $method); }
if (is_array($class)) { $className = is_string($class[0]) ? $class[0] : get_class($class[0]);
return $this->methodAllowsGuests($className, $class[1]); }
return $this->callbackAllowsGuests($class); }
/** * Determine if the given class method allows guests. * * @param string $class * @param string $method * @return bool */ protected function methodAllowsGuests($class, $method) { try { $reflection = new ReflectionClass($class);
$method = $reflection->getMethod($method); } catch (Exception $e) { return false; }
if ($method) { $parameters = $method->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]); }
return false; }
/** * Determine if the callback allows guests. * * @param callable $callback * @return bool * * @throws \ReflectionException */ protected function callbackAllowsGuests($callback) { $parameters = (new ReflectionFunction($callback))->getParameters();
return isset($parameters[0]) && $this->parameterAllowsGuests($parameters[0]); }
/** * Determine if the given parameter allows guests. * * @param \ReflectionParameter $parameter * @return bool */ protected function parameterAllowsGuests($parameter) { return ($parameter->hasType() && $parameter->allowsNull()) || ($parameter->isDefaultValueAvailable() && is_null($parameter->getDefaultValue())); }
/** * Resolve and call the appropriate authorization callback. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param string $ability * @param array $arguments * @return bool */ protected function callAuthCallback($user, $ability, array $arguments) { $callback = $this->resolveAuthCallback($user, $ability, $arguments);
return $callback($user, ...$arguments); }
/** * Call all of the before callbacks and return if a result is given. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param string $ability * @param array $arguments * @return bool|null */ protected function callBeforeCallbacks($user, $ability, array $arguments) { foreach ($this->beforeCallbacks as $before) { if (! $this->canBeCalledWithUser($user, $before)) { continue; }
if (! is_null($result = $before($user, $ability, $arguments))) { return $result; } } }
/** * Call all of the after callbacks with check result. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @param bool $result * @return bool|null */ protected function callAfterCallbacks($user, $ability, array $arguments, $result) { foreach ($this->afterCallbacks as $after) { if (! $this->canBeCalledWithUser($user, $after)) { continue; }
$afterResult = $after($user, $ability, $result, $arguments);
$result = $result ?? $afterResult; }
return $result; }
/** * Dispatch a gate evaluation event. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param string $ability * @param array $arguments * @param bool|null $result * @return void */ protected function dispatchGateEvaluatedEvent($user, $ability, array $arguments, $result) { if ($this->container->bound(Dispatcher::class)) { $this->container->make(Dispatcher::class)->dispatch( new Events\GateEvaluated($user, $ability, $result, $arguments) ); } }
/** * Resolve the callable for the given ability and arguments. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param string $ability * @param array $arguments * @return callable */ protected function resolveAuthCallback($user, $ability, array $arguments) { if (isset($arguments[0]) && ! is_null($policy = $this->getPolicyFor($arguments[0])) && $callback = $this->resolvePolicyCallback($user, $ability, $arguments, $policy)) { return $callback; }
if (isset($this->stringCallbacks[$ability])) { [$class, $method] = Str::parseCallback($this->stringCallbacks[$ability]);
if ($this->canBeCalledWithUser($user, $class, $method ?: '__invoke')) { return $this->abilities[$ability]; } }
if (isset($this->abilities[$ability]) && $this->canBeCalledWithUser($user, $this->abilities[$ability])) { return $this->abilities[$ability]; }
return function () { // }; }
/** * Get a policy instance for a given class. * * @param object|string $class * @return mixed */ public function getPolicyFor($class) { if (is_object($class)) { $class = get_class($class); }
if (! is_string($class)) { return; }
if (isset($this->policies[$class])) { return $this->resolvePolicy($this->policies[$class]); }
foreach ($this->guessPolicyName($class) as $guessedPolicy) { if (class_exists($guessedPolicy)) { return $this->resolvePolicy($guessedPolicy); } }
foreach ($this->policies as $expected => $policy) { if (is_subclass_of($class, $expected)) { return $this->resolvePolicy($policy); } } }
/** * Guess the policy name for the given class. * * @param string $class * @return array */ protected function guessPolicyName($class) { if ($this->guessPolicyNamesUsingCallback) { return Arr::wrap(call_user_func($this->guessPolicyNamesUsingCallback, $class)); }
$classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class)));
$classDirnameSegments = explode('\\', $classDirname);
return Arr::wrap(Collection::times(count($classDirnameSegments), function ($index) use ($class, $classDirnameSegments) { $classDirname = implode('\\', array_slice($classDirnameSegments, 0, $index));
return $classDirname.'\\Policies\\'.class_basename($class).'Policy'; })->reverse()->values()->first(function ($class) { return class_exists($class); }) ?: [$classDirname.'\\Policies\\'.class_basename($class).'Policy']); }
/** * Specify a callback to be used to guess policy names. * * @param callable $callback * @return $this */ public function guessPolicyNamesUsing(callable $callback) { $this->guessPolicyNamesUsingCallback = $callback;
return $this; }
/** * Build a policy class instance of the given type. * * @param object|string $class * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function resolvePolicy($class) { return $this->container->make($class); }
/** * Resolve the callback for a policy check. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @param mixed $policy * @return bool|callable */ protected function resolvePolicyCallback($user, $ability, array $arguments, $policy) { if (! is_callable([$policy, $this->formatAbilityToMethod($ability)])) { return false; }
return function () use ($user, $ability, $arguments, $policy) { // This callback will be responsible for calling the policy's before method and // running this policy method if necessary. This is used to when objects are // mapped to policy objects in the user's configurations or on this class. $result = $this->callPolicyBefore( $policy, $user, $ability, $arguments );
// When we receive a non-null result from this before method, we will return it // as the "final" results. This will allow developers to override the checks // in this policy to return the result for all rules defined in the class. if (! is_null($result)) { return $result; }
$method = $this->formatAbilityToMethod($ability);
return $this->callPolicyMethod($policy, $method, $user, $arguments); }; }
/** * Call the "before" method on the given policy, if applicable. * * @param mixed $policy * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @return mixed */ protected function callPolicyBefore($policy, $user, $ability, $arguments) { if (! method_exists($policy, 'before')) { return; }
if ($this->canBeCalledWithUser($user, $policy, 'before')) { return $policy->before($user, $ability, ...$arguments); } }
/** * Call the appropriate method on the given policy. * * @param mixed $policy * @param string $method * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param array $arguments * @return mixed */ protected function callPolicyMethod($policy, $method, $user, array $arguments) { // If this first argument is a string, that means they are passing a class name // to the policy. We will remove the first argument from this argument array // because this policy already knows what type of models it can authorize. if (isset($arguments[0]) && is_string($arguments[0])) { array_shift($arguments); }
if (! is_callable([$policy, $method])) { return; }
if ($this->canBeCalledWithUser($user, $policy, $method)) { return $policy->{$method}($user, ...$arguments); } }
/** * Format the policy ability into a method name. * * @param string $ability * @return string */ protected function formatAbilityToMethod($ability) { return str_contains($ability, '-') ? Str::camel($ability) : $ability; }
/** * Get a gate instance for the given user. * * @param \Illuminate\Contracts\Auth\Authenticatable|mixed $user * @return static */ public function forUser($user) { $callback = function () use ($user) { return $user; };
return new static( $this->container, $callback, $this->abilities, $this->policies, $this->beforeCallbacks, $this->afterCallbacks, $this->guessPolicyNamesUsingCallback ); }
/** * Resolve the user from the user resolver. * * @return mixed */ protected function resolveUser() { return call_user_func($this->userResolver); }
/** * Get all of the defined abilities. * * @return array */ public function abilities() { return $this->abilities; }
/** * Get all of the defined policies. * * @return array */ public function policies() { return $this->policies; }
/** * Set the container instance used by the gate. * * @param \Illuminate\Contracts\Container\Container $container * @return $this */ public function setContainer(Container $container) { $this->container = $container;
return $this; } }
|