0
0
Fork 0
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:
Dan Brown 2022-10-08 13:52:59 +01:00
parent 1d3dbd6f6e
commit aee0e16194
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
9 changed files with 70 additions and 43 deletions

View file

@ -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');
} }

View file

@ -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);
}); });
} }
}); });

View file

@ -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');
}
}
} }

View file

@ -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;
} }

View file

@ -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;

View file

@ -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();

View file

@ -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;
} }
} }

View file

@ -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>

View file

@ -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);