mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-04-24 04:33:06 +00:00
Started code update for new entity permission format
This commit is contained in:
parent
1d3dbd6f6e
commit
aee0e16194
9 changed files with 70 additions and 43 deletions
app
Auth/Permissions
Entities
resources/views/form
tests/Helpers
|
@ -3,18 +3,29 @@
|
||||||
namespace BookStack\Auth\Permissions;
|
namespace BookStack\Auth\Permissions;
|
||||||
|
|
||||||
use BookStack\Model;
|
use BookStack\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $role_id
|
||||||
|
* @property int $entity_id
|
||||||
|
* @property string $entity_type
|
||||||
|
* @property boolean $view
|
||||||
|
* @property boolean $create
|
||||||
|
* @property boolean $update
|
||||||
|
* @property boolean $delete
|
||||||
|
*/
|
||||||
class EntityPermission extends Model
|
class EntityPermission extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = ['role_id', 'action'];
|
public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
|
||||||
|
|
||||||
|
protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all this restriction's attached entity.
|
* Get this restriction's attached entity.
|
||||||
*
|
|
||||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
|
||||||
*/
|
*/
|
||||||
public function restrictable()
|
public function restrictable(): MorphTo
|
||||||
{
|
{
|
||||||
return $this->morphTo('restrictable');
|
return $this->morphTo('restrictable');
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,10 +250,13 @@ class JointPermissionBuilder
|
||||||
$permissions = $this->getEntityPermissionsForEntities($entities);
|
$permissions = $this->getEntityPermissionsForEntities($entities);
|
||||||
|
|
||||||
// Create a mapping of explicit entity permissions
|
// Create a mapping of explicit entity permissions
|
||||||
|
// TODO - Handle new format, Now getting all defined entity permissions
|
||||||
|
// from the above call, Need to handle entries with none, and the 'Other Roles' (role_id=0)
|
||||||
|
// fallback option.
|
||||||
$permissionMap = [];
|
$permissionMap = [];
|
||||||
foreach ($permissions as $permission) {
|
foreach ($permissions as $permission) {
|
||||||
$key = $permission->restrictable_type . ':' . $permission->restrictable_id . ':' . $permission->role_id;
|
$key = $permission->entity_type . ':' . $permission->entity_id . ':' . $permission->role_id;
|
||||||
$isRestricted = $entityRestrictedMap[$permission->restrictable_type . ':' . $permission->restrictable_id];
|
$isRestricted = $entityRestrictedMap[$permission->entity_type . ':' . $permission->entity_id];
|
||||||
$permissionMap[$key] = $isRestricted;
|
$permissionMap[$key] = $isRestricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,11 +322,10 @@ class JointPermissionBuilder
|
||||||
{
|
{
|
||||||
$idsByType = $this->entitiesToTypeIdMap($entities);
|
$idsByType = $this->entitiesToTypeIdMap($entities);
|
||||||
$permissionFetch = EntityPermission::query()
|
$permissionFetch = EntityPermission::query()
|
||||||
->where('action', '=', 'view')
|
|
||||||
->where(function (Builder $query) use ($idsByType) {
|
->where(function (Builder $query) use ($idsByType) {
|
||||||
foreach ($idsByType as $type => $ids) {
|
foreach ($idsByType as $type => $ids) {
|
||||||
$query->orWhere(function (Builder $query) use ($type, $ids) {
|
$query->orWhere(function (Builder $query) use ($type, $ids) {
|
||||||
$query->where('restrictable_type', '=', $type)->whereIn('restrictable_id', $ids);
|
$query->where('entity_type', '=', $type)->whereIn('entity_id', $ids);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,6 +59,8 @@ class PermissionApplicator
|
||||||
*/
|
*/
|
||||||
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
|
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
|
||||||
{
|
{
|
||||||
|
$this->ensureValidEntityAction($action);
|
||||||
|
|
||||||
$adminRoleId = Role::getSystemRole('admin')->id;
|
$adminRoleId = Role::getSystemRole('admin')->id;
|
||||||
if (in_array($adminRoleId, $userRoleIds)) {
|
if (in_array($adminRoleId, $userRoleIds)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -81,7 +83,7 @@ class PermissionApplicator
|
||||||
if ($currentEntity->restricted) {
|
if ($currentEntity->restricted) {
|
||||||
return $currentEntity->permissions()
|
return $currentEntity->permissions()
|
||||||
->whereIn('role_id', $userRoleIds)
|
->whereIn('role_id', $userRoleIds)
|
||||||
->where('action', '=', $action)
|
->where($action, '=', true)
|
||||||
->count() > 0;
|
->count() > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,18 +97,16 @@ class PermissionApplicator
|
||||||
*/
|
*/
|
||||||
public function checkUserHasEntityPermissionOnAny(string $action, string $entityClass = ''): bool
|
public function checkUserHasEntityPermissionOnAny(string $action, string $entityClass = ''): bool
|
||||||
{
|
{
|
||||||
if (strpos($action, '-') !== false) {
|
$this->ensureValidEntityAction($action);
|
||||||
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
|
|
||||||
}
|
|
||||||
|
|
||||||
$permissionQuery = EntityPermission::query()
|
$permissionQuery = EntityPermission::query()
|
||||||
->where('action', '=', $action)
|
->where($action, '=', true)
|
||||||
->whereIn('role_id', $this->getCurrentUserRoleIds());
|
->whereIn('role_id', $this->getCurrentUserRoleIds());
|
||||||
|
|
||||||
if (!empty($entityClass)) {
|
if (!empty($entityClass)) {
|
||||||
/** @var Entity $entityInstance */
|
/** @var Entity $entityInstance */
|
||||||
$entityInstance = app()->make($entityClass);
|
$entityInstance = app()->make($entityClass);
|
||||||
$permissionQuery = $permissionQuery->where('restrictable_type', '=', $entityInstance->getMorphClass());
|
$permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasPermission = $permissionQuery->count() > 0;
|
$hasPermission = $permissionQuery->count() > 0;
|
||||||
|
@ -255,4 +255,16 @@ class PermissionApplicator
|
||||||
|
|
||||||
return $this->currentUser()->roles->pluck('id')->values()->all();
|
return $this->currentUser()->roles->pluck('id')->values()->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the given action is a valid and expected entity action.
|
||||||
|
* Throws an exception if invalid otherwise does nothing.
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
protected function ensureValidEntityAction(string $action): void
|
||||||
|
{
|
||||||
|
if (!in_array($action, EntityPermission::PERMISSIONS)) {
|
||||||
|
throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||||
*/
|
*/
|
||||||
public function permissions(): MorphMany
|
public function permissions(): MorphMany
|
||||||
{
|
{
|
||||||
return $this->morphMany(EntityPermission::class, 'restrictable');
|
return $this->morphMany(EntityPermission::class, 'entity');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +186,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||||
{
|
{
|
||||||
return $this->permissions()
|
return $this->permissions()
|
||||||
->where('role_id', '=', $role_id)
|
->where('role_id', '=', $role_id)
|
||||||
->where('action', '=', $action)
|
->where($action, '=', true)
|
||||||
->count() > 0;
|
->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ class BookshelfRepo
|
||||||
*/
|
*/
|
||||||
public function copyDownPermissions(Bookshelf $shelf, $checkUserPermissions = true): int
|
public function copyDownPermissions(Bookshelf $shelf, $checkUserPermissions = true): int
|
||||||
{
|
{
|
||||||
$shelfPermissions = $shelf->permissions()->get(['role_id', 'action'])->toArray();
|
$shelfPermissions = $shelf->permissions()->get(['role_id', 'view', 'create', 'update', 'delete'])->toArray();
|
||||||
$shelfBooks = $shelf->books()->get(['id', 'restricted', 'owned_by']);
|
$shelfBooks = $shelf->books()->get(['id', 'restricted', 'owned_by']);
|
||||||
$updatedBookCount = 0;
|
$updatedBookCount = 0;
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ class Cloner
|
||||||
public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
|
public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
|
||||||
{
|
{
|
||||||
$targetEntity->restricted = $sourceEntity->restricted;
|
$targetEntity->restricted = $sourceEntity->restricted;
|
||||||
$permissions = $sourceEntity->permissions()->get(['role_id', 'action'])->toArray();
|
$permissions = $sourceEntity->permissions()->get(['role_id', 'view', 'create', 'update', 'delete'])->toArray();
|
||||||
$targetEntity->permissions()->delete();
|
$targetEntity->permissions()->delete();
|
||||||
$targetEntity->permissions()->createMany($permissions);
|
$targetEntity->permissions()->createMany($permissions);
|
||||||
$targetEntity->rebuildPermissions();
|
$targetEntity->rebuildPermissions();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Entities\Tools;
|
namespace BookStack\Entities\Tools;
|
||||||
|
|
||||||
use BookStack\Actions\ActivityType;
|
use BookStack\Actions\ActivityType;
|
||||||
|
use BookStack\Auth\Permissions\EntityPermission;
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Facades\Activity;
|
use BookStack\Facades\Activity;
|
||||||
|
@ -16,11 +17,9 @@ class PermissionsUpdater
|
||||||
*/
|
*/
|
||||||
public function updateFromPermissionsForm(Entity $entity, Request $request)
|
public function updateFromPermissionsForm(Entity $entity, Request $request)
|
||||||
{
|
{
|
||||||
$restricted = $request->get('restricted') === 'true';
|
$permissions = $request->get('permissions', null);
|
||||||
$permissions = $request->get('restrictions', null);
|
|
||||||
$ownerId = $request->get('owned_by', null);
|
$ownerId = $request->get('owned_by', null);
|
||||||
|
|
||||||
$entity->restricted = $restricted;
|
|
||||||
$entity->permissions()->delete();
|
$entity->permissions()->delete();
|
||||||
|
|
||||||
if (!is_null($permissions)) {
|
if (!is_null($permissions)) {
|
||||||
|
@ -52,18 +51,20 @@ class PermissionsUpdater
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format permissions provided from a permission form to be
|
* Format permissions provided from a permission form to be EntityPermission data.
|
||||||
* EntityPermission data.
|
|
||||||
*/
|
*/
|
||||||
protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): Collection
|
protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
|
||||||
{
|
{
|
||||||
return collect($permissions)->flatMap(function ($restrictions, $roleId) {
|
$formatted = [];
|
||||||
return collect($restrictions)->keys()->map(function ($action) use ($roleId) {
|
|
||||||
return [
|
foreach ($permissions as $roleId => $info) {
|
||||||
'role_id' => $roleId,
|
$entityPermissionData = ['role_id' => $roleId];
|
||||||
'action' => strtolower($action),
|
foreach (EntityPermission::PERMISSIONS as $permission) {
|
||||||
];
|
$entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
|
||||||
});
|
}
|
||||||
});
|
$formatted[] = $entityPermissionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $formatted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,19 +28,20 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="flex-container-row justify-space-between gap-x-xl wrap items-center">
|
<div class="flex-container-row justify-space-between gap-x-xl wrap items-center">
|
||||||
|
<input type="hidden" name="permissions[{{ $role->id }}][active]" value="true">
|
||||||
<div class="px-l">
|
<div class="px-l">
|
||||||
@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.view'), 'action' => 'view', 'disabled' => $inheriting])
|
@include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.view'), 'action' => 'view', 'disabled' => $inheriting])
|
||||||
</div>
|
</div>
|
||||||
<div class="px-l">
|
<div class="px-l">
|
||||||
@if(!$model instanceof \BookStack\Entities\Models\Page)
|
@if(!$model instanceof \BookStack\Entities\Models\Page)
|
||||||
@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.create'), 'action' => 'create', 'disabled' => $inheriting])
|
@include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.create'), 'action' => 'create', 'disabled' => $inheriting])
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="px-l">
|
<div class="px-l">
|
||||||
@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.update'), 'action' => 'update', 'disabled' => $inheriting])
|
@include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.update'), 'action' => 'update', 'disabled' => $inheriting])
|
||||||
</div>
|
</div>
|
||||||
<div class="px-l">
|
<div class="px-l">
|
||||||
@include('form.restriction-checkbox', ['name'=>'restrictions', 'label' => trans('common.delete'), 'action' => 'delete', 'disabled' => $inheriting])
|
@include('form.restriction-checkbox', ['name'=>'permissions', 'label' => trans('common.delete'), 'action' => 'delete', 'disabled' => $inheriting])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Tests\Helpers;
|
namespace Tests\Helpers;
|
||||||
|
|
||||||
|
use BookStack\Auth\Permissions\EntityPermission;
|
||||||
use BookStack\Auth\Role;
|
use BookStack\Auth\Role;
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
|
@ -207,13 +208,12 @@ class EntityProvider
|
||||||
$entity->permissions()->delete();
|
$entity->permissions()->delete();
|
||||||
|
|
||||||
$permissions = [];
|
$permissions = [];
|
||||||
foreach ($actions as $action) {
|
foreach ($roles as $role) {
|
||||||
foreach ($roles as $role) {
|
$permission = ['role_id' => $role->id];
|
||||||
$permissions[] = [
|
foreach (EntityPermission::PERMISSIONS as $possibleAction) {
|
||||||
'role_id' => $role->id,
|
$permission[$possibleAction] = in_array($possibleAction, $actions);
|
||||||
'action' => strtolower($action),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
$permissions[] = $permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
$entity->permissions()->createMany($permissions);
|
$entity->permissions()->createMany($permissions);
|
||||||
|
|
Loading…
Add table
Reference in a new issue