diff --git a/src/Api/Serializer/DiscussionSerializer.php b/src/Api/Serializer/DiscussionSerializer.php index 24c3dc09c..c12670d29 100644 --- a/src/Api/Serializer/DiscussionSerializer.php +++ b/src/Api/Serializer/DiscussionSerializer.php @@ -10,40 +10,24 @@ namespace Flarum\Api\Serializer; use Flarum\Discussion\Discussion; -use Flarum\User\Gate; class DiscussionSerializer extends BasicDiscussionSerializer { - /** - * @var \Flarum\User\Gate - */ - protected $gate; - - /** - * @param \Flarum\User\Gate $gate - */ - public function __construct(Gate $gate) - { - $this->gate = $gate; - } - /** * {@inheritdoc} */ protected function getDefaultAttributes($discussion) { - $gate = $this->gate->forUser($this->actor); - $attributes = parent::getDefaultAttributes($discussion) + [ 'commentCount' => (int) $discussion->comment_count, 'participantCount' => (int) $discussion->participant_count, 'createdAt' => $this->formatDate($discussion->created_at), 'lastPostedAt' => $this->formatDate($discussion->last_posted_at), 'lastPostNumber' => (int) $discussion->last_post_number, - 'canReply' => $gate->allows('reply', $discussion), - 'canRename' => $gate->allows('rename', $discussion), - 'canDelete' => $gate->allows('delete', $discussion), - 'canHide' => $gate->allows('hide', $discussion) + 'canReply' => $this->actor->can('reply', $discussion), + 'canRename' => $this->actor->can('rename', $discussion), + 'canDelete' => $this->actor->can('delete', $discussion), + 'canHide' => $this->actor->can('hide', $discussion) ]; if ($discussion->hidden_at) { diff --git a/src/Api/Serializer/PostSerializer.php b/src/Api/Serializer/PostSerializer.php index 6db3450c8..325c2d781 100644 --- a/src/Api/Serializer/PostSerializer.php +++ b/src/Api/Serializer/PostSerializer.php @@ -10,23 +10,9 @@ namespace Flarum\Api\Serializer; use Flarum\Post\CommentPost; -use Flarum\User\Gate; class PostSerializer extends BasicPostSerializer { - /** - * @var \Flarum\User\Gate - */ - protected $gate; - - /** - * @param \Flarum\User\Gate $gate - */ - public function __construct(Gate $gate) - { - $this->gate = $gate; - } - /** * {@inheritdoc} */ @@ -36,15 +22,13 @@ class PostSerializer extends BasicPostSerializer unset($attributes['content']); - $gate = $this->gate->forUser($this->actor); - - $canEdit = $gate->allows('edit', $post); + $canEdit = $this->actor->can('edit', $post); if ($post instanceof CommentPost) { if ($canEdit) { $attributes['content'] = $post->content; } - if ($gate->allows('viewIps', $post)) { + if ($this->actor->can('viewIps', $post)) { $attributes['ipAddress'] = $post->ip_address; } } else { @@ -62,8 +46,8 @@ class PostSerializer extends BasicPostSerializer $attributes += [ 'canEdit' => $canEdit, - 'canDelete' => $gate->allows('delete', $post), - 'canHide' => $gate->allows('hide', $post) + 'canDelete' => $this->actor->can('delete', $post), + 'canHide' => $this->actor->can('hide', $post) ]; return $attributes; diff --git a/src/Api/Serializer/UserSerializer.php b/src/Api/Serializer/UserSerializer.php index 867fb249d..78d3cb554 100644 --- a/src/Api/Serializer/UserSerializer.php +++ b/src/Api/Serializer/UserSerializer.php @@ -9,23 +9,8 @@ namespace Flarum\Api\Serializer; -use Flarum\User\Gate; - class UserSerializer extends BasicUserSerializer { - /** - * @var \Flarum\User\Gate - */ - protected $gate; - - /** - * @param Gate $gate - */ - public function __construct(Gate $gate) - { - $this->gate = $gate; - } - /** * @param \Flarum\User\User $user * @return array @@ -34,16 +19,14 @@ class UserSerializer extends BasicUserSerializer { $attributes = parent::getDefaultAttributes($user); - $gate = $this->gate->forUser($this->actor); - - $canEdit = $gate->allows('edit', $user); + $canEdit = $this->actor->can('edit', $user); $attributes += [ 'joinTime' => $this->formatDate($user->joined_at), 'discussionCount' => (int) $user->discussion_count, 'commentCount' => (int) $user->comment_count, 'canEdit' => $canEdit, - 'canDelete' => $gate->allows('delete', $user), + 'canDelete' => $this->actor->can('delete', $user), ]; if ($user->getPreference('discloseOnline') || $this->actor->can('viewLastSeenAt', $user)) { diff --git a/src/Discussion/DiscussionPolicy.php b/src/Discussion/DiscussionPolicy.php index 335509096..4d353939f 100644 --- a/src/Discussion/DiscussionPolicy.php +++ b/src/Discussion/DiscussionPolicy.php @@ -12,7 +12,6 @@ namespace Flarum\Discussion; use Flarum\Event\ScopeModelVisibility; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\User\AbstractPolicy; -use Flarum\User\Gate; use Flarum\User\User; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Database\Eloquent\Builder; @@ -29,11 +28,6 @@ class DiscussionPolicy extends AbstractPolicy */ protected $settings; - /** - * @var Gate - */ - protected $gate; - /** * @var Dispatcher */ @@ -41,13 +35,11 @@ class DiscussionPolicy extends AbstractPolicy /** * @param SettingsRepositoryInterface $settings - * @param Gate $gate * @param Dispatcher $events */ - public function __construct(SettingsRepositoryInterface $settings, Gate $gate, Dispatcher $events) + public function __construct(SettingsRepositoryInterface $settings, Dispatcher $events) { $this->settings = $settings; - $this->gate = $gate; $this->events = $events; } diff --git a/src/User/Gate.php b/src/User/Gate.php index 1c0cb4596..c3a8d54f4 100644 --- a/src/User/Gate.php +++ b/src/User/Gate.php @@ -9,406 +9,52 @@ namespace Flarum\User; -use Illuminate\Contracts\Auth\Access\Gate as GateContract; -use Illuminate\Contracts\Container\Container; -use Illuminate\Support\Str; -use InvalidArgumentException; +use Flarum\Event\GetPermission; +use Illuminate\Contracts\Events\Dispatcher; -/** - * @author Taylor Otwell - */ -class Gate implements GateContract +class Gate { /** - * The container instance. - * - * @var Container + * @var Dispatcher */ - protected $container; + protected $events; /** - * The user resolver callable. - * - * @var callable + * @param Dispatcher $events */ - 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 = []; - - /** - * Create a new gate instance. - * - * @param Container $container - * @param callable $userResolver - * @param array $abilities - * @param array $policies - * @param array $beforeCallbacks - * @return void - */ - public function __construct(Container $container, callable $userResolver, array $abilities = [], array $policies = [], array $beforeCallbacks = []) + public function __construct(Dispatcher $events) { - $this->policies = $policies; - $this->container = $container; - $this->abilities = $abilities; - $this->userResolver = $userResolver; - $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; + $this->events = $events; } /** * Determine if the given ability should be granted for the current user. * + * @param User $actor * @param string $ability * @param array|mixed $arguments * @return bool */ - public function allows($ability, $arguments = []) + public function allows($actor, $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 = []) - { - if (! $user = $this->resolveUser()) { - return false; - } - - $arguments = is_array($arguments) ? $arguments : [$arguments]; - - if (! is_null($result = $this->callBeforeCallbacks($user, $ability, $arguments))) { - return $result; - } - - $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|void - */ - 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; - } - } - } - - /** - * 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 received 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 (! 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) - { - return new static( - $this->container, - function () use ($user) { - return $user; - }, - $this->abilities, - $this->policies, - $this->beforeCallbacks + // Fire an event so that core and extension policies can hook into + // this permission query and explicitly grant or deny the + // permission. + $allowed = $this->events->until( + new GetPermission($actor, $ability, $arguments) ); - } - /** - * Resolve the user from the user resolver. - * - * @return mixed - */ - protected function resolveUser() - { - return call_user_func($this->userResolver); - } + if (! is_null($allowed)) { + return $allowed; + } - /** - * Register a callback to run after all Gate checks. - * - * @param callable $callback - * @return GateContract - */ - public function after(callable $callback) - { - // TODO: Implement after() method. - } + // If no policy covered this permission query, we will only grant + // the permission if the actor's groups have it. Otherwise, we will + // not allow the user to perform this action. + if ($actor->isAdmin() || ($actor->hasPermission($ability))) { + return true; + } - /** - * 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 = []) - { - // TODO: Implement any() method. - } - - /** - * 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 = []) - { - // TODO: Implement authorize() method. - } - - /** - * Get all of the defined abilities. - * - * @return array - */ - public function abilities() - { - // TODO: Implement abilities() method. - } - - /** - * Get the raw result from the authorization callback. - * - * @param string $ability - * @param array|mixed $arguments - * @return mixed - */ - public function raw($ability, $arguments = []) - { - // TODO: Implement raw() method. + return false; } } diff --git a/src/User/User.php b/src/User/User.php index a9d0eeb1b..48b02b64a 100644 --- a/src/User/User.php +++ b/src/User/User.php @@ -164,14 +164,6 @@ class User extends AbstractModel return $user; } - /** - * @return Gate - */ - public static function getGate() - { - return static::$gate; - } - /** * @param Gate $gate */ @@ -708,7 +700,7 @@ class User extends AbstractModel */ public function can($ability, $arguments = []) { - return static::$gate->forUser($this)->allows($ability, $arguments); + return static::$gate->allows($this, $ability, $arguments); } /** diff --git a/src/User/UserServiceProvider.php b/src/User/UserServiceProvider.php index 1af46f60a..c83fe6ad1 100644 --- a/src/User/UserServiceProvider.php +++ b/src/User/UserServiceProvider.php @@ -10,7 +10,6 @@ namespace Flarum\User; use Flarum\Event\ConfigureUserPreferences; -use Flarum\Event\GetPermission; use Flarum\Foundation\AbstractServiceProvider; use Flarum\Settings\SettingsRepositoryInterface; use Flarum\User\DisplayName\DriverInterface; @@ -18,12 +17,10 @@ use Flarum\User\DisplayName\UsernameDriver; use Flarum\User\Event\EmailChangeRequested; use Flarum\User\Event\Registered; use Flarum\User\Event\Saving; -use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Support\Arr; use League\Flysystem\FilesystemInterface; -use RuntimeException; class UserServiceProvider extends AbstractServiceProvider { @@ -32,7 +29,6 @@ class UserServiceProvider extends AbstractServiceProvider */ public function register() { - $this->registerGate(); $this->registerAvatarsFilesystem(); $this->registerDisplayNameDrivers(); } @@ -60,18 +56,6 @@ class UserServiceProvider extends AbstractServiceProvider $this->app->alias('flarum.user.display_name.driver', DriverInterface::class); } - protected function registerGate() - { - $this->app->singleton('flarum.gate', function ($app) { - return new Gate($app, function () { - throw new RuntimeException('You must set the gate user with forUser()'); - }); - }); - - $this->app->alias('flarum.gate', GateContract::class); - $this->app->alias('flarum.gate', Gate::class); - } - protected function registerAvatarsFilesystem() { $avatarsFilesystem = function (Container $app) { @@ -88,30 +72,8 @@ class UserServiceProvider extends AbstractServiceProvider */ public function boot() { - $this->app->make('flarum.gate')->before(function (User $actor, $ability, $model = null) { - // Fire an event so that core and extension policies can hook into - // this permission query and explicitly grant or deny the - // permission. - $allowed = $this->app->make('events')->until( - new GetPermission($actor, $ability, $model) - ); - - if (! is_null($allowed)) { - return $allowed; - } - - // If no policy covered this permission query, we will only grant - // the permission if the actor's groups have it. Otherwise, we will - // not allow the user to perform this action. - if ($actor->isAdmin() || $actor->hasPermission($ability)) { - return true; - } - - return false; - }); - User::setHasher($this->app->make('hash')); - User::setGate($this->app->make('flarum.gate')); + User::setGate($this->app->make(Gate::class)); User::setDisplayNameDriver($this->app->make('flarum.user.display_name.driver')); $events = $this->app->make('events');