mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added users to permission form interface
Also updated non-joint permission handling to support user permissions.
This commit is contained in:
parent
f8c4725166
commit
7a269e7689
@ -3,9 +3,9 @@
|
||||
namespace BookStack\Auth\Permissions;
|
||||
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -22,17 +22,9 @@ class EntityPermission extends Model
|
||||
{
|
||||
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
|
||||
|
||||
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
|
||||
protected $fillable = ['role_id', 'user_id', 'view', 'create', 'update', 'delete'];
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get this restriction's attached entity.
|
||||
*/
|
||||
public function restrictable(): MorphTo
|
||||
{
|
||||
return $this->morphTo('restrictable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role assigned to this entity permission.
|
||||
*/
|
||||
@ -40,4 +32,12 @@ class EntityPermission extends Model
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user assigned to this entity permission.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class PermissionApplicator
|
||||
return $hasRolePermission;
|
||||
}
|
||||
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action);
|
||||
$hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $user->id, $action);
|
||||
|
||||
return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions;
|
||||
}
|
||||
@ -57,7 +57,7 @@ class PermissionApplicator
|
||||
* Check if there are permissions that are applicable for the given entity item, action and roles.
|
||||
* Returns null when no entity permissions are in force.
|
||||
*/
|
||||
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
|
||||
protected function hasEntityPermission(Entity $entity, array $userRoleIds, int $userId, string $action): ?bool
|
||||
{
|
||||
$this->ensureValidEntityAction($action);
|
||||
|
||||
@ -79,8 +79,9 @@ class PermissionApplicator
|
||||
|
||||
foreach ($chain as $currentEntity) {
|
||||
$relevantPermissions = $currentEntity->permissions()
|
||||
->where(function (Builder $query) use ($userRoleIds) {
|
||||
->where(function (Builder $query) use ($userRoleIds, $userId) {
|
||||
$query->whereIn('role_id', $userRoleIds)
|
||||
->orWhere('user_id', '=', $userId)
|
||||
->orWhere(function (Builder $query) {
|
||||
$query->whereNull(['role_id', 'user_id']);
|
||||
});
|
||||
@ -88,22 +89,17 @@ class PermissionApplicator
|
||||
->get(['role_id', 'user_id', $action])
|
||||
->all();
|
||||
|
||||
// TODO - Update below for user permissions
|
||||
|
||||
// 1. Default fallback set and allows, no role permissions -> True
|
||||
// 2. Default fallback set and prevents, no role permissions -> False
|
||||
// 3. Role permission allows, fallback set and allows -> True
|
||||
// 3. Role permission allows, fallback set and prevents -> True
|
||||
// 3. Role permission allows, fallback not set -> True
|
||||
// 3. Role permission prevents, fallback set and allows -> False
|
||||
// 3. Role permission prevents, fallback set and prevents -> False
|
||||
// 3. Role permission prevents, fallback not set -> False
|
||||
// 4. Nothing exists -> Continue
|
||||
// Permissions work on specificity, in order of:
|
||||
// 1. User-specific permissions
|
||||
// 2. Role-specific permissions
|
||||
// 3. Fallback-specific permissions
|
||||
// For role permissions, the system tries to be fairly permissive, in that if the user has two roles,
|
||||
// one lacking and one permitting an action, they will be permitted.
|
||||
|
||||
// If the default is set, we have to return something here.
|
||||
$allowedById = [];
|
||||
foreach ($relevantPermissions as $permission) {
|
||||
$allowedById[$permission->role_id . ':' . $permission->user_id] = $permission->$action;
|
||||
$allowedById[($permission->role_id ?? '') . ':' . ($permission->user_id ?? '')] = $permission->$action;
|
||||
}
|
||||
|
||||
// Continue up the chain if no applicable entity permission overrides.
|
||||
@ -111,7 +107,14 @@ class PermissionApplicator
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have user-role-specific permissions set, allow if any of those
|
||||
// If we have user-specific permissions set, return the status of that
|
||||
// since it's the most specific possible.
|
||||
$userKey = ':' . $userId;
|
||||
if (isset($allowedById[$userKey])) {
|
||||
return $allowedById[$userKey];
|
||||
}
|
||||
|
||||
// If we have role-specific permissions set, allow if any of those
|
||||
// role permissions allow access.
|
||||
$hasDefault = isset($allowedById[':']);
|
||||
if (!$hasDefault || count($allowedById) > 1) {
|
||||
@ -140,8 +143,10 @@ class PermissionApplicator
|
||||
|
||||
$permissionQuery = EntityPermission::query()
|
||||
->where($action, '=', true)
|
||||
->whereIn('role_id', $this->getCurrentUserRoleIds());
|
||||
// TODO - Update for user permission
|
||||
->where(function (Builder $query) {
|
||||
$query->whereIn('role_id', $this->getCurrentUserRoleIds())
|
||||
->orWhere('user_id', '=', $this->currentUser()->id);
|
||||
});
|
||||
|
||||
if (!empty($entityClass)) {
|
||||
/** @var Entity $entityInstance */
|
||||
|
@ -27,6 +27,19 @@ class PermissionFormData
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions with assigned users.
|
||||
*/
|
||||
public function permissionsWithUsers(): array
|
||||
{
|
||||
return $this->entity->permissions()
|
||||
->with('user')
|
||||
->whereNotNull('user_id')
|
||||
->get()
|
||||
->sortBy('user.name')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the roles that don't yet have specific permissions for the
|
||||
* entity we're managing permissions for.
|
||||
|
@ -10,7 +10,6 @@ use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Facades\Activity;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PermissionsUpdater
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
|
||||
use BookStack\Auth\Permissions\EntityPermission;
|
||||
use BookStack\Auth\Permissions\PermissionFormData;
|
||||
use BookStack\Auth\Role;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
@ -175,4 +176,25 @@ class PermissionsController extends Controller
|
||||
'inheriting' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an empty entity permissions form row for the given user.
|
||||
*/
|
||||
public function formRowForUser(string $entityType, string $userId)
|
||||
{
|
||||
$this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own'));
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::query()->findOrFail($userId);
|
||||
|
||||
return view('form.entity-permissions-row', [
|
||||
'modelType' => 'user',
|
||||
'modelId' => $user->id,
|
||||
'modelName' => $user->name,
|
||||
'modelDescription' => '',
|
||||
'permission' => new EntityPermission(),
|
||||
'entityType' => $entityType,
|
||||
'inheriting' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ export class EntityPermissions extends Component {
|
||||
this.everyoneInheritToggle = this.$refs.everyoneInherit;
|
||||
this.roleSelect = this.$refs.roleSelect;
|
||||
this.roleContainer = this.$refs.roleContainer;
|
||||
this.userContainer = this.$refs.userContainer;
|
||||
this.userSelectContainer = this.$refs.userSelectContainer;
|
||||
|
||||
this.setupListeners();
|
||||
}
|
||||
@ -40,6 +42,14 @@ export class EntityPermissions extends Component {
|
||||
this.addRoleRow(roleId);
|
||||
}
|
||||
});
|
||||
|
||||
// User select change
|
||||
this.userSelectContainer.querySelector('input[name="user_select"]').addEventListener('change', event => {
|
||||
const userId = event.target.value;
|
||||
if (userId) {
|
||||
this.addUserRow(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async addRoleRow(roleId) {
|
||||
@ -52,13 +62,32 @@ export class EntityPermissions extends Component {
|
||||
}
|
||||
|
||||
// Get and insert new row
|
||||
const resp = await window.$http.get(`/permissions/form-row/${this.entityType}/${roleId}`);
|
||||
const resp = await window.$http.get(`/permissions/role-form-row/${this.entityType}/${roleId}`);
|
||||
const row = htmlToDom(resp.data);
|
||||
this.roleContainer.append(row);
|
||||
|
||||
this.roleSelect.disabled = false;
|
||||
}
|
||||
|
||||
async addUserRow(userId) {
|
||||
const exists = this.userContainer.querySelector(`[name^="permissions[user][${userId}]"]`) !== null;
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggle = this.userSelectContainer.querySelector('.dropdown-search-toggle-select');
|
||||
toggle.classList.add('disabled');
|
||||
this.userContainer.style.pointerEvents = 'none';
|
||||
|
||||
// Get and insert new row
|
||||
const resp = await window.$http.get(`/permissions/user-form-row/${this.entityType}/${userId}`);
|
||||
const row = htmlToDom(resp.data);
|
||||
this.userContainer.append(row);
|
||||
|
||||
toggle.classList.remove('disabled');
|
||||
this.userContainer.style.pointerEvents = null;
|
||||
}
|
||||
|
||||
removeRowOnButtonClick(button) {
|
||||
const row = button.closest('.item-list-row');
|
||||
const modelId = button.dataset.modelId;
|
||||
@ -72,7 +101,7 @@ export class EntityPermissions extends Component {
|
||||
if (modelType === 'role') {
|
||||
this.roleSelect.append(option);
|
||||
}
|
||||
// TODO - User role!
|
||||
|
||||
row.remove();
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,7 @@ return [
|
||||
'permissions_role_everyone_else' => 'Everyone Else',
|
||||
'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.',
|
||||
'permissions_role_override' => 'Override permissions for role',
|
||||
'permissions_user_override' => 'Override permissions for user',
|
||||
'permissions_inherit_defaults' => 'Inherit defaults',
|
||||
|
||||
// Search
|
||||
|
@ -11,7 +11,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||
<div component="permissions-table" class="item-list-row flex-container-row justify-space-between wrap">
|
||||
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
|
||||
<div class="text-large" title="{{ $modelType === 'fallback' ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
|
||||
@icon($modelType === 'fallback' ? 'groups' : 'role')
|
||||
@icon($modelType === 'fallback' ? 'groups' : ($modelType === 'role' ? 'role' : 'user'))
|
||||
</div>
|
||||
<span>
|
||||
<strong>{{ $modelName }}</strong> <br>
|
||||
|
@ -35,6 +35,27 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<div refs="entity-permissions@user-container" class="item-list mt-m mb-m">
|
||||
@foreach($data->permissionsWithUsers() as $permission)
|
||||
@include('form.entity-permissions-row', [
|
||||
'permission' => $permission,
|
||||
'modelType' => 'user',
|
||||
'modelId' => $permission->user->id,
|
||||
'modelName' => $permission->user->name,
|
||||
'modelDescription' => '',
|
||||
'entityType' => $model->getType(),
|
||||
'inheriting' => false,
|
||||
])
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="flex-container-row justify-flex-end mb-xl">
|
||||
<div refs="entity-permissions@user-select-container" class="flex-container-row items-center gap-m">
|
||||
<label for="user_select" class="m-none p-none"><span class="bold">{{ trans('entities.permissions_user_override') }}</span></label>
|
||||
@include('form.user-select', ['name' => 'user_select', 'user' => null])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div refs="entity-permissions@role-container" class="item-list mt-m mb-m">
|
||||
@foreach($data->permissionsWithRoles() as $permission)
|
||||
@include('form.entity-permissions-row', [
|
||||
|
@ -217,7 +217,8 @@ Route::middleware('auth')->group(function () {
|
||||
Route::get('/home', [HomeController::class, 'index']);
|
||||
|
||||
// Permissions
|
||||
Route::get('/permissions/form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
|
||||
Route::get('/permissions/role-form-row/{entityType}/{roleId}', [PermissionsController::class, 'formRowForRole']);
|
||||
Route::get('/permissions/user-form-row/{entityType}/{userId}', [PermissionsController::class, 'formRowForUser']);
|
||||
|
||||
// Maintenance
|
||||
Route::get('/settings/maintenance', [MaintenanceController::class, 'index']);
|
||||
|
Loading…
Reference in New Issue
Block a user