Skip to content

[fga] WorkspaceService.controlAdmission #18549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions components/server/src/authorization/authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,19 @@ export class Authorizer {
);
}

async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string, shared: boolean): Promise<void> {
if (await this.isDisabled(userID)) {
return;
}
await this.authorizer.writeRelationships(
const rels = [
set(rel.workspace(workspaceID).org.organization(orgID)),
set(rel.workspace(workspaceID).owner.user(userID)),
);
];
if (shared) {
rels.push(set(rel.workspace(workspaceID).shared.anyUser));
}

await this.authorizer.writeRelationships(...rels);
}

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

async setWorkspaceIsShared(userID: string, workspaceID: string, shared: boolean): Promise<void> {
if (await this.isDisabled(userID)) {
return;
}
const op = shared ? set : remove;
await this.authorizer.writeRelationships(op(rel.workspace(workspaceID).shared.anyUser));
}

async bulkCreateWorkspaceInOrg(ids: { orgID: string; userID: string; workspaceID: string }[]): Promise<void> {
const rels = ids
.map(({ orgID, userID, workspaceID }) => [
Expand Down
22 changes: 21 additions & 1 deletion components/server/src/authorization/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export type ProjectPermission =

export type WorkspaceResourceType = "workspace";

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

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

Expand Down Expand Up @@ -424,6 +424,26 @@ export const rel = {
},
};
},

get shared() {
const result2 = {
...result,
relation: "shared",
};
return {
get anyUser() {
return {
...result2,
subject: {
object: {
objectType: "user",
objectId: "*",
},
},
} as v1.Relationship;
},
};
},
};
},
};
47 changes: 8 additions & 39 deletions components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,7 @@ import {
} from "@gitpod/gitpod-protocol/lib/analytics";
import { SupportedWorkspaceClass } from "@gitpod/gitpod-protocol/lib/workspace-class";
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
import {
AdmissionLevel,
ControlAdmissionRequest,
StopWorkspacePolicy,
TakeSnapshotRequest,
} from "@gitpod/ws-manager/lib/core_pb";
import { StopWorkspacePolicy, TakeSnapshotRequest } from "@gitpod/ws-manager/lib/core_pb";
import { inject, injectable } from "inversify";
import { URL } from "url";
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
Expand Down Expand Up @@ -3297,41 +3292,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {

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

const lvlmap = new Map<string, AdmissionLevel>();
lvlmap.set("owner", AdmissionLevel.ADMIT_OWNER_ONLY);
lvlmap.set("everyone", AdmissionLevel.ADMIT_EVERYONE);
if (!lvlmap.has(level)) {
throw new ApplicationError(ErrorCodes.NOT_FOUND, "Invalid admission level.");
}

const workspace = await this.workspaceService.getWorkspace(user.id, workspaceId);
await this.guardAccess({ kind: "workspace", subject: workspace }, "update");

if (level != "owner" && workspace.organizationId) {
const settings = await this.organizationService.getSettings(user.id, workspace.organizationId);
if (settings?.workspaceSharingDisabled) {
throw new ApplicationError(
ErrorCodes.PERMISSION_DENIED,
"An Organization Owner has disabled workspace sharing for workspaces in this Organization. ",
await this.workspaceService.controlAdmission(user.id, workspaceId, level, (workspace, instance) => {
if (instance) {
return this.guardAccess(
{ kind: "workspaceInstance", subject: instance, workspace: workspace },
"update",
);
} else {
return this.guardAccess({ kind: "workspace", subject: workspace }, "update");
}
}

const instance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId);
if (instance) {
await this.guardAccess({ kind: "workspaceInstance", subject: instance, workspace: workspace }, "update");

const req = new ControlAdmissionRequest();
req.setId(instance.id);
req.setLevel(lvlmap.get(level)!);

const client = await this.workspaceManagerClientProvider.get(instance.region);
await client.controlAdmission(ctx, req);
}

await this.workspaceDb.trace(ctx).transaction(async (db) => {
workspace.shareable = level === "everyone";
await db.store(workspace);
});
}

Expand Down
Loading