Per-tag permissions!

Pretty easy to implement with the groundwork I’ve done for permissions.
(All the logic is in TagsServiceProvider currently)
This commit is contained in:
Toby Zerner 2015-06-16 21:58:18 +09:30
parent f3f0684eee
commit 5345a1ef9e
8 changed files with 129 additions and 14 deletions

View File

@ -11,7 +11,7 @@ export default class TagDiscussionModal extends FormModal {
constructor(props) {
super(props);
this.tags = sortTags(app.store.all('tags'));
this.tags = sortTags(app.store.all('tags').filter(tag => tag.canStartDiscussion()));
this.selected = m.prop([]);
if (this.props.selectedTags) {
@ -28,6 +28,8 @@ export default class TagDiscussionModal extends FormModal {
}
addTag(tag) {
if (!tag.canStartDiscussion()) return;
var selected = this.selected();
var parent = tag.parent();
if (parent) {

View File

@ -21,4 +21,6 @@ Tag.prototype.discussionsCount = Model.prop('discussionsCount');
Tag.prototype.lastTime = Model.prop('lastTime', Model.date);
Tag.prototype.lastDiscussion = Model.one('lastDiscussion');
Tag.prototype.canStartDiscussion = Model.prop('canStartDiscussion');
export default Tag;

View File

@ -26,6 +26,7 @@ class CreateTagsTable extends Migration
$table->integer('position')->nullable();
$table->integer('parent_id')->unsigned()->nullable();
$table->string('default_sort', 50)->nullable();
$table->boolean('is_restricted')->default(0);
$table->integer('discussions_count')->unsigned()->default(0);
$table->integer('last_time')->unsigned()->nullable();

View File

@ -0,0 +1,25 @@
<?php namespace Flarum\Tags\Handlers;
use Flarum\Api\Events\WillRespond;
use Flarum\Api\Actions\Forum\ShowAction as ForumShowAction;
use Flarum\Tags\Tag;
class TagLoader
{
public function subscribe($events)
{
$events->listen('Flarum\Api\Events\WillRespond', __CLASS__.'@whenWillRespond');
}
public function whenWillRespond(WillRespond $event)
{
if ($event->action instanceof ForumShowAction) {
$forum = $event->data;
$query = Tag::whereVisibleTo($event->request->actor->getUser());
$forum->tags = $query->with('lastDiscussion')->get();
$forum->tags_ids = $forum->tags->lists('id');
}
}
}

View File

@ -1,9 +1,11 @@
<?php namespace Flarum\Tags\Handlers;
use Flarum\Tags\Tag;
use Flarum\Tags\Events\DiscussionWasTagged;
use Flarum\Core\Events\DiscussionWillBeSaved;
use Flarum\Core\Events\DiscussionWasDeleted;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Exceptions\PermissionDeniedException;
class TagSaver
{
@ -25,6 +27,13 @@ class TagSaver
$newTagIds[] = (int) $link['id'];
}
$newTags = Tag::whereIn('id', $newTagIds);
foreach ($newTags as $tag) {
if (! $tag->can($user, 'startDiscussion')) {
throw new PermissionDeniedException;
}
}
$oldTags = [];
if ($discussion->exists) {

View File

@ -1,9 +1,14 @@
<?php namespace Flarum\Tags;
use Flarum\Core\Models\Model;
use Flarum\Core\Support\Locked;
use Flarum\Core\Support\VisibleScope;
class Tag extends Model
{
use Locked;
use VisibleScope;
protected $table = 'tags';
protected $dates = ['last_time'];

View File

@ -19,6 +19,8 @@ class TagSerializer extends BaseSerializer
*/
protected function attributes($tag)
{
$user = $this->actor->getUser();
$attributes = [
'name' => $tag->name,
'description' => $tag->description,
@ -31,7 +33,8 @@ class TagSerializer extends BaseSerializer
'position' => $tag->position === null ? null : (int) $tag->position,
'defaultSort' => $tag->default_sort,
'isChild' => (bool) $tag->parent_id,
'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null
'lastTime' => $tag->last_time ? $tag->last_time->toRFC3339String() : null,
'canStartDiscussion' => $tag->can($user, 'startDiscussion')
];
return $this->extendAttributes($tag, $attributes);
@ -44,6 +47,6 @@ class TagSerializer extends BaseSerializer
protected function lastDiscussion()
{
return $this->hasOne('Flarum\Api\Serializers\DiscussionSerializer');
return $this->hasOne('Flarum\Api\Serializers\DiscussionBasicSerializer');
}
}

View File

@ -6,9 +6,13 @@ use Flarum\Extend\EventSubscribers;
use Flarum\Extend\Relationship;
use Flarum\Extend\SerializeRelationship;
use Flarum\Extend\ApiInclude;
use Flarum\Extend\ApiLink;
use Flarum\Extend\Permission;
use Flarum\Extend\DiscussionGambit;
use Flarum\Extend\PostType;
use Flarum\Core\Models\Discussion;
use Flarum\Core\Models\Post;
use Flarum\Core\Models\User;
class TagsServiceProvider extends ServiceProvider
{
@ -27,7 +31,8 @@ class TagsServiceProvider extends ServiceProvider
new EventSubscribers([
'Flarum\Tags\Handlers\DiscussionTaggedNotifier',
'Flarum\Tags\Handlers\TagSaver'
'Flarum\Tags\Handlers\TagSaver',
'Flarum\Tags\Handlers\TagLoader'
]),
new Relationship('Flarum\Core\Models\Discussion', 'tags', function ($model) {
@ -38,25 +43,88 @@ class TagsServiceProvider extends ServiceProvider
new ApiInclude(['discussions.index', 'discussions.show'], 'tags', true),
new Relationship('Flarum\Core\Models\Forum', 'tags', function ($model) {
return Tag::query();
}),
new SerializeRelationship('Flarum\Api\Serializers\ForumSerializer', 'hasMany', 'tags', 'Flarum\Tags\TagSerializer'),
new ApiInclude(['forum.show'], ['tags', 'tags.parent', 'tags.lastDiscussion'], true),
new ApiInclude(['forum.show'], ['tags', 'tags.lastDiscussion'], true),
new ApiLink(['forum.show'], ['tags.parent'], true),
(new Permission('discussion.tag'))
->serialize()
->grant(function ($grant, $user) {
$grant->where('start_user_id', $user->id);
// @todo add limitations to time etc. according to a config setting
}),
->serialize(),
// ->grant(function ($grant, $user) {
// $grant->where('start_user_id', $user->id);
// // @todo add limitations to time etc. according to a config setting
// }),
new DiscussionGambit('Flarum\Tags\TagGambit'),
new PostType('Flarum\Tags\DiscussionTaggedPost')
]);
Tag::scopeVisible(function ($query, User $user) {
$query->whereIn('id', $this->getTagsWithPermission($user, 'view'));
});
Tag::allow('startDiscussion', function (Tag $tag, User $user) {
if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.startDiscussion')) {
return true;
}
});
Discussion::scopeVisible(function ($query, User $user) {
$query->whereNotExists(function ($query) use ($user) {
return $query->select(app('db')->raw(1))
->from('discussions_tags')
->whereNotIn('tag_id', $this->getTagsWithPermission($user, 'view'))
->whereRaw('discussion_id = discussions.id');
});
});
Discussion::allow('*', function (Discussion $discussion, User $user, $action) {
$tags = $discussion->getRelation('tags');
if (! count($tags)) {
return;
}
$restricted = true;
// If the discussion has a tag that has been restricted, and the user
// has this permission for that tag, then they are allowed. If the
// discussion only has tags that have been restricted, then the user
// *must* have permission for at least one of them. Otherwise, inherit
// global permissions.
foreach ($tags as $tag) {
if ($tag->is_restricted) {
if ($user->hasPermission('tag'.$tag->id.'.discussion.'.$action)) {
return true;
}
} else {
$restricted = false;
}
}
if ($restricted) {
return false;
}
});
Post::allow('*', function (Post $post, User $user, $action) {
return $post->discussion->can($user, $action.'Posts');
});
}
protected function getTagsWithPermission($user, $permission) {
static $tags;
if (!$tags) $tags = Tag::all();
$ids = [];
foreach ($tags as $tag) {
if (! $tag->is_restricted || $user->hasPermission('tag'.$tag->id.'.'.$permission)) {
$ids[] = $tag->id;
}
}
return $ids;
}
/**