namespace BookStack\Auth\Permissions;
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
{
- protected $fillable = ['role_id', 'action'];
+ public const PERMISSIONS = ['view', 'create', 'update', 'delete'];
+
+ protected $fillable = ['role_id', 'view', 'create', 'update', 'delete'];
public $timestamps = false;
/**
- * Get all this restriction's attached entity.
- *
- * @return \Illuminate\Database\Eloquent\Relations\MorphTo
+ * Get this restriction's attached entity.
*/
- public function restrictable()
+ public function restrictable(): MorphTo
{
return $this->morphTo('restrictable');
}
$permissions = $this->getEntityPermissionsForEntities($entities);
// 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 = [];
foreach ($permissions as $permission) {
- $key = $permission->restrictable_type . ':' . $permission->restrictable_id . ':' . $permission->role_id;
- $isRestricted = $entityRestrictedMap[$permission->restrictable_type . ':' . $permission->restrictable_id];
+ $key = $permission->entity_type . ':' . $permission->entity_id . ':' . $permission->role_id;
+ $isRestricted = $entityRestrictedMap[$permission->entity_type . ':' . $permission->entity_id];
$permissionMap[$key] = $isRestricted;
}
{
$idsByType = $this->entitiesToTypeIdMap($entities);
$permissionFetch = EntityPermission::query()
- ->where('action', '=', 'view')
->where(function (Builder $query) use ($idsByType) {
foreach ($idsByType as $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);
});
}
});
*/
protected function hasEntityPermission(Entity $entity, array $userRoleIds, string $action): ?bool
{
+ $this->ensureValidEntityAction($action);
+
$adminRoleId = Role::getSystemRole('admin')->id;
if (in_array($adminRoleId, $userRoleIds)) {
return true;
if ($currentEntity->restricted) {
return $currentEntity->permissions()
->whereIn('role_id', $userRoleIds)
- ->where('action', '=', $action)
+ ->where($action, '=', true)
->count() > 0;
}
}
*/
public function checkUserHasEntityPermissionOnAny(string $action, string $entityClass = ''): bool
{
- if (strpos($action, '-') !== false) {
- throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission');
- }
+ $this->ensureValidEntityAction($action);
$permissionQuery = EntityPermission::query()
- ->where('action', '=', $action)
+ ->where($action, '=', true)
->whereIn('role_id', $this->getCurrentUserRoleIds());
if (!empty($entityClass)) {
/** @var Entity $entityInstance */
$entityInstance = app()->make($entityClass);
- $permissionQuery = $permissionQuery->where('restrictable_type', '=', $entityInstance->getMorphClass());
+ $permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
}
$hasPermission = $permissionQuery->count() > 0;
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');
+ }
+ }
}
*/
public function permissions(): MorphMany
{
- return $this->morphMany(EntityPermission::class, 'restrictable');
+ return $this->morphMany(EntityPermission::class, 'entity');
}
/**
{
return $this->permissions()
->where('role_id', '=', $role_id)
- ->where('action', '=', $action)
+ ->where($action, '=', true)
->count() > 0;
}
*/
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']);
$updatedBookCount = 0;
public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
{
$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()->createMany($permissions);
$targetEntity->rebuildPermissions();
namespace BookStack\Entities\Tools;
use BookStack\Actions\ActivityType;
+use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\User;
use BookStack\Entities\Models\Entity;
use BookStack\Facades\Activity;
*/
public function updateFromPermissionsForm(Entity $entity, Request $request)
{
- $restricted = $request->get('restricted') === 'true';
- $permissions = $request->get('restrictions', null);
+ $permissions = $request->get('permissions', null);
$ownerId = $request->get('owned_by', null);
- $entity->restricted = $restricted;
$entity->permissions()->delete();
if (!is_null($permissions)) {
}
/**
- * Format permissions provided from a permission form to be
- * EntityPermission data.
+ * Format permissions provided from a permission form to be EntityPermission data.
*/
- protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): Collection
+ protected function formatPermissionsFromRequestToEntityPermissions(array $permissions): array
{
- return collect($permissions)->flatMap(function ($restrictions, $roleId) {
- return collect($restrictions)->keys()->map(function ($action) use ($roleId) {
- return [
- 'role_id' => $roleId,
- 'action' => strtolower($action),
- ];
- });
- });
+ $formatted = [];
+
+ foreach ($permissions as $roleId => $info) {
+ $entityPermissionData = ['role_id' => $roleId];
+ foreach (EntityPermission::PERMISSIONS as $permission) {
+ $entityPermissionData[$permission] = (($info[$permission] ?? false) === "true");
+ }
+ $formatted[] = $entityPermissionData;
+ }
+
+ return $formatted;
}
}
</div>
@endif
<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">
- @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 class="px-l">
@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
</div>
<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 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>
\ No newline at end of file
namespace Tests\Helpers;
+use BookStack\Auth\Permissions\EntityPermission;
use BookStack\Auth\Role;
use BookStack\Auth\User;
use BookStack\Entities\Models\Book;
$entity->permissions()->delete();
$permissions = [];
- foreach ($actions as $action) {
- foreach ($roles as $role) {
- $permissions[] = [
- 'role_id' => $role->id,
- 'action' => strtolower($action),
- ];
+ foreach ($roles as $role) {
+ $permission = ['role_id' => $role->id];
+ foreach (EntityPermission::PERMISSIONS as $possibleAction) {
+ $permission[$possibleAction] = in_array($possibleAction, $actions);
}
+ $permissions[] = $permission;
}
$entity->permissions()->createMany($permissions);