mirror of
https://github.com/flarum/core.git
synced 2025-10-27 05:31:29 +01:00
Policy Extender and Tests (#2461)
Policy application has also been refactored, so that policies return one of `allow`, `deny`, `forceAllow`, `forceDeny`. The result of a set of policies is no longer the first non-null result, but rather the highest priority result (forceDeny > forceAllow > deny > allow, so if a single forceDeny is present, that beats out all other returned results). This removes order in which extensions boot as a factor.
This commit is contained in:
committed by
GitHub
parent
8901073d12
commit
d1dfa758e4
64
src/User/Access/AbstractPolicy.php
Normal file
64
src/User/Access/AbstractPolicy.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Access;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
abstract class AbstractPolicy
|
||||
{
|
||||
public const GLOBAL = 'GLOBAL';
|
||||
public const ALLOW = 'ALLOW';
|
||||
public const DENY = 'DENY';
|
||||
public const FORCE_ALLOW = 'FORCE_ALLOW';
|
||||
public const FORCE_DENY = 'FORCE_DENY';
|
||||
|
||||
protected function allow()
|
||||
{
|
||||
return static::ALLOW;
|
||||
}
|
||||
|
||||
protected function deny()
|
||||
{
|
||||
return static::DENY;
|
||||
}
|
||||
|
||||
protected function forceAllow()
|
||||
{
|
||||
return static::FORCE_ALLOW;
|
||||
}
|
||||
|
||||
protected function forceDeny()
|
||||
{
|
||||
return static::FORCE_DENY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $ability
|
||||
* @param $instance
|
||||
* @return bool|void
|
||||
*/
|
||||
public function checkAbility(User $actor, string $ability, $instance)
|
||||
{ // If a specific method for this ability is defined,
|
||||
// call that and return any non-null results
|
||||
if (method_exists($this, $ability)) {
|
||||
$result = call_user_func_array([$this, $ability], [$actor, $instance]);
|
||||
|
||||
if (! is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// If a "total access" method is defined, try that.
|
||||
if (method_exists($this, 'can')) {
|
||||
return call_user_func_array([$this, 'can'], [$actor, $ability, $instance]);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
src/User/Access/Gate.php
Normal file
131
src/User/Access/Gate.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User\Access;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Event\GetPermission;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Gate
|
||||
{
|
||||
protected const EVALUATION_CRITERIA_PRIORITY = [
|
||||
AbstractPolicy::FORCE_DENY => false,
|
||||
AbstractPolicy::FORCE_ALLOW => true,
|
||||
AbstractPolicy::DENY => false,
|
||||
AbstractPolicy::ALLOW => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $policyClasses;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $policies;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function __construct(Container $container, Dispatcher $events, array $policyClasses)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->events = $events;
|
||||
$this->policyClasses = $policyClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given ability should be granted for the current user.
|
||||
*
|
||||
* @param User $actor
|
||||
* @param string $ability
|
||||
* @param string|AbstractModel $model
|
||||
* @return bool
|
||||
*/
|
||||
public function allows(User $actor, string $ability, $model): bool
|
||||
{
|
||||
$results = [];
|
||||
$appliedPolicies = [];
|
||||
|
||||
if ($model) {
|
||||
$modelClasses = is_string($model) ? [$model] : array_merge(class_parents(($model)), [get_class($model)]);
|
||||
|
||||
foreach ($modelClasses as $class) {
|
||||
$appliedPolicies = array_merge($appliedPolicies, $this->getPolicies($class));
|
||||
}
|
||||
} else {
|
||||
$appliedPolicies = $this->getPolicies(AbstractPolicy::GLOBAL);
|
||||
}
|
||||
|
||||
foreach ($appliedPolicies as $policy) {
|
||||
$results[] = $policy->checkAbility($actor, $ability, $model);
|
||||
}
|
||||
|
||||
foreach (static::EVALUATION_CRITERIA_PRIORITY as $criteria => $decision) {
|
||||
if (in_array($criteria, $results, true)) {
|
||||
return $decision;
|
||||
}
|
||||
}
|
||||
|
||||
// START OLD DEPRECATED SYSTEM
|
||||
|
||||
// Fire an event so that core and extension modelPolicies can hook into
|
||||
// this permission query and explicitly grant or deny the
|
||||
// permission.
|
||||
$allowed = $this->events->until(
|
||||
new GetPermission($actor, $ability, $model)
|
||||
);
|
||||
|
||||
if (! is_null($allowed)) {
|
||||
return $allowed;
|
||||
}
|
||||
// END OLD DEPRECATED SYSTEM
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all policies for a given model and ability.
|
||||
*/
|
||||
protected function getPolicies(string $model)
|
||||
{
|
||||
$compiledPolicies = Arr::get($this->policies, $model);
|
||||
if (is_null($compiledPolicies)) {
|
||||
$policyClasses = Arr::get($this->policyClasses, $model, []);
|
||||
$compiledPolicies = array_map(function ($policyClass) {
|
||||
return $this->container->make($policyClass);
|
||||
}, $policyClasses);
|
||||
Arr::set($this->policies, $model, $compiledPolicies);
|
||||
}
|
||||
|
||||
return $compiledPolicies;
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,12 @@
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User;
|
||||
namespace Flarum\User\Access;
|
||||
|
||||
use Flarum\User\User;
|
||||
|
||||
class UserPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $model = User::class;
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param string $ability
|
||||
@@ -24,7 +21,7 @@ class UserPolicy extends AbstractPolicy
|
||||
public function can(User $actor, $ability)
|
||||
{
|
||||
if ($actor->hasPermission('user.'.$ability)) {
|
||||
return true;
|
||||
return $this->allow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Event\GetPermission;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class Gate
|
||||
{
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function __construct(Dispatcher $events)
|
||||
{
|
||||
$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($actor, $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(
|
||||
new GetPermission($actor, $ability, $arguments)
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -624,7 +624,7 @@ class User extends AbstractModel
|
||||
* @param mixed $arguments
|
||||
* @throws PermissionDeniedException
|
||||
*/
|
||||
public function assertCan($ability, $arguments = [])
|
||||
public function assertCan($ability, $arguments = null)
|
||||
{
|
||||
$this->assertPermission(
|
||||
$this->can($ability, $arguments)
|
||||
@@ -762,7 +762,7 @@ class User extends AbstractModel
|
||||
* @param array|mixed $arguments
|
||||
* @return bool
|
||||
*/
|
||||
public function can($ability, $arguments = [])
|
||||
public function can($ability, $arguments = null)
|
||||
{
|
||||
return static::$gate->allows($this, $ability, $arguments);
|
||||
}
|
||||
@@ -772,7 +772,7 @@ class User extends AbstractModel
|
||||
* @param array|mixed $arguments
|
||||
* @return bool
|
||||
*/
|
||||
public function cannot($ability, $arguments = [])
|
||||
public function cannot($ability, $arguments = null)
|
||||
{
|
||||
return ! $this->can($ability, $arguments);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Discussion\Access\DiscussionPolicy;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Group\Access\GroupPolicy;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Post\Access\PostPolicy;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\Access\ScopeUserVisibility;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
@@ -36,6 +42,16 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
$this->app->singleton('flarum.user.group_processors', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.policies', function () {
|
||||
return [
|
||||
Access\AbstractPolicy::GLOBAL => [],
|
||||
Discussion::class => [DiscussionPolicy::class],
|
||||
Group::class => [GroupPolicy::class],
|
||||
Post::class => [PostPolicy::class],
|
||||
User::class => [Access\UserPolicy::class],
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerDisplayNameDrivers()
|
||||
@@ -81,18 +97,17 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $this->app));
|
||||
}
|
||||
|
||||
User::setHasher($this->app->make('hash'));
|
||||
User::setGate($this->app->make(Gate::class));
|
||||
User::setDisplayNameDriver($this->app->make('flarum.user.display_name.driver'));
|
||||
|
||||
$events = $this->app->make('events');
|
||||
|
||||
User::setHasher($this->app->make('hash'));
|
||||
User::setGate($this->app->makeWith(Access\Gate::class, ['policyClasses' => $this->app->make('flarum.policies')]));
|
||||
User::setDisplayNameDriver($this->app->make('flarum.user.display_name.driver'));
|
||||
|
||||
$events->listen(Saving::class, SelfDemotionGuard::class);
|
||||
$events->listen(Registered::class, AccountActivationMailer::class);
|
||||
$events->listen(EmailChangeRequested::class, EmailConfirmationMailer::class);
|
||||
|
||||
$events->subscribe(UserMetadataUpdater::class);
|
||||
$events->subscribe(UserPolicy::class);
|
||||
|
||||
User::registerPreference('discloseOnline', 'boolval', true);
|
||||
User::registerPreference('indexProfile', 'boolval', true);
|
||||
|
||||
Reference in New Issue
Block a user