X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/ee24635e06a8c01d751f80caba47c57f76e8989d..refs/pull/3918/head:/app/Auth/Permissions/PermissionApplicator.php diff --git a/app/Auth/Permissions/PermissionApplicator.php b/app/Auth/Permissions/PermissionApplicator.php index d855a6170..af372cb74 100644 --- a/app/Auth/Permissions/PermissionApplicator.php +++ b/app/Auth/Permissions/PermissionApplicator.php @@ -34,7 +34,13 @@ class PermissionApplicator $ownRolePermission = $user->can($fullPermission . '-own'); $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment']; $ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by'; - $isOwner = $user->id === $ownable->getAttribute($ownerField); + $ownableFieldVal = $ownable->getAttribute($ownerField); + + if (is_null($ownableFieldVal)) { + throw new InvalidArgumentException("{$ownerField} field used but has not been loaded"); + } + + $isOwner = $user->id === $ownableFieldVal; $hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission); // Handle non entity specific jointPermissions @@ -53,11 +59,15 @@ class PermissionApplicator */ 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; } + // The chain order here is very important due to the fact we walk up the chain + // in the loop below. Earlier items in the chain have higher priority. $chain = [$entity]; if ($entity instanceof Page && $entity->chapter_id) { $chain[] = $entity->chapter; @@ -68,12 +78,26 @@ class PermissionApplicator } foreach ($chain as $currentEntity) { - if ($currentEntity->restricted) { - return $currentEntity->permissions() - ->whereIn('role_id', $userRoleIds) - ->where('action', '=', $action) - ->count() > 0; + $allowedByRoleId = $currentEntity->permissions() + ->whereIn('role_id', [0, ...$userRoleIds]) + ->pluck($action, 'role_id'); + + // Continue up the chain if no applicable entity permission overrides. + if ($allowedByRoleId->isEmpty()) { + continue; } + + // If we have user-role-specific permissions set, allow if any of those + // role permissions allow access. + $hasDefault = $allowedByRoleId->has(0); + if (!$hasDefault || $allowedByRoleId->count() > 1) { + return $allowedByRoleId->search(function (bool $allowed, int $roleId) { + return $roleId !== 0 && $allowed; + }) !== false; + } + + // Otherwise, return the default "Other roles" fallback value. + return $allowedByRoleId->get(0); } return null; @@ -85,18 +109,16 @@ class PermissionApplicator */ 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; @@ -245,4 +267,16 @@ class PermissionApplicator 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'); + } + } }