Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 1879110d78 fix: filter folder children by object read permissions in navigation sidebar
https://sonarly.com/issue/32509?type=bug

Navigation items nested inside folders in the sidebar bypass the `canReadObjectRecords` permission check, making restricted objects and views visible inside folders even when their role forbids reading them.

Fix: The fix addresses the missing `canReadObjectRecords` permission check for navigation items nested inside folders.

**What changed** in `WorkspaceSectionContainer.tsx`:

1. **Extracted `isObjectBackedItemAccessible` helper** — a reusable function that checks whether a navigation menu item should be visible based on object read permissions. It returns `true` for LINK and PAGE_LAYOUT items (always visible), checks `canReadObjectRecords` for OBJECT/VIEW/RECORD items, and returns `false` for unknown types. This eliminates duplication between root-level and folder-level filtering.

2. **Added `filteredFolderChildrenById`** — a new Map derived from `folderChildrenById` where each folder's children are filtered through `isObjectBackedItemAccessible`. This ensures folder children respect the same permission rules as root-level items.

3. **Updated folder visibility in `filteredItems`** — FOLDER items are now only included if they have at least one visible child in `filteredFolderChildrenById`. This hides empty folders that would otherwise appear when all their children are restricted.

4. **Read-only path uses filtered data** — The `WorkspaceSectionListReadOnly` component now receives `filteredFolderChildrenById` instead of `folderChildrenById`, so restricted users only see permitted items.

5. **Edit mode preserved** — The `LazyWorkspaceSectionListDndKit` and `WorkspaceSectionListEditModeFallback` components still receive the unfiltered `folderChildrenById`, so admins in layout customization mode can see and manage all items (with lock icons for restricted ones, as already implemented).
2026-04-29 14:50:57 +00:00
@@ -69,23 +69,10 @@ export const WorkspaceSectionContainer = ({
const isAddToNavigationDropTargetVisible =
addToNavigationFallbackDestination?.droppableId ===
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS;
const folderChildrenById = items.reduce<Map<string, NavigationMenuItem[]>>(
(acc, item) => {
const folderId = item.folderId;
if (isDefined(folderId)) {
const children = acc.get(folderId) ?? [];
children.push(item);
acc.set(folderId, children);
}
return acc;
},
new Map(),
);
const filteredItems = flatItems.filter((item) => {
const isObjectBackedItemAccessible = (item: NavigationMenuItem): boolean => {
const itemType = item.type;
if (
itemType === NavigationMenuItemType.FOLDER ||
itemType === NavigationMenuItemType.LINK ||
itemType === NavigationMenuItemType.PAGE_LAYOUT
) {
@@ -110,6 +97,48 @@ export const WorkspaceSectionContainer = ({
);
}
return false;
};
const folderChildrenById = items.reduce<Map<string, NavigationMenuItem[]>>(
(acc, item) => {
const folderId = item.folderId;
if (isDefined(folderId)) {
const children = acc.get(folderId) ?? [];
children.push(item);
acc.set(folderId, children);
}
return acc;
},
new Map(),
);
const filteredFolderChildrenById = new Map(
Array.from(folderChildrenById.entries()).map(([folderId, children]) => [
folderId,
children.filter(isObjectBackedItemAccessible),
]),
);
const filteredItems = flatItems.filter((item) => {
const itemType = item.type;
if (itemType === NavigationMenuItemType.FOLDER) {
const visibleChildren = filteredFolderChildrenById.get(item.id) ?? [];
return visibleChildren.length > 0;
}
if (
itemType === NavigationMenuItemType.LINK ||
itemType === NavigationMenuItemType.PAGE_LAYOUT
) {
return true;
}
if (
itemType === NavigationMenuItemType.OBJECT ||
itemType === NavigationMenuItemType.VIEW ||
itemType === NavigationMenuItemType.RECORD
) {
return isObjectBackedItemAccessible(item);
}
return false;
});
const workspaceOrphanItemsForSection = isLayoutCustomizationModeEnabled
@@ -181,7 +210,7 @@ export const WorkspaceSectionContainer = ({
) : (
<WorkspaceSectionListReadOnly
filteredItems={filteredItems}
folderChildrenById={folderChildrenById}
folderChildrenById={filteredFolderChildrenById}
onActiveObjectMetadataItemClick={onActiveObjectMetadataItemClick}
/>
)}