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; namespace Flarum\Api\Serializer;
use Flarum\Discussion\Discussion; use Flarum\Discussion\Discussion;
use Flarum\User\Gate;
class DiscussionSerializer extends BasicDiscussionSerializer class DiscussionSerializer extends BasicDiscussionSerializer
{ {
/**
* @var \Flarum\User\Gate
*/
protected $gate;
/**
* @param \Flarum\User\Gate $gate
*/
public function __construct(Gate $gate)
{
$this->gate = $gate;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function getDefaultAttributes($discussion) protected function getDefaultAttributes($discussion)
{ {
$gate = $this->gate->forUser($this->actor);
$attributes = parent::getDefaultAttributes($discussion) + [ $attributes = parent::getDefaultAttributes($discussion) + [
'commentCount' => (int) $discussion->comment_count, 'commentCount' => (int) $discussion->comment_count,
'participantCount' => (int) $discussion->participant_count, 'participantCount' => (int) $discussion->participant_count,
'createdAt' => $this->formatDate($discussion->created_at), 'createdAt' => $this->formatDate($discussion->created_at),
'lastPostedAt' => $this->formatDate($discussion->last_posted_at), 'lastPostedAt' => $this->formatDate($discussion->last_posted_at),
'lastPostNumber' => (int) $discussion->last_post_number, 'lastPostNumber' => (int) $discussion->last_post_number,
'canReply' => $gate->allows('reply', $discussion), 'canReply' => $this->actor->can('reply', $discussion),
'canRename' => $gate->allows('rename', $discussion), 'canRename' => $this->actor->can('rename', $discussion),
'canDelete' => $gate->allows('delete', $discussion), 'canDelete' => $this->actor->can('delete', $discussion),
'canHide' => $gate->allows('hide', $discussion) 'canHide' => $this->actor->can('hide', $discussion)
]; ];
if ($discussion->hidden_at) { if ($discussion->hidden_at) {

View File

@ -10,23 +10,9 @@
namespace Flarum\Api\Serializer; namespace Flarum\Api\Serializer;
use Flarum\Post\CommentPost; use Flarum\Post\CommentPost;
use Flarum\User\Gate;
class PostSerializer extends BasicPostSerializer class PostSerializer extends BasicPostSerializer
{ {
/**
* @var \Flarum\User\Gate
*/
protected $gate;
/**
* @param \Flarum\User\Gate $gate
*/
public function __construct(Gate $gate)
{
$this->gate = $gate;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -36,15 +22,13 @@ class PostSerializer extends BasicPostSerializer
unset($attributes['content']); unset($attributes['content']);
$gate = $this->gate->forUser($this->actor); $canEdit = $this->actor->can('edit', $post);
$canEdit = $gate->allows('edit', $post);
if ($post instanceof CommentPost) { if ($post instanceof CommentPost) {
if ($canEdit) { if ($canEdit) {
$attributes['content'] = $post->content; $attributes['content'] = $post->content;
} }
if ($gate->allows('viewIps', $post)) { if ($this->actor->can('viewIps', $post)) {
$attributes['ipAddress'] = $post->ip_address; $attributes['ipAddress'] = $post->ip_address;
} }
} else { } else {
@ -62,8 +46,8 @@ class PostSerializer extends BasicPostSerializer
$attributes += [ $attributes += [
'canEdit' => $canEdit, 'canEdit' => $canEdit,
'canDelete' => $gate->allows('delete', $post), 'canDelete' => $this->actor->can('delete', $post),
'canHide' => $gate->allows('hide', $post) 'canHide' => $this->actor->can('hide', $post)
]; ];
return $attributes; return $attributes;

View File

@ -9,23 +9,8 @@
namespace Flarum\Api\Serializer; namespace Flarum\Api\Serializer;
use Flarum\User\Gate;
class UserSerializer extends BasicUserSerializer 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 * @param \Flarum\User\User $user
* @return array * @return array
@ -34,16 +19,14 @@ class UserSerializer extends BasicUserSerializer
{ {
$attributes = parent::getDefaultAttributes($user); $attributes = parent::getDefaultAttributes($user);
$gate = $this->gate->forUser($this->actor); $canEdit = $this->actor->can('edit', $user);
$canEdit = $gate->allows('edit', $user);
$attributes += [ $attributes += [
'joinTime' => $this->formatDate($user->joined_at), 'joinTime' => $this->formatDate($user->joined_at),
'discussionCount' => (int) $user->discussion_count, 'discussionCount' => (int) $user->discussion_count,
'commentCount' => (int) $user->comment_count, 'commentCount' => (int) $user->comment_count,
'canEdit' => $canEdit, 'canEdit' => $canEdit,
'canDelete' => $gate->allows('delete', $user), 'canDelete' => $this->actor->can('delete', $user),
]; ];
if ($user->getPreference('discloseOnline') || $this->actor->can('viewLastSeenAt', $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\Event\ScopeModelVisibility;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\AbstractPolicy; use Flarum\User\AbstractPolicy;
use Flarum\User\Gate;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -29,11 +28,6 @@ class DiscussionPolicy extends AbstractPolicy
*/ */
protected $settings; protected $settings;
/**
* @var Gate
*/
protected $gate;
/** /**
* @var Dispatcher * @var Dispatcher
*/ */
@ -41,13 +35,11 @@ class DiscussionPolicy extends AbstractPolicy
/** /**
* @param SettingsRepositoryInterface $settings * @param SettingsRepositoryInterface $settings
* @param Gate $gate
* @param Dispatcher $events * @param Dispatcher $events
*/ */
public function __construct(SettingsRepositoryInterface $settings, Gate $gate, Dispatcher $events) public function __construct(SettingsRepositoryInterface $settings, Dispatcher $events)
{ {
$this->settings = $settings; $this->settings = $settings;
$this->gate = $gate;
$this->events = $events; $this->events = $events;
} }

View File

@ -9,406 +9,52 @@
namespace Flarum\User; namespace Flarum\User;
use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Flarum\Event\GetPermission;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Str;
use InvalidArgumentException;
/** class Gate
* @author Taylor Otwell
*/
class Gate implements GateContract
{ {
/** /**
* The container instance. * @var Dispatcher
*
* @var Container
*/ */
protected $container; protected $events;
/** /**
* The user resolver callable. * @param Dispatcher $events
*
* @var callable
*/ */
protected $userResolver; public function __construct(Dispatcher $events)
/**
* 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 = [])
{ {
$this->policies = $policies; $this->events = $events;
$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;
} }
/** /**
* Determine if the given ability should be granted for the current user. * Determine if the given ability should be granted for the current user.
* *
* @param User $actor
* @param string $ability * @param string $ability
* @param array|mixed $arguments * @param array|mixed $arguments
* @return bool * @return bool
*/ */
public function allows($ability, $arguments = []) public function allows($actor, $ability, $arguments)
{ {
return $this->check($ability, $arguments); // 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(
* Determine if the given ability should be denied for the current user. new GetPermission($actor, $ability, $arguments)
*
* @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
); );
}
/** if (! is_null($allowed)) {
* Resolve the user from the user resolver. return $allowed;
* }
* @return mixed
*/
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
/** // If no policy covered this permission query, we will only grant
* Register a callback to run after all Gate checks. // the permission if the actor's groups have it. Otherwise, we will
* // not allow the user to perform this action.
* @param callable $callback if ($actor->isAdmin() || ($actor->hasPermission($ability))) {
* @return GateContract return true;
*/ }
public function after(callable $callback)
{
// TODO: Implement after() method.
}
/** return false;
* 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.
} }
} }

View File

@ -164,14 +164,6 @@ class User extends AbstractModel
return $user; return $user;
} }
/**
* @return Gate
*/
public static function getGate()
{
return static::$gate;
}
/** /**
* @param Gate $gate * @param Gate $gate
*/ */
@ -708,7 +700,7 @@ class User extends AbstractModel
*/ */
public function can($ability, $arguments = []) 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; namespace Flarum\User;
use Flarum\Event\ConfigureUserPreferences; use Flarum\Event\ConfigureUserPreferences;
use Flarum\Event\GetPermission;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\DisplayName\DriverInterface; use Flarum\User\DisplayName\DriverInterface;
@ -18,12 +17,10 @@ use Flarum\User\DisplayName\UsernameDriver;
use Flarum\User\Event\EmailChangeRequested; use Flarum\User\Event\EmailChangeRequested;
use Flarum\User\Event\Registered; use Flarum\User\Event\Registered;
use Flarum\User\Event\Saving; use Flarum\User\Event\Saving;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use League\Flysystem\FilesystemInterface; use League\Flysystem\FilesystemInterface;
use RuntimeException;
class UserServiceProvider extends AbstractServiceProvider class UserServiceProvider extends AbstractServiceProvider
{ {
@ -32,7 +29,6 @@ class UserServiceProvider extends AbstractServiceProvider
*/ */
public function register() public function register()
{ {
$this->registerGate();
$this->registerAvatarsFilesystem(); $this->registerAvatarsFilesystem();
$this->registerDisplayNameDrivers(); $this->registerDisplayNameDrivers();
} }
@ -60,18 +56,6 @@ class UserServiceProvider extends AbstractServiceProvider
$this->app->alias('flarum.user.display_name.driver', DriverInterface::class); $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() protected function registerAvatarsFilesystem()
{ {
$avatarsFilesystem = function (Container $app) { $avatarsFilesystem = function (Container $app) {
@ -88,30 +72,8 @@ class UserServiceProvider extends AbstractServiceProvider
*/ */
public function boot() 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::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')); User::setDisplayNameDriver($this->app->make('flarum.user.display_name.driver'));
$events = $this->app->make('events'); $events = $this->app->make('events');