From f6538d2f85abaa6e61478945f8132e8d32fc4a49 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 27 Aug 2026 13:54:45 +0200 Subject: [PATCH] feat: feat: enforce team membership validation for cloudtty access --- src/api-v2.authz.test.ts | 27 ++++++++++++++++++++++++++- src/openapi/cloudtty.yaml | 8 ++++---- src/otomi-stack.ts | 6 ++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/api-v2.authz.test.ts b/src/api-v2.authz.test.ts index 08e625b35..826b6f95f 100644 --- a/src/api-v2.authz.test.ts +++ b/src/api-v2.authz.test.ts @@ -1830,7 +1830,6 @@ describe('API V2 authz tests', () => { .expect(200) }) - //TODO check if this is the desired behavior test('team member cannot connect cloudtty for other team', async () => { await agent .get('/v2/cloudtty') @@ -1840,6 +1839,32 @@ describe('API V2 authz tests', () => { }) }) + describe('Team Admin', () => { + test('team admin can connect cloudtty for own team', async () => { + await agent + .get('/v2/cloudtty') + .query({ teamId: 'team1' }) + .set('Authorization', `Bearer ${teamAdminToken}`) + .expect(200) + }) + + test('team admin cannot connect cloudtty for other team', async () => { + await agent + .get('/v2/cloudtty') + .query({ teamId: 'team2' }) + .set('Authorization', `Bearer ${teamAdminToken}`) + .expect(403) + }) + + test('team admin cannot delete cloudtty for other team', async () => { + await agent + .delete('/v2/cloudtty') + .query({ teamId: 'team2' }) + .set('Authorization', `Bearer ${teamAdminToken}`) + .expect(403) + }) + }) + describe('Unauthenticated', () => { test('anonymous user cannot connect cloudtty', async () => { await agent.get('/v2/cloudtty').query({ teamId: 'team1' }).expect(401) diff --git a/src/openapi/cloudtty.yaml b/src/openapi/cloudtty.yaml index 817c3131b..b4b124d3f 100644 --- a/src/openapi/cloudtty.yaml +++ b/src/openapi/cloudtty.yaml @@ -6,10 +6,10 @@ Cloudtty: - update-any - delete-any teamAdmin: - - create-any - - read-any - - update-any - - delete-any + - create + - read + - update + - delete teamMember: - create - read diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 6f6362b79..e540dbb87 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1775,6 +1775,9 @@ export default class OtomiStack { async connectCloudtty(teamId: string, sessionUser: SessionUser): Promise { const isAdmin = sessionUser.isPlatformAdmin + if (!isAdmin && !sessionUser.teams.includes(teamId)) { + throw new ForbiddenError('Cannot open a cloud shell in a team you are not a member of.') + } const targetNamespace = isAdmin ? 'team-admin' : `team-${teamId}` if (!sessionUser.sub) { debug('No user sub found, cannot connect to shell.') @@ -1820,6 +1823,9 @@ export default class OtomiStack { } async deleteCloudtty(teamId: string, sessionUser: SessionUser): Promise { + if (!sessionUser.isPlatformAdmin && !sessionUser.teams.includes(teamId)) { + throw new ForbiddenError('Cannot delete a cloud shell in a team you are not a member of.') + } await this.getCloudTty().deleteTty(teamId, sessionUser) }