Skip to content

Commit 5daf36e

Browse files
authored
[fga] WorkspaceService.controlAdmission (#18549)
* [server] Move controlAdmission into WorkspaceService + tests * [server] Implement Workspace sharing in FGA * rebase adjustments
1 parent 6499416 commit 5daf36e

File tree

6 files changed

+316
-56
lines changed

6 files changed

+316
-56
lines changed

components/server/src/authorization/authorizer.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,14 +386,19 @@ export class Authorizer {
386386
);
387387
}
388388

389-
async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
389+
async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string, shared: boolean): Promise<void> {
390390
if (await this.isDisabled(userID)) {
391391
return;
392392
}
393-
await this.authorizer.writeRelationships(
393+
const rels = [
394394
set(rel.workspace(workspaceID).org.organization(orgID)),
395395
set(rel.workspace(workspaceID).owner.user(userID)),
396-
);
396+
];
397+
if (shared) {
398+
rels.push(set(rel.workspace(workspaceID).shared.anyUser));
399+
}
400+
401+
await this.authorizer.writeRelationships(...rels);
397402
}
398403

399404
async removeWorkspaceFromOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
@@ -403,9 +408,18 @@ export class Authorizer {
403408
await this.authorizer.writeRelationships(
404409
remove(rel.workspace(workspaceID).org.organization(orgID)),
405410
remove(rel.workspace(workspaceID).owner.user(userID)),
411+
remove(rel.workspace(workspaceID).shared.anyUser),
406412
);
407413
}
408414

415+
async setWorkspaceIsShared(userID: string, workspaceID: string, shared: boolean): Promise<void> {
416+
if (await this.isDisabled(userID)) {
417+
return;
418+
}
419+
const op = shared ? set : remove;
420+
await this.authorizer.writeRelationships(op(rel.workspace(workspaceID).shared.anyUser));
421+
}
422+
409423
async bulkCreateWorkspaceInOrg(ids: { orgID: string; userID: string; workspaceID: string }[]): Promise<void> {
410424
const rels = ids
411425
.map(({ orgID, userID, workspaceID }) => [

components/server/src/authorization/definitions.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export type ProjectPermission =
8989

9090
export type WorkspaceResourceType = "workspace";
9191

92-
export type WorkspaceRelation = "org" | "owner";
92+
export type WorkspaceRelation = "org" | "owner" | "shared";
9393

9494
export type WorkspacePermission = "access" | "start" | "stop" | "delete" | "read_info";
9595

@@ -424,6 +424,26 @@ export const rel = {
424424
},
425425
};
426426
},
427+
428+
get shared() {
429+
const result2 = {
430+
...result,
431+
relation: "shared",
432+
};
433+
return {
434+
get anyUser() {
435+
return {
436+
...result2,
437+
subject: {
438+
object: {
439+
objectType: "user",
440+
objectId: "*",
441+
},
442+
},
443+
} as v1.Relationship;
444+
},
445+
};
446+
},
427447
};
428448
},
429449
};

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,7 @@ import {
9393
} from "@gitpod/gitpod-protocol/lib/analytics";
9494
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";
9595
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
96-
import {
97-
AdmissionLevel,
98-
ControlAdmissionRequest,
99-
StopWorkspacePolicy,
100-
TakeSnapshotRequest,
101-
} from "@gitpod/ws-manager/lib/core_pb";
96+
import { StopWorkspacePolicy, TakeSnapshotRequest } from "@gitpod/ws-manager/lib/core_pb";
10297
import { inject, injectable } from "inversify";
10398
import { URL } from "url";
10499
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
@@ -3297,41 +3292,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
32973292

32983293
const user = await this.checkAndBlockUser("controlAdmission");
32993294

3300-
const lvlmap = new Map<string, AdmissionLevel>();
3301-
lvlmap.set("owner", AdmissionLevel.ADMIT_OWNER_ONLY);
3302-
lvlmap.set("everyone", AdmissionLevel.ADMIT_EVERYONE);
3303-
if (!lvlmap.has(level)) {
3304-
throw new ApplicationError(ErrorCodes.NOT_FOUND, "Invalid admission level.");
3305-
}
3306-
3307-
const workspace = await this.workspaceService.getWorkspace(user.id, workspaceId);
3308-
await this.guardAccess({ kind: "workspace", subject: workspace }, "update");
3309-
3310-
if (level != "owner" && workspace.organizationId) {
3311-
const settings = await this.organizationService.getSettings(user.id, workspace.organizationId);
3312-
if (settings?.workspaceSharingDisabled) {
3313-
throw new ApplicationError(
3314-
ErrorCodes.PERMISSION_DENIED,
3315-
"An Organization Owner has disabled workspace sharing for workspaces in this Organization. ",
3295+
await this.workspaceService.controlAdmission(user.id, workspaceId, level, (workspace, instance) => {
3296+
if (instance) {
3297+
return this.guardAccess(
3298+
{ kind: "workspaceInstance", subject: instance, workspace: workspace },
3299+
"update",
33163300
);
3301+
} else {
3302+
return this.guardAccess({ kind: "workspace", subject: workspace }, "update");
33173303
}
3318-
}
3319-
3320-
const instance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId);
3321-
if (instance) {
3322-
await this.guardAccess({ kind: "workspaceInstance", subject: instance, workspace: workspace }, "update");
3323-
3324-
const req = new ControlAdmissionRequest();
3325-
req.setId(instance.id);
3326-
req.setLevel(lvlmap.get(level)!);
3327-
3328-
const client = await this.workspaceManagerClientProvider.get(instance.region);
3329-
await client.controlAdmission(ctx, req);
3330-
}
3331-
3332-
await this.workspaceDb.trace(ctx).transaction(async (db) => {
3333-
workspace.shareable = level === "everyone";
3334-
await db.store(workspace);
33353304
});
33363305
}
33373306

0 commit comments

Comments
 (0)