policies = $policies; $this->container = $container; $this->abilities = $abilities; $this->userResolver = $userResolver; $this->afterCallbacks = $afterCallbacks; $this->beforeCallbacks = $beforeCallbacks; } /** * Determine if a given ability has been defined. * * @param string $ability * @return bool */ public function has($ability) { return isset($this->abilities[$ability]); } /** * Define a new ability. * * @param string $ability * @param callable|string $callback * @return $this * * @throws \InvalidArgumentException */ public function define($ability, $callback) { if (is_callable($callback)) { $this->abilities[$ability] = $callback; } elseif (is_string($callback) && str_contains($callback, '@')) { $this->abilities[$ability] = $this->buildAbilityCallback($callback); } else { throw new InvalidArgumentException("Callback must be a callable or a 'Class@method' string."); } return $this; } /** * Create the ability callback for a callback string. * * @param string $callback * @return \Closure */ protected function buildAbilityCallback($callback) { return function () use ($callback) { list($class, $method) = explode('@', $callback); return call_user_func_array([$this->resolvePolicy($class), $method], 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 the given ability should be granted for the current user. * * @param string $ability * @param array|mixed $arguments * @return bool */ public function check($ability, $arguments = []) { try { $result = $this->raw($ability, $arguments); } catch (UnauthorizedException $e) { return false; } return (bool) $result; } /** * 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\UnauthorizedException */ public function authorize($ability, $arguments = []) { $result = $this->raw($ability, $arguments); if ($result instanceof Response) { return $result; } return $result ? $this->allow() : $this->deny(); } /** * Get the raw result for the given ability for the current user. * * @param string $ability * @param array|mixed $arguments * @return mixed */ protected function raw($ability, $arguments = []) { if (! $user = $this->resolveUser()) { return false; } $arguments = is_array($arguments) ? $arguments : [$arguments]; if (is_null($result = $this->callBeforeCallbacks($user, $ability, $arguments))) { $result = $this->callAuthCallback($user, $ability, $arguments); } $this->callAfterCallbacks( $user, $ability, $arguments, $result ); return $result; } /** * Resolve and call the appropriate authorization callback. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @return bool */ protected function callAuthCallback($user, $ability, array $arguments) { $callback = $this->resolveAuthCallback( $user, $ability, $arguments ); return call_user_func_array( $callback, array_merge([$user], $arguments) ); } /** * Call all of the before callbacks and return if a result is given. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @return bool|null */ protected function callBeforeCallbacks($user, $ability, array $arguments) { $arguments = array_merge([$user, $ability], $arguments); foreach ($this->beforeCallbacks as $before) { if (! is_null($result = call_user_func_array($before, $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 void */ protected function callAfterCallbacks($user, $ability, array $arguments, $result) { $arguments = array_merge([$user, $ability, $result], $arguments); foreach ($this->afterCallbacks as $after) { call_user_func_array($after, $arguments); } } /** * Resolve the callable for the given ability and arguments. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @return callable */ protected function resolveAuthCallback($user, $ability, array $arguments) { if ($this->firstArgumentCorrespondsToPolicy($arguments)) { return $this->resolvePolicyCallback($user, $ability, $arguments); } elseif (isset($this->abilities[$ability])) { return $this->abilities[$ability]; } else { return function () { return false; }; } } /** * Determine if the first argument in the array corresponds to a policy. * * @param array $arguments * @return bool */ protected function firstArgumentCorrespondsToPolicy(array $arguments) { if (! isset($arguments[0])) { return false; } if (is_object($arguments[0])) { return isset($this->policies[get_class($arguments[0])]); } return is_string($arguments[0]) && isset($this->policies[$arguments[0]]); } /** * Resolve the callback for a policy check. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param string $ability * @param array $arguments * @return callable */ protected function resolvePolicyCallback($user, $ability, array $arguments) { return function () use ($user, $ability, $arguments) { $instance = $this->getPolicyFor($arguments[0]); if (method_exists($instance, 'before')) { // We will prepend the user and ability onto the arguments so that the before // callback can determine which ability is being called. Then we will call // into the policy before methods with the arguments and get the result. $beforeArguments = array_merge([$user, $ability], $arguments); $result = call_user_func_array( [$instance, 'before'], $beforeArguments ); // If we recieved a non-null result from the before method, we will return it // as the result of a check. This allows developers to override the checks // in the policy and return a result for all rules defined in the class. if (! is_null($result)) { return $result; } } if (strpos($ability, '-') !== false) { $ability = Str::camel($ability); } if (! is_callable([$instance, $ability])) { return false; } return call_user_func_array( [$instance, $ability], array_merge([$user], $arguments) ); }; } /** * Get a policy instance for a given class. * * @param object|string $class * @return mixed * * @throws \InvalidArgumentException */ public function getPolicyFor($class) { if (is_object($class)) { $class = get_class($class); } if (! isset($this->policies[$class])) { throw new InvalidArgumentException("Policy not defined for [{$class}]."); } return $this->resolvePolicy($this->policies[$class]); } /** * Build a policy class instance of the given type. * * @param object|string $class * @return mixed */ public function resolvePolicy($class) { return $this->container->make($class); } /** * Get a guard 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 ); } /** * Resolve the user from the user resolver. * * @return mixed */ protected function resolveUser() { return call_user_func($this->userResolver); } }