Started code update for new entity permission format
This commit is contained in:
		
							parent
							
								
									1d3dbd6f6e
								
							
						
					
					
						commit
						aee0e16194
					
				| 
						 | 
					@ -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) {
 | 
				
			||||||
                $permissions[] = [
 | 
					            $permission = ['role_id' => $role->id];
 | 
				
			||||||
                    'role_id' => $role->id,
 | 
					            foreach (EntityPermission::PERMISSIONS as $possibleAction) {
 | 
				
			||||||
                    'action'  => strtolower($action),
 | 
					                $permission[$possibleAction] = in_array($possibleAction, $actions);
 | 
				
			||||||
                ];
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            $permissions[] = $permission;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $entity->permissions()->createMany($permissions);
 | 
					        $entity->permissions()->createMany($permissions);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue