diff --git a/src/backend/services/permission/PermissionService.defaultGrants.test.ts b/src/backend/services/permission/PermissionService.defaultGrants.test.ts index 8947f60dd..838eaffeb 100644 --- a/src/backend/services/permission/PermissionService.defaultGrants.test.ts +++ b/src/backend/services/permission/PermissionService.defaultGrants.test.ts @@ -195,7 +195,13 @@ describe('check on default grants', () => { it('answers a default grant without reading the scan cache', async () => { const getScanCache = vi.spyOn(server.stores.permission, 'getScanCache'); try { - for (const actor of [actors.user, actors.looseUser, actors.app]) { + // A full-access token holds its issuer's defaults. + for (const actor of [ + actors.user, + actors.looseUser, + actors.app, + actors.fullToken, + ]) { expect( await permService.check( actor, @@ -211,10 +217,7 @@ describe('check on default grants', () => { actors.scopedToken, 'service:puter-chat-completion:ii:puter-chat-completion', ], - [ - actors.fullToken, - 'service:puter-chat-completion:ii:puter-chat-completion', - ], + [actors.fullToken, `fs:${fileUid}:read`], [actors.app, 'feature:something'], [actors.user, `fs:${fileUid}:read`], ] as const) { diff --git a/src/backend/services/permission/PermissionService.test.ts b/src/backend/services/permission/PermissionService.test.ts index 40239d1e5..dbf9d30cc 100644 --- a/src/backend/services/permission/PermissionService.test.ts +++ b/src/backend/services/permission/PermissionService.test.ts @@ -2113,6 +2113,31 @@ describe('PermissionService — scan paths', () => { ).toBe(false); }); + it('answers default grants for full-access tokens without reading the scan cache', async () => { + const { actor } = await makeGroupedUser(); + const token: Actor = { + user: actor.user, + accessToken: { + uid: `tok-${uuidv4()}`, + issuer: actor, + authorized: null, + fullAccess: true, + }, + }; + const get = vi.spyOn(server.clients.redis, 'get'); + try { + expect( + await permService.check( + token, + 'service:puter-kvstore:ii:puter-kvstore', + ), + ).toBe(true); + expect(get).not.toHaveBeenCalled(); + } finally { + get.mockRestore(); + } + }); + it('a full-access token resolves anything its issuer holds, and nothing more', async () => { const { row, actor } = await makeGroupedUser(); const held = `zztest:full-${uuidv4()}:ii:read`; diff --git a/src/backend/services/permission/PermissionService.ts b/src/backend/services/permission/PermissionService.ts index 80f35d252..cb8a6cc6a 100644 --- a/src/backend/services/permission/PermissionService.ts +++ b/src/backend/services/permission/PermissionService.ts @@ -239,7 +239,10 @@ export class PermissionService extends PuterService { permissionOptions: string | string[], scanOptions?: ScanOptions, ): Promise { - if (this.#heldByDefault(actor, permissionOptions)) return true; + const defaultActor = actor.accessToken?.fullAccess + ? actor.accessToken.issuer + : actor; + if (this.#heldByDefault(defaultActor, permissionOptions)) return true; const reading = await this.scan( actor, permissionOptions, @@ -626,8 +629,11 @@ export class PermissionService extends PuterService { // Account-management endpoints stay closed because they gate on actor // type (requireUserActor / session cookie), not permissions. if (actor.accessToken.fullAccess) { - for (const permission of options) { - const issuerReading = await this.scan(issuerActor, permission); + const issuerReadings = await Promise.all( + options.map((permission) => this.scan(issuerActor, permission)), + ); + for (const [index, permission] of options.entries()) { + const issuerReading = issuerReadings[index]; reading.push({ $: 'path', via: 'access-token',