X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/afe1a042396454e071b4b3bb5bb0043586ba333a..refs/pull/3918/head:/app/Auth/Permissions/PermissionApplicator.php diff --git a/app/Auth/Permissions/PermissionApplicator.php b/app/Auth/Permissions/PermissionApplicator.php index 91a7c72ae..af372cb74 100644 --- a/app/Auth/Permissions/PermissionApplicator.php +++ b/app/Auth/Permissions/PermissionApplicator.php @@ -25,14 +25,22 @@ class PermissionApplicator { $explodedPermission = explode('-', $permission); $action = $explodedPermission[1] ?? $explodedPermission[0]; + $fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission; + $user = $this->currentUser(); $userRoleIds = $this->getCurrentUserRoleIds(); - $allRolePermission = $user->can($permission . '-all'); - $ownRolePermission = $user->can($permission . '-own'); + $allRolePermission = $user->can($fullPermission . '-all'); + $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 @@ -40,23 +48,26 @@ class PermissionApplicator return $hasRolePermission; } - $entityPermissions = $this->getApplicableEntityPermissions($ownable, $userRoleIds, $action); - if (is_null($entityPermissions)) { - return $hasRolePermission; - } + $hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action); - return count($entityPermissions) > 0; + return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions; } /** - * Get the permissions that are applicable for the given entity item. - * Returns null when no entity permissions apply otherwise entity permissions - * are active, even if the returned array is empty. - * - * @returns EntityPermission[] + * Check if there are permissions that are applicable for the given entity item, action and roles. + * Returns null when no entity permissions are in force. */ - protected function getApplicableEntityPermissions(Entity $entity, array $userRoleIds, string $action): ?array + 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; @@ -67,13 +78,26 @@ class PermissionApplicator } foreach ($chain as $currentEntity) { - if ($currentEntity->restricted) { - return $currentEntity->permissions() - ->whereIn('role_id', $userRoleIds) - ->where('action', '=', $action) - ->get() - ->all(); + $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; @@ -113,8 +135,6 @@ class PermissionApplicator return $query->where(function (Builder $parentQuery) { $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) { $permissionQuery->whereIn('role_id', $this->getCurrentUserRoleIds()) - // TODO - Delete line once only views - ->where('action', '=', 'view') ->where(function (Builder $query) { $this->addJointHasPermissionCheck($query, $this->currentUser()->id); }); @@ -154,7 +174,6 @@ class PermissionApplicator $permissionQuery->select(['role_id'])->from('joint_permissions') ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) ->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) - ->where('joint_permissions.action', '=', 'view') ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds()) ->where(function (QueryBuilder $query) { $this->addJointHasPermissionCheck($query, $this->currentUser()->id); @@ -189,7 +208,6 @@ class PermissionApplicator $permissionQuery->select('joint_permissions.role_id')->from('joint_permissions') ->whereColumn('joint_permissions.entity_id', '=', $fullPageIdColumn) ->where('joint_permissions.entity_type', '=', $morphClass) - ->where('joint_permissions.action', '=', 'view') ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoleIds()) ->where(function (QueryBuilder $query) { $this->addJointHasPermissionCheck($query, $this->currentUser()->id); @@ -249,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'); + } + } }