Get rid of Laravel Gate contract (#2181)

* Get rid of unnecessary uses of gate

* Move gate off of Laravel's gate contract
This commit is contained in:
Alexander Skvortsov 2020-05-28 18:00:44 -04:00 committed by GitHub
parent 004d80b860
commit af22f5f89a
7 changed files with 38 additions and 495 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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)
);
if (! is_null($allowed)) {
return $allowed;
}
/**
* Resolve the user from the user resolver.
*
* @return mixed
*/
protected function resolveUser()
{
return call_user_func($this->userResolver);
// 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;
}
/**
* Register a callback to run after all Gate checks.
*
* @param callable $callback
* @return GateContract
*/
public function after(callable $callback)
{
// TODO: Implement after() method.
}
/**
* 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;
}
}

View File

@ -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);
}
/**

View File

@ -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');