mirror of
https://github.com/flarum/framework.git
synced 2024-12-01 22:43:41 +08:00
Refactor for Policies Revamp (#105)
This commit is contained in:
parent
384f17ea94
commit
bf31f3e8ec
|
@ -13,12 +13,15 @@ use Flarum\Api\Serializer\ForumSerializer;
|
|||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Event\Saving;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Tags\Access;
|
||||
use Flarum\Tags\Api\Controller;
|
||||
use Flarum\Tags\Api\Serializer\TagSerializer;
|
||||
use Flarum\Tags\Content;
|
||||
use Flarum\Tags\Event\DiscussionWasTagged;
|
||||
use Flarum\Tags\Listener;
|
||||
use Flarum\Tags\LoadForumTagsRelationship;
|
||||
use Flarum\Tags\Post\DiscussionTaggedPost;
|
||||
use Flarum\Tags\Tag;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
|
@ -71,21 +74,36 @@ return [
|
|||
->serializeToForum('minSecondaryTags', 'flarum-tags.min_secondary_tags')
|
||||
->serializeToForum('maxSecondaryTags', 'flarum-tags.max_secondary_tags'),
|
||||
|
||||
(new Extend\Policy())
|
||||
->modelPolicy(Discussion::class, Access\DiscussionPolicy::class)
|
||||
->modelPolicy(Tag::class, Access\TagPolicy::class)
|
||||
->globalPolicy(Access\GlobalPolicy::class),
|
||||
|
||||
(new Extend\ModelVisibility(Discussion::class))
|
||||
->scope(Access\ScopeDiscussionVisibility::class)
|
||||
->scopeAll(Access\ScopeDiscussionVisibilityForAbility::class),
|
||||
|
||||
(new Extend\ModelVisibility(Flag::class))
|
||||
->scope(Access\ScopeFlagVisibility::class),
|
||||
|
||||
(new Extend\ModelVisibility(Tag::class))
|
||||
->scope(Access\ScopeTagVisibility::class),
|
||||
|
||||
new Extend\Locales(__DIR__.'/locale'),
|
||||
|
||||
(new Extend\View)
|
||||
->namespace('tags', __DIR__.'/views'),
|
||||
|
||||
(new Extend\Post)
|
||||
->type(DiscussionTaggedPost::class),
|
||||
|
||||
(new Extend\Event())
|
||||
->listen(Saving::class, Listener\SaveTagsToDatabase::class)
|
||||
->listen(DiscussionWasTagged::class, Listener\CreatePostWhenTagsAreChanged::class),
|
||||
|
||||
function (Dispatcher $events) {
|
||||
$events->subscribe(Listener\CreatePostWhenTagsAreChanged::class);
|
||||
$events->subscribe(Listener\FilterDiscussionListByTags::class);
|
||||
$events->subscribe(Listener\FilterPostsQueryByTag::class);
|
||||
$events->listen(Saving::class, Listener\SaveTagsToDatabase::class);
|
||||
$events->subscribe(Listener\UpdateTagMetadata::class);
|
||||
|
||||
$events->subscribe(Access\GlobalPolicy::class);
|
||||
$events->subscribe(Access\DiscussionPolicy::class);
|
||||
$events->subscribe(Access\TagPolicy::class);
|
||||
$events->subscribe(Access\FlagPolicy::class);
|
||||
},
|
||||
];
|
||||
|
|
|
@ -11,38 +11,22 @@ namespace Flarum\Tags\Access;
|
|||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Event\ScopeModelVisibility;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\AbstractPolicy;
|
||||
use Flarum\User\Access\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DiscussionPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $model = Discussion::class;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
*/
|
||||
public function __construct(Dispatcher $events, SettingsRepositoryInterface $settings)
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
|
@ -60,69 +44,24 @@ class DiscussionPolicy extends AbstractPolicy
|
|||
$tags = $discussion->tags;
|
||||
|
||||
if (count($tags)) {
|
||||
$restricted = false;
|
||||
$restrictedButHasAccess = false;
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag->is_restricted) {
|
||||
if (! $actor->hasPermission('tag'.$tag->id.'.discussion.'.$ability)) {
|
||||
return false;
|
||||
return $this->deny();
|
||||
}
|
||||
|
||||
$restricted = true;
|
||||
$restrictedButHasAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($restricted) {
|
||||
return true;
|
||||
if ($restrictedButHasAccess) {
|
||||
return $this->allow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function find(User $actor, Builder $query)
|
||||
{
|
||||
// Hide discussions which have tags that the user is not allowed to see, unless an extension overrides this.
|
||||
$query->where(function ($query) use ($actor) {
|
||||
$query
|
||||
->whereNotIn('discussions.id', function ($query) use ($actor) {
|
||||
return $query->select('discussion_id')
|
||||
->from('discussion_tag')
|
||||
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'viewDiscussions'));
|
||||
})
|
||||
->orWhere(function ($query) use ($actor) {
|
||||
$this->events->dispatch(
|
||||
new ScopeModelVisibility($query, $actor, 'viewDiscussionsInRestrictedTags')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Hide discussions with no tags if the user doesn't have that global
|
||||
// permission.
|
||||
if (! $actor->hasPermission('viewDiscussions')) {
|
||||
$query->has('tags');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
* @param string $ability
|
||||
*/
|
||||
public function findWithPermission(User $actor, Builder $query, $ability)
|
||||
{
|
||||
// If a discussion requires a certain permission in order for it to be
|
||||
// visible, then we can check if the user has been granted that
|
||||
// permission for any of the discussion's tags.
|
||||
$query->whereIn('discussions.id', function ($query) use ($actor, $ability) {
|
||||
return $query->select('discussion_id')
|
||||
->from('discussion_tag')
|
||||
->whereIn('tag_id', Tag::getIdsWhereCan($actor, 'discussion.'.$ability));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks, if the user is still allowed to edit the tags
|
||||
* based on the configuration item.
|
||||
|
|
|
@ -9,28 +9,39 @@
|
|||
|
||||
namespace Flarum\Tags\Access;
|
||||
|
||||
use Flarum\Event\GetPermission;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Tags\Tag;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Flarum\User\Access\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
|
||||
class GlobalPolicy
|
||||
class GlobalPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
protected $settings;
|
||||
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$events->listen(GetPermission::class, [$this, 'grantGlobalDiscussionPermissions']);
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GetPermission $event
|
||||
* @return bool
|
||||
* @param Flarum\User\User $actor
|
||||
* @param string $ability
|
||||
* @return bool|void
|
||||
*/
|
||||
public function grantGlobalDiscussionPermissions(GetPermission $event)
|
||||
public function can(User $actor, string $ability)
|
||||
{
|
||||
if (in_array($event->ability, ['viewDiscussions', 'startDiscussion']) && is_null($event->model)) {
|
||||
return ! empty(Tag::getIdsWhereCan($event->actor, $event->ability));
|
||||
if (in_array($ability, ['viewDiscussions', 'startDiscussion'])) {
|
||||
$enoughPrimary = count(Tag::getIdsWhereCan($actor, $ability, true, false)) >= $this->settings->get('min_primary_tags');
|
||||
$enoughSecondary = count(Tag::getIdsWhereCan($actor, $ability, false, true)) >= $this->settings->get('min_secondary_tags');
|
||||
|
||||
if ($enoughPrimary && $enoughSecondary) {
|
||||
return $this->allow();
|
||||
} else {
|
||||
return $this->deny();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
43
extensions/tags/src/Access/ScopeDiscussionVisibility.php
Normal file
43
extensions/tags/src/Access/ScopeDiscussionVisibility.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?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\Tags\Access;
|
||||
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeDiscussionVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(User $actor, Builder $query)
|
||||
{
|
||||
// Hide discussions which have tags that the user is not allowed to see, unless an extension overrides this.
|
||||
$query->where(function ($query) use ($actor) {
|
||||
$query
|
||||
->whereNotIn('discussions.id', function ($query) use ($actor) {
|
||||
return $query->select('discussion_id')
|
||||
->from('discussion_tag')
|
||||
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'viewDiscussions'));
|
||||
})
|
||||
->orWhere(function ($query) use ($actor) {
|
||||
$query->whereVisibleTo($actor, 'viewDiscussionsInRestrictedTags');
|
||||
});
|
||||
});
|
||||
|
||||
// Hide discussions with no tags if the user doesn't have that global
|
||||
// permission.
|
||||
if (! $actor->hasPermission('viewDiscussions')) {
|
||||
$query->has('tags');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?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\Tags\Access;
|
||||
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeDiscussionVisibilityForAbility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
* @param string $ability
|
||||
*/
|
||||
public function __invoke(User $actor, Builder $query, $ability)
|
||||
{
|
||||
if (substr($ability, 0, 4) === 'view') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a discussion requires a certain permission in order for it to be
|
||||
// visible, then we can check if the user has been granted that
|
||||
// permission for any of the discussion's tags.
|
||||
$query->whereIn('discussions.id', function ($query) use ($actor, $ability) {
|
||||
return $query->select('discussion_id')
|
||||
->from('discussion_tag')
|
||||
->whereIn('tag_id', Tag::getIdsWhereCan($actor, 'discussion.'.$ability));
|
||||
});
|
||||
}
|
||||
}
|
11
extensions/tags/src/Access/FlagPolicy.php → extensions/tags/src/Access/ScopeFlagVisibility.php
Executable file → Normal file
11
extensions/tags/src/Access/FlagPolicy.php → extensions/tags/src/Access/ScopeFlagVisibility.php
Executable file → Normal file
|
@ -9,24 +9,17 @@
|
|||
|
||||
namespace Flarum\Tags\Access;
|
||||
|
||||
use Flarum\Flags\Flag;
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class FlagPolicy extends AbstractPolicy
|
||||
class ScopeFlagVisibility
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $model = Flag::class;
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function find(User $actor, Builder $query)
|
||||
public function __invoke(User $actor, Builder $query)
|
||||
{
|
||||
$query
|
||||
->select('flags.*')
|
26
extensions/tags/src/Access/ScopeTagVisibility.php
Normal file
26
extensions/tags/src/Access/ScopeTagVisibility.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?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\Tags\Access;
|
||||
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ScopeTagVisibility
|
||||
{
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function __invoke(User $actor, Builder $query)
|
||||
{
|
||||
$query->whereNotIn('id', Tag::getIdsWhereCannot($actor, 'viewDiscussions'));
|
||||
}
|
||||
}
|
|
@ -10,26 +10,11 @@
|
|||
namespace Flarum\Tags\Access;
|
||||
|
||||
use Flarum\Tags\Tag;
|
||||
use Flarum\User\AbstractPolicy;
|
||||
use Flarum\User\Access\AbstractPolicy;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TagPolicy extends AbstractPolicy
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $model = Tag::class;
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Builder $query
|
||||
*/
|
||||
public function find(User $actor, Builder $query)
|
||||
{
|
||||
$query->whereNotIn('id', Tag::getIdsWhereCannot($actor, 'viewDiscussions'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $actor
|
||||
* @param Tag $tag
|
||||
|
@ -37,9 +22,8 @@ class TagPolicy extends AbstractPolicy
|
|||
*/
|
||||
public function startDiscussion(User $actor, Tag $tag)
|
||||
{
|
||||
if ((! $tag->is_restricted && $actor->hasPermission('startDiscussion'))
|
||||
|| ($tag->is_restricted && $actor->hasPermission('tag'.$tag->id.'.startDiscussion'))) {
|
||||
return true;
|
||||
if ($tag->is_restricted) {
|
||||
return $actor->hasPermission('tag'.$tag->id.'.startDiscussion') ? $this->allow() : $this->deny();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,35 +9,17 @@
|
|||
|
||||
namespace Flarum\Tags\Listener;
|
||||
|
||||
use Flarum\Event\ConfigurePostTypes;
|
||||
use Flarum\Tags\Event\DiscussionWasTagged;
|
||||
use Flarum\Tags\Post\DiscussionTaggedPost;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CreatePostWhenTagsAreChanged
|
||||
{
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(ConfigurePostTypes::class, [$this, 'addPostType']);
|
||||
$events->listen(DiscussionWasTagged::class, [$this, 'whenDiscussionWasTagged']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConfigurePostTypes $event
|
||||
*/
|
||||
public function addPostType(ConfigurePostTypes $event)
|
||||
{
|
||||
$event->add(DiscussionTaggedPost::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DiscussionWasTagged $event
|
||||
* @return void
|
||||
*/
|
||||
public function whenDiscussionWasTagged(DiscussionWasTagged $event)
|
||||
public function handle(DiscussionWasTagged $event)
|
||||
{
|
||||
$post = DiscussionTaggedPost::reply(
|
||||
$event->discussion->id,
|
||||
|
|
|
@ -190,7 +190,7 @@ class Tag extends AbstractModel
|
|||
Permission::where('permission', 'like', "tag{$this->id}.%")->delete();
|
||||
}
|
||||
|
||||
protected static function getIdsWherePermission(User $user, string $permission, bool $condition = true): array
|
||||
protected static function getIdsWherePermission(User $user, string $permission, bool $condition = true, bool $includePrimary, bool $includeSecondary): array
|
||||
{
|
||||
static $tags;
|
||||
|
||||
|
@ -212,7 +212,9 @@ class Tag extends AbstractModel
|
|||
$can = $canForTag($tag->parent);
|
||||
}
|
||||
|
||||
if ($can === $condition) {
|
||||
$isPrimary = $tag->position === null && ! $tag->parent;
|
||||
|
||||
if ($can === $condition && ($includePrimary && $isPrimary || $includeSecondary && ! $isPrimary)) {
|
||||
$ids[] = $tag->id;
|
||||
}
|
||||
}
|
||||
|
@ -220,13 +222,13 @@ class Tag extends AbstractModel
|
|||
return $ids;
|
||||
}
|
||||
|
||||
public static function getIdsWhereCan(User $user, string $permission): array
|
||||
public static function getIdsWhereCan(User $user, string $permission, bool $includePrimary = true, bool $includeSecondary = true): array
|
||||
{
|
||||
return static::getIdsWherePermission($user, $permission, true);
|
||||
return static::getIdsWherePermission($user, $permission, true, $includePrimary, $includeSecondary);
|
||||
}
|
||||
|
||||
public static function getIdsWhereCannot(User $user, string $permission): array
|
||||
public static function getIdsWhereCannot(User $user, string $permission, bool $includePrimary = true, bool $includeSecondary = true): array
|
||||
{
|
||||
return static::getIdsWherePermission($user, $permission, false);
|
||||
return static::getIdsWherePermission($user, $permission, false, $includePrimary, $includeSecondary);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user