mirror of
https://github.com/flarum/framework.git
synced 2024-11-25 09:41:49 +08:00
Implement hidden permission groups (#2129)
Only users that have the new `viewHiddenGroups` permissions will be able to see these groups. You might want this when you want to give certain users special permissions, but don't want to make your authorization scheme public to regular users. Co-authored-by: luceos <daniel+github@klabbers.email>
This commit is contained in:
parent
df8f73bd3d
commit
6e8884f190
|
@ -3,6 +3,7 @@ import Button from '../../common/components/Button';
|
||||||
import Badge from '../../common/components/Badge';
|
import Badge from '../../common/components/Badge';
|
||||||
import Group from '../../common/models/Group';
|
import Group from '../../common/models/Group';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
|
import Switch from '../../common/components/Switch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `EditGroupModal` component shows a modal dialog which allows the user
|
* The `EditGroupModal` component shows a modal dialog which allows the user
|
||||||
|
@ -16,6 +17,7 @@ export default class EditGroupModal extends Modal {
|
||||||
this.namePlural = m.prop(this.group.namePlural() || '');
|
this.namePlural = m.prop(this.group.namePlural() || '');
|
||||||
this.icon = m.prop(this.group.icon() || '');
|
this.icon = m.prop(this.group.icon() || '');
|
||||||
this.color = m.prop(this.group.color() || '');
|
this.color = m.prop(this.group.color() || '');
|
||||||
|
this.isHidden = m.prop(this.group.isHidden() || false);
|
||||||
}
|
}
|
||||||
|
|
||||||
className() {
|
className() {
|
||||||
|
@ -89,6 +91,18 @@ export default class EditGroupModal extends Modal {
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'hidden',
|
||||||
|
<div className="Form-group">
|
||||||
|
{Switch.component({
|
||||||
|
state: !!Number(this.isHidden()),
|
||||||
|
children: app.translator.trans('core.admin.edit_group.hide_label'),
|
||||||
|
onchange: this.isHidden,
|
||||||
|
})}
|
||||||
|
</div>,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
items.add(
|
items.add(
|
||||||
'submit',
|
'submit',
|
||||||
<div className="Form-group">
|
<div className="Form-group">
|
||||||
|
@ -118,6 +132,7 @@ export default class EditGroupModal extends Modal {
|
||||||
namePlural: this.namePlural(),
|
namePlural: this.namePlural(),
|
||||||
color: this.color(),
|
color: this.color(),
|
||||||
icon: this.icon(),
|
icon: this.icon(),
|
||||||
|
isHidden: this.isHidden(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,16 @@ export default class PermissionGrid extends Component {
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'viewHiddenGroups',
|
||||||
|
{
|
||||||
|
icon: 'fas fa-users',
|
||||||
|
label: app.translator.trans('core.admin.permissions.view_hidden_groups_label'),
|
||||||
|
permission: 'viewHiddenGroups',
|
||||||
|
},
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
items.add(
|
items.add(
|
||||||
'viewUserList',
|
'viewUserList',
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@ Object.assign(Group.prototype, {
|
||||||
namePlural: Model.attribute('namePlural'),
|
namePlural: Model.attribute('namePlural'),
|
||||||
color: Model.attribute('color'),
|
color: Model.attribute('color'),
|
||||||
icon: Model.attribute('icon'),
|
icon: Model.attribute('icon'),
|
||||||
|
isHidden: Model.attribute('isHidden'),
|
||||||
});
|
});
|
||||||
|
|
||||||
Group.ADMINISTRATOR_ID = '1';
|
Group.ADMINISTRATOR_ID = '1';
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Flarum\Database\Migration;
|
||||||
|
|
||||||
|
return Migration::addColumns('groups', [
|
||||||
|
'is_hidden' => ['boolean', 'default' => false]
|
||||||
|
]);
|
|
@ -26,6 +26,8 @@ class ListGroupsController extends AbstractListController
|
||||||
*/
|
*/
|
||||||
protected function data(ServerRequestInterface $request, Document $document)
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
{
|
{
|
||||||
return Group::all();
|
$actor = $request->getAttribute('actor');
|
||||||
|
|
||||||
|
return Group::whereVisibleTo($actor)->get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ class BasicUserSerializer extends AbstractSerializer
|
||||||
*/
|
*/
|
||||||
protected function groups($user)
|
protected function groups($user)
|
||||||
{
|
{
|
||||||
return $this->hasMany($user, GroupSerializer::class);
|
if ($this->getActor()->can('viewHiddenGroups')) {
|
||||||
|
return $this->hasMany($user, GroupSerializer::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->hasMany($user, GroupSerializer::class, 'visibleGroups');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ class GroupSerializer extends AbstractSerializer
|
||||||
'namePlural' => $this->translateGroupName($group->name_plural),
|
'namePlural' => $this->translateGroupName($group->name_plural),
|
||||||
'color' => $group->color,
|
'color' => $group->color,
|
||||||
'icon' => $group->icon,
|
'icon' => $group->icon,
|
||||||
|
'isHidden' => $group->is_hidden
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,8 @@ class CreateGroupHandler
|
||||||
Arr::get($data, 'attributes.nameSingular'),
|
Arr::get($data, 'attributes.nameSingular'),
|
||||||
Arr::get($data, 'attributes.namePlural'),
|
Arr::get($data, 'attributes.namePlural'),
|
||||||
Arr::get($data, 'attributes.color'),
|
Arr::get($data, 'attributes.color'),
|
||||||
Arr::get($data, 'attributes.icon')
|
Arr::get($data, 'attributes.icon'),
|
||||||
|
Arr::get($data, 'attributes.isHidden', false)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->events->dispatch(
|
$this->events->dispatch(
|
||||||
|
|
|
@ -74,6 +74,10 @@ class EditGroupHandler
|
||||||
$group->icon = $attributes['icon'];
|
$group->icon = $attributes['icon'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($attributes['isHidden'])) {
|
||||||
|
$group->is_hidden = $attributes['isHidden'];
|
||||||
|
}
|
||||||
|
|
||||||
$this->events->dispatch(
|
$this->events->dispatch(
|
||||||
new Saving($group, $actor, $data)
|
new Saving($group, $actor, $data)
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,7 @@ use Flarum\User\User;
|
||||||
* @property string $name_plural
|
* @property string $name_plural
|
||||||
* @property string|null $color
|
* @property string|null $color
|
||||||
* @property string|null $icon
|
* @property string|null $icon
|
||||||
|
* @property bool $is_hidden
|
||||||
* @property \Illuminate\Database\Eloquent\Collection $users
|
* @property \Illuminate\Database\Eloquent\Collection $users
|
||||||
* @property \Illuminate\Database\Eloquent\Collection $permissions
|
* @property \Illuminate\Database\Eloquent\Collection $permissions
|
||||||
*/
|
*/
|
||||||
|
@ -72,9 +73,10 @@ class Group extends AbstractModel
|
||||||
* @param string $namePlural
|
* @param string $namePlural
|
||||||
* @param string $color
|
* @param string $color
|
||||||
* @param string $icon
|
* @param string $icon
|
||||||
|
* @param bool $isHidden
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function build($nameSingular, $namePlural, $color, $icon)
|
public static function build($nameSingular, $namePlural, $color = null, $icon = null, bool $isHidden = false): self
|
||||||
{
|
{
|
||||||
$group = new static;
|
$group = new static;
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ class Group extends AbstractModel
|
||||||
$group->name_plural = $namePlural;
|
$group->name_plural = $namePlural;
|
||||||
$group->color = $color;
|
$group->color = $color;
|
||||||
$group->icon = $icon;
|
$group->icon = $icon;
|
||||||
|
$group->is_hidden = $isHidden;
|
||||||
|
|
||||||
$group->raise(new Created($group));
|
$group->raise(new Created($group));
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Flarum\Group;
|
||||||
|
|
||||||
use Flarum\User\AbstractPolicy;
|
use Flarum\User\AbstractPolicy;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class GroupPolicy extends AbstractPolicy
|
class GroupPolicy extends AbstractPolicy
|
||||||
{
|
{
|
||||||
|
@ -30,4 +31,15 @@ class GroupPolicy extends AbstractPolicy
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $actor
|
||||||
|
* @param Builder $query
|
||||||
|
*/
|
||||||
|
public function find(User $actor, Builder $query)
|
||||||
|
{
|
||||||
|
if ($actor->cannot('viewHiddenGroups')) {
|
||||||
|
$query->where('is_hidden', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -606,6 +606,11 @@ class User extends AbstractModel
|
||||||
return $this->belongsToMany(Group::class);
|
return $this->belongsToMany(Group::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function visibleGroups()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Group::class)->where('is_hidden', false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define the relationship with the user's notifications.
|
* Define the relationship with the user's notifications.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,15 +9,37 @@
|
||||||
|
|
||||||
namespace Flarum\Tests\integration\api\groups;
|
namespace Flarum\Tests\integration\api\groups;
|
||||||
|
|
||||||
use Flarum\Group\Group;
|
use Flarum\Tests\integration\RetrievesAuthorizedUsers;
|
||||||
use Flarum\Tests\integration\TestCase;
|
use Flarum\Tests\integration\TestCase;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class ListTest extends TestCase
|
class ListTest extends TestCase
|
||||||
{
|
{
|
||||||
|
use RetrievesAuthorizedUsers;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->prepareDatabase([
|
||||||
|
'users' => [
|
||||||
|
$this->adminUser(),
|
||||||
|
$this->normalUser(),
|
||||||
|
],
|
||||||
|
'groups' => [
|
||||||
|
$this->adminGroup(),
|
||||||
|
$this->hiddenGroup()
|
||||||
|
],
|
||||||
|
'group_user' => [
|
||||||
|
['user_id' => 1, 'group_id' => 1],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
*/
|
*/
|
||||||
public function shows_index_for_guest()
|
public function shows_limited_index_for_guest()
|
||||||
{
|
{
|
||||||
$response = $this->send(
|
$response = $this->send(
|
||||||
$this->request('GET', '/api/groups')
|
$this->request('GET', '/api/groups')
|
||||||
|
@ -26,6 +48,35 @@ class ListTest extends TestCase
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
$data = json_decode($response->getBody()->getContents(), true);
|
$data = json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
$this->assertEquals(Group::count(), count($data['data']));
|
$this->assertEquals(['1'], Arr::pluck($data['data'], 'id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function shows_index_for_admin()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('GET', '/api/groups', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode($response->getBody()->getContents(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(['1', '10'], Arr::pluck($data['data'], 'id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hiddenGroup(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => 10,
|
||||||
|
'name_singular' => 'Hidden',
|
||||||
|
'name_plural' => 'Ninjas',
|
||||||
|
'color' => null,
|
||||||
|
'icon' => 'fas fa-wrench',
|
||||||
|
'is_hidden' => 1
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user