1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-27 15:57:04 +03:00

Reworked userCan permission check to follow defined logic.

Got all current scenario tests passing.
Also fixes own permission which was using the wrong field.
This commit is contained in:
Dan Brown 2022-12-23 21:07:49 +00:00
parent 451e4ac452
commit 026e9030b9
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9

View File

@ -66,88 +66,63 @@ class PermissionApplicator
return true; return true;
} }
// The chain order here is very important due to the fact we walk up the chain // The array 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. // in the flattening loop below. Earlier items in the chain have higher priority.
$chain = [$entity]; $typeIdList = [$entity->getMorphClass() . ':' . $entity->id];
if ($entity instanceof Page && $entity->chapter_id) { if ($entity instanceof Page && $entity->chapter_id) {
$chain[] = $entity->chapter; $typeIdList[] = 'chapter:' . $entity->chapter_id;
} }
if ($entity instanceof Page || $entity instanceof Chapter) { if ($entity instanceof Page || $entity instanceof Chapter) {
$chain[] = $entity->book; $typeIdList[] = 'book:' . $entity->book_id;
} }
// Record role access preventions. $relevantPermissions = EntityPermission::query()
// Used when we encounter a negative role permission where inheritance is active and therefore ->where(function (Builder $query) use ($typeIdList) {
// need to check permissive status on parent items. foreach ($typeIdList as $typeId) {
$blockedRoleIds = []; $query->orWhere(function (Builder $query) use ($typeId) {
[$type, $id] = explode(':', $typeId);
foreach ($chain as $currentEntity) { $query->where('entity_type', '=', $type)
$relevantPermissions = $currentEntity->permissions() ->where('entity_id', '=', $id);
->where(function (Builder $query) use ($userRoleIds, $userId) { });
$query->whereIn('role_id', $userRoleIds) }
})->where(function (Builder $query) use ($userRoleIds, $userId) {
$query->whereIn('role_id', $userRoleIds)
->orWhere('user_id', '=', $userId) ->orWhere('user_id', '=', $userId)
->orWhere(function (Builder $query) { ->orWhere(function (Builder $query) {
$query->whereNull(['role_id', 'user_id']); $query->whereNull(['role_id', 'user_id']);
}); });
}) })->get(['entity_id', 'entity_type', 'role_id', 'user_id', $action])
->get(['role_id', 'user_id', $action]) ->all();
->all();
// See dev/docs/permission-scenario-testing.md for technical details $permissionMap = new EntityPermissionMap($relevantPermissions);
// on how permissions should be enforced. $permitsByType = ['user' => [], 'fallback' => [], 'role' => []];
$allowedByTypeById = ['fallback' => [], 'user' => [], 'role' => []]; // Collapse and simplify permission structure
/** @var EntityPermission $permission */ foreach ($typeIdList as $typeId) {
foreach ($relevantPermissions as $permission) { $permissions = $permissionMap->getForEntity($typeId);
$allowedByTypeById[$permission->getAssignedType()][$permission->getAssignedTypeId()] = boolval($permission->$action); foreach ($permissions as $permission) {
} $related = $permission->getAssignedType();
$relatedId = $permission->getAssignedTypeId();
$inheriting = !isset($allowedByTypeById['fallback'][0]); if (!isset($permitsByType[$related][$relatedId])) {
$permitsByType[$related][$relatedId] = $permission->$action;
// Continue up the chain if no applicable entity permission overrides.
if (count($relevantPermissions) === 0) {
continue;
}
// If we have user-specific permissions set, return the status of that
// since it's the most specific possible.
if (isset($allowedByTypeById['user'][$userId])) {
return $allowedByTypeById['user'][$userId];
}
// If we have role-specific permissions set, allow if any of those
// role permissions allow access. We do not allow if the role has been previously
// blocked by a high-priority inheriting level.
// If we're inheriting at this level, and there's an explicit non-allow permission, we record
// it for checking up the chain.
foreach ($allowedByTypeById['role'] as $roleId => $allowed) {
if ($allowed && !in_array($roleId, $blockedRoleIds)) {
return true;
} else if (!$allowed) {
$blockedRoleIds[] = $roleId;
} }
} }
// If we had role permissions, and none of them allowed (via above loop), and
// we are not inheriting, exit here since we only have role permissions in play blocking access.
if (count($allowedByTypeById['role']) > 0 && !$inheriting) {
return false;
}
// Continue up the chain if inheriting
if ($inheriting) {
continue;
}
// Otherwise, return the default "Other roles" fallback value.
return $allowedByTypeById['fallback'][0];
} }
// If we have relevant roles conditions that are actively blocking // Return user-level permission if exists
// return false since these are more specific than potential role-level permissions. if (count($permitsByType['user']) > 0) {
if (count($blockedRoleIds) > 0) { return boolval(array_values($permitsByType['user'])[0]);
return false; }
// Return grant or reject from role-level if exists
if (count($permitsByType['role']) > 0) {
return boolval(max($permitsByType['role']));
}
// Return fallback permission if exists
if (count($permitsByType['fallback']) > 0) {
return boolval($permitsByType['fallback'][0]);
} }
return null; return null;
@ -241,7 +216,7 @@ class PermissionApplicator
} else if ($userViewOwn) { } else if ($userViewOwn) {
$query->orWhere(function (Builder $query) { $query->orWhere(function (Builder $query) {
$query->whereNull(['perms_user', 'perms_role', 'perms_fallback']) $query->whereNull(['perms_user', 'perms_role', 'perms_fallback'])
->where('created_by', '=', $this->currentUser()->id); ->where('owned_by', '=', $this->currentUser()->id);
}); });
} }
}); });