mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 08:04:05 +00:00
fix(sidebar): reject stale folder owners
This commit is contained in:
@@ -302,6 +302,31 @@ describe('filesystem-auth path containment', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a foreign-stamped folder whose group exists only locally', async () => {
|
||||
const tempRoot = await mkdtemp(join(tmpdir(), 'orca-auth-stale-folder-owner-'))
|
||||
try {
|
||||
const localPath = join(tempRoot, 'local')
|
||||
const foreignPath = join(tempRoot, 'foreign')
|
||||
await mkdir(localPath)
|
||||
await mkdir(foreignPath)
|
||||
const store = makeStore([], {
|
||||
projectGroups: [makeProjectGroup({ id: 'same-id', parentPath: localPath })],
|
||||
folderWorkspaces: [
|
||||
makeFolderWorkspace({
|
||||
id: 'foreign-folder',
|
||||
projectGroupId: 'same-id',
|
||||
folderPath: foreignPath,
|
||||
connectionId: 'missing'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
await expect(resolveAuthorizedPath(foreignPath, store)).rejects.toThrow('Access denied')
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps large multi-owner folder authorization linear in catalog size', () => {
|
||||
const groupCount = 2_048
|
||||
const repoCount = 2_048
|
||||
|
||||
@@ -35,6 +35,13 @@ describe('project-group sidebar identity', () => {
|
||||
expect(getProjectGroupSidebarIdentity(local)).not.toBe(getProjectGroupSidebarIdentity(runtime))
|
||||
})
|
||||
|
||||
it('does not fall back to a foreign group for an explicit stale owner', () => {
|
||||
const local = group('same-id', 'local')
|
||||
const index = buildProjectGroupSidebarIndex([local])
|
||||
|
||||
expect(findProjectGroupForSidebarOwner(index, 'same-id', 'runtime:missing')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('disables focused-host mutations for mixed-owner and mismatched lists', () => {
|
||||
const localA = group('a', 'local')
|
||||
const localB = group('b', 'local')
|
||||
|
||||
@@ -103,10 +103,7 @@ export function findProjectGroupForSidebarOwner(
|
||||
return undefined
|
||||
}
|
||||
if (ownerHostId) {
|
||||
const exact = index.byIdentity.get(JSON.stringify([ownerHostId, groupId]))
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
return index.byIdentity.get(JSON.stringify([ownerHostId, groupId]))
|
||||
}
|
||||
return index.byUnambiguousId.get(groupId)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
import {
|
||||
buildProjectGroupOwnerIndex,
|
||||
getFolderWorkspaceProjectGroupOwnerHostId,
|
||||
resolveFolderWorkspaceProjectGroup,
|
||||
resolveProjectGroupOwner
|
||||
resolveFolderWorkspaceProjectGroup
|
||||
} from './project-groups'
|
||||
import type { FolderWorkspace, ProjectGroup } from './types'
|
||||
import { isTuiAgent } from './tui-agent-config'
|
||||
@@ -107,12 +106,11 @@ export function normalizeFolderWorkspaces(
|
||||
continue
|
||||
}
|
||||
const executionHostId = normalizeExecutionHostId(raw.executionHostId)
|
||||
const group =
|
||||
resolveFolderWorkspaceProjectGroup(projectGroupIndex, {
|
||||
projectGroupId: raw.projectGroupId,
|
||||
connectionId: raw.connectionId,
|
||||
executionHostId
|
||||
}) ?? resolveProjectGroupOwner(projectGroupIndex, raw.projectGroupId)
|
||||
const group = resolveFolderWorkspaceProjectGroup(projectGroupIndex, {
|
||||
projectGroupId: raw.projectGroupId,
|
||||
connectionId: raw.connectionId,
|
||||
executionHostId
|
||||
})
|
||||
if (!group?.parentPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
resolveFolderWorkspaceProjectGroup,
|
||||
resolveProjectGroupOwner
|
||||
} from './project-groups'
|
||||
import { normalizeFolderWorkspaces } from './folder-workspaces'
|
||||
import type { ProjectGroup, Repo } from './types'
|
||||
|
||||
function repo(overrides: Partial<Repo>): Repo {
|
||||
@@ -162,6 +163,28 @@ describe('project-groups', () => {
|
||||
expect(resolveFolderWorkspaceProjectGroup(index, { projectGroupId: 'same-id' })).toBeNull()
|
||||
})
|
||||
|
||||
it('rejects folder workspaces stamped for missing foreign owners', () => {
|
||||
const local = projectGroup({ id: 'same-id', name: 'Local', parentPath: '/local' })
|
||||
|
||||
expect(
|
||||
normalizeFolderWorkspaces(
|
||||
[
|
||||
{
|
||||
id: 'runtime-folder',
|
||||
projectGroupId: 'same-id',
|
||||
executionHostId: 'runtime:missing'
|
||||
},
|
||||
{
|
||||
id: 'ssh-folder',
|
||||
projectGroupId: 'same-id',
|
||||
connectionId: 'missing'
|
||||
}
|
||||
],
|
||||
[local]
|
||||
)
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('collects descendants only from the selected owner hierarchy', () => {
|
||||
const localRoot = projectGroup({ id: 'root', name: 'Local root' })
|
||||
const localChild = projectGroup({ id: 'child', parentGroupId: 'root' })
|
||||
|
||||
@@ -17,11 +17,10 @@ export type ProjectGroupOwnerIndex = {
|
||||
export function getProjectGroupOwnerHostId(
|
||||
group: Pick<ProjectGroup, 'connectionId' | 'executionHostId'>
|
||||
): ExecutionHostId {
|
||||
const executionHostId = normalizeExecutionHostId(group.executionHostId)
|
||||
if (executionHostId) {
|
||||
return executionHostId
|
||||
}
|
||||
return group.connectionId ? toSshExecutionHostId(group.connectionId) : LOCAL_EXECUTION_HOST_ID
|
||||
return (
|
||||
normalizeExecutionHostId(group.executionHostId) ??
|
||||
(group.connectionId ? toSshExecutionHostId(group.connectionId) : LOCAL_EXECUTION_HOST_ID)
|
||||
)
|
||||
}
|
||||
|
||||
export function getProjectGroupOwnerIdentity(
|
||||
@@ -99,7 +98,10 @@ export function resolveFolderWorkspaceProjectGroup(
|
||||
? toSshExecutionHostId(workspace.connectionId)
|
||||
: LOCAL_EXECUTION_HOST_ID
|
||||
const group = resolveProjectGroupMembership(index, workspace.projectGroupId, ownerHostId)
|
||||
return group ?? resolveProjectGroupOwner(index, workspace.projectGroupId)
|
||||
// Why: null predates owner stamps; a named SSH owner is authoritative.
|
||||
return workspace.connectionId === null
|
||||
? (group ?? resolveProjectGroupOwner(index, workspace.projectGroupId))
|
||||
: group
|
||||
}
|
||||
return resolveProjectGroupOwner(index, workspace.projectGroupId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user