Skip to content

Commit 0c3eb9f

Browse files
authored
[server] FGA checks for all admin*Workspace methods (#18569)
* [server] FGA checks for all admin*Workspace methods * rebase artifacts
1 parent 930fe77 commit 0c3eb9f

File tree

3 files changed

+40
-10
lines changed

3 files changed

+40
-10
lines changed

components/server/src/authorization/definitions.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,14 @@ export type WorkspaceResourceType = "workspace";
9292

9393
export type WorkspaceRelation = "org" | "owner" | "shared";
9494

95-
export type WorkspacePermission = "access" | "start" | "stop" | "delete" | "read_info" | "create_snapshot";
95+
export type WorkspacePermission =
96+
| "access"
97+
| "start"
98+
| "stop"
99+
| "delete"
100+
| "read_info"
101+
| "create_snapshot"
102+
| "admin_control";
96103

97104
export const rel = {
98105
user(id: string) {

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,7 +1988,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
19881988

19891989
// we use the workspacService which checks if the requesting user has access to the workspace. If that is the case they have access to snapshots as well.
19901990
// below is the old permission check which would also check if the user has access to the snapshot itself. This is not the case anymore.
1991-
const workspace = await this.workspaceService.getWorkspace(user.id, workspaceId);
1991+
const { workspace } = await this.workspaceService.getWorkspace(user.id, workspaceId);
19921992
if (workspace.ownerId !== user.id) {
19931993
throw new ApplicationError(ErrorCodes.NOT_FOUND, `Workspace ${workspaceId} does not exist.`);
19941994
}
@@ -2785,9 +2785,9 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
27852785
): Promise<AdminGetListResult<WorkspaceAndInstance>> {
27862786
traceAPIParams(ctx, { req });
27872787

2788-
await this.guardAdminAccess("adminGetWorkspaces", { req }, Permission.ADMIN_WORKSPACES);
2788+
const admin = await this.guardAdminAccess("adminGetWorkspaces", { req }, Permission.ADMIN_WORKSPACES);
27892789

2790-
return await this.workspaceDb
2790+
const wss = await this.workspaceDb
27912791
.trace(ctx)
27922792
.findAllWorkspaceAndInstances(
27932793
req.offset,
@@ -2796,12 +2796,27 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
27962796
req.orderDir === "asc" ? "ASC" : "DESC",
27972797
req,
27982798
);
2799+
2800+
await Promise.all(
2801+
wss.rows.map(async (row) => {
2802+
if (!(await this.auth.hasPermissionOnWorkspace(admin.id, "access", row.workspaceId))) {
2803+
wss.total--;
2804+
wss.rows = wss.rows.filter((ws) => ws.workspaceId !== row.workspaceId);
2805+
}
2806+
}),
2807+
);
2808+
return wss;
27992809
}
28002810

28012811
async adminGetWorkspace(ctx: TraceContext, workspaceId: string): Promise<WorkspaceAndInstance> {
28022812
traceAPIParams(ctx, { workspaceId });
28032813

2804-
await this.guardAdminAccess("adminGetWorkspace", { id: workspaceId }, Permission.ADMIN_WORKSPACES);
2814+
const admin = await this.guardAdminAccess(
2815+
"adminGetWorkspace",
2816+
{ id: workspaceId },
2817+
Permission.ADMIN_WORKSPACES,
2818+
);
2819+
await this.auth.checkPermissionOnWorkspace(admin.id, "access", workspaceId);
28052820

28062821
const result = await this.workspaceDb.trace(ctx).findWorkspaceAndInstance(workspaceId);
28072822
if (!result) {
@@ -2813,7 +2828,12 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28132828
async adminGetWorkspaceInstances(ctx: TraceContext, workspaceId: string): Promise<WorkspaceInstance[]> {
28142829
traceAPIParams(ctx, { workspaceId });
28152830

2816-
await this.guardAdminAccess("adminGetWorkspaceInstances", { id: workspaceId }, Permission.ADMIN_WORKSPACES);
2831+
const admin = await this.guardAdminAccess(
2832+
"adminGetWorkspaceInstances",
2833+
{ id: workspaceId },
2834+
Permission.ADMIN_WORKSPACES,
2835+
);
2836+
await this.auth.checkPermissionOnWorkspace(admin.id, "access", workspaceId);
28172837

28182838
const result = await this.workspaceDb.trace(ctx).findInstances(workspaceId);
28192839
return result || [];
@@ -2827,6 +2847,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28272847
{ id: workspaceId },
28282848
Permission.ADMIN_WORKSPACES,
28292849
);
2850+
await this.auth.checkPermissionOnWorkspace(admin.id, "admin_control", workspaceId);
28302851

28312852
const workspace = await this.workspaceDb.trace(ctx).findById(workspaceId);
28322853
if (workspace) {
@@ -2844,11 +2865,12 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28442865
async adminRestoreSoftDeletedWorkspace(ctx: TraceContext, workspaceId: string): Promise<void> {
28452866
traceAPIParams(ctx, { workspaceId });
28462867

2847-
await this.guardAdminAccess(
2868+
const admin = await this.guardAdminAccess(
28482869
"adminRestoreSoftDeletedWorkspace",
28492870
{ id: workspaceId },
28502871
Permission.ADMIN_WORKSPACES,
28512872
);
2873+
await this.auth.checkPermissionOnWorkspace(admin.id, "admin_control", workspaceId);
28522874

28532875
await this.workspaceDb.trace(ctx).transaction(async (db) => {
28542876
const ws = await db.findById(workspaceId);

components/spicedb/schema/schema.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ schema: |-
117117
relation shared: user:*
118118
119119
// Whether a user can access a workspace (with an IDE)
120-
//+ (hasAccessToRepository && isPrebuild)
121-
permission access = owner + shared
120+
permission access = owner + shared + org->installation_admin
122121
123122
// Note: All of this is modelled after current behavior.
124123
// There are a lot of improvements we can make here in the light of Organizations, but we explicitly do that as a separate step
@@ -127,10 +126,12 @@ schema: |-
127126
permission delete = owner
128127
129128
// Whether a user can read basic info/metadata of a workspace
130-
//+ (hasAccessToRepository && isPrebuild)
131129
permission read_info = owner + shared + org->member
132130
133131
permission create_snapshot = owner & org->snapshoter
132+
133+
// Whether someone is allowed to do administrative tasks on a workspace, e.g. un-delete etc.
134+
permission admin_control = org->installation_admin
134135
}
135136
# relationships to be used for assertions & validation
136137
relationships: |-

0 commit comments

Comments
 (0)