Skip to content

Commit 75c3415

Browse files
committed
[fga] Support createWorkspace + getWorkspace
1 parent 10e2468 commit 75c3415

File tree

9 files changed

+349
-59
lines changed

9 files changed

+349
-59
lines changed

components/server/src/authorization/authorizer.ts

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Relation,
1717
ResourceType,
1818
UserPermission,
19+
WorkspacePermission,
1920
rel,
2021
} from "./definitions";
2122
import { SpiceDBAuthorizer } from "./spicedb-authorizer";
@@ -101,11 +102,11 @@ export class Authorizer {
101102
);
102103
}
103104

104-
async hasPermissionOnUser(userId: string, permission: UserPermission, userResourceId: string): Promise<boolean> {
105+
async hasPermissionOnUser(userId: string, permission: UserPermission, resourceUserId: string): Promise<boolean> {
105106
const req = v1.CheckPermissionRequest.create({
106107
subject: subject("user", userId),
107108
permission,
108-
resource: object("user", userResourceId),
109+
resource: object("user", resourceUserId),
109110
consistency,
110111
});
111112

@@ -126,6 +127,35 @@ export class Authorizer {
126127
);
127128
}
128129

130+
async hasPermissionOnWorkspace(
131+
userId: string,
132+
permission: WorkspacePermission,
133+
workspaceId: string,
134+
): Promise<boolean> {
135+
const req = v1.CheckPermissionRequest.create({
136+
subject: subject("user", userId),
137+
permission,
138+
resource: object("workspace", workspaceId),
139+
consistency,
140+
});
141+
142+
return this.authorizer.check(req, { userId });
143+
}
144+
145+
async checkPermissionOnWorkspace(userId: string, permission: WorkspacePermission, workspaceId: string) {
146+
if (await this.hasPermissionOnWorkspace(userId, permission, workspaceId)) {
147+
return;
148+
}
149+
if ("read_info" === permission || !(await this.hasPermissionOnWorkspace(userId, "read_info", workspaceId))) {
150+
throw new ApplicationError(ErrorCodes.NOT_FOUND, `Workspace ${workspaceId} not found.`);
151+
}
152+
153+
throw new ApplicationError(
154+
ErrorCodes.PERMISSION_DENIED,
155+
`You do not have ${permission} on workspace ${workspaceId}`,
156+
);
157+
}
158+
129159
// write operations below
130160

131161
public async removeAllRelationships(type: ResourceType, id: string) {
@@ -158,7 +188,7 @@ export class Authorizer {
158188

159189
async addUser(userId: string, owningOrgId?: string) {
160190
await this.authorizer.writeRelationships(
161-
set(rel.user(userId).self.user(userId)), //
191+
set(rel.user(userId).self.user(userId)),
162192
set(
163193
owningOrgId
164194
? rel.user(userId).organization.organization(owningOrgId)
@@ -186,15 +216,11 @@ export class Authorizer {
186216
}
187217

188218
async addProjectToOrg(orgID: string, projectID: string): Promise<void> {
189-
await this.authorizer.writeRelationships(
190-
set(rel.project(projectID).org.organization(orgID)), //
191-
);
219+
await this.authorizer.writeRelationships(set(rel.project(projectID).org.organization(orgID)));
192220
}
193221

194222
async removeProjectFromOrg(orgID: string, projectID: string): Promise<void> {
195-
await this.authorizer.writeRelationships(
196-
remove(rel.project(projectID).org.organization(orgID)), //
197-
);
223+
await this.authorizer.writeRelationships(remove(rel.project(projectID).org.organization(orgID)));
198224
}
199225

200226
async addOrganization(org: Organization, members: TeamMemberInfo[], projects: Project[]): Promise<void> {
@@ -206,35 +232,40 @@ export class Authorizer {
206232
await this.addProjectToOrg(org.id, project.id);
207233
}
208234

209-
await this.authorizer.writeRelationships(
210-
set(rel.organization(org.id).installation.installation), //
211-
);
235+
await this.authorizer.writeRelationships(set(rel.organization(org.id).installation.installation));
212236
}
213237

214238
async addInstallationMemberRole(userID: string) {
215-
await this.authorizer.writeRelationships(
216-
set(rel.installation.member.user(userID)), //
217-
);
239+
await this.authorizer.writeRelationships(set(rel.installation.member.user(userID)));
218240
}
219241

220242
async removeInstallationMemberRole(userID: string) {
221-
await this.authorizer.writeRelationships(
222-
remove(rel.installation.member.user(userID)), //
223-
);
243+
await this.authorizer.writeRelationships(remove(rel.installation.member.user(userID)));
224244
}
225245

226246
async addInstallationAdminRole(userID: string) {
227-
await this.authorizer.writeRelationships(
228-
set(rel.installation.admin.user(userID)), //
229-
);
247+
await this.authorizer.writeRelationships(set(rel.installation.admin.user(userID)));
230248
}
231249

232250
async removeInstallationAdminRole(userID: string) {
251+
await this.authorizer.writeRelationships(remove(rel.installation.admin.user(userID)));
252+
}
253+
254+
async createWorkspaceInOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
233255
await this.authorizer.writeRelationships(
234-
remove(rel.installation.admin.user(userID)), //
256+
set(rel.workspace(workspaceID).org.organization(orgID)),
257+
set(rel.workspace(workspaceID).owner.user(userID)),
235258
);
236259
}
237260

261+
async addWorkspaceToOrg(orgID: string, workspaceID: string): Promise<void> {
262+
await this.authorizer.writeRelationships(set(rel.workspace(workspaceID).org.organization(orgID)));
263+
}
264+
265+
async removeWorkspaceFromOrg(orgID: string, workspaceID: string): Promise<void> {
266+
await this.authorizer.writeRelationships(remove(rel.workspace(workspaceID).org.organization(orgID)));
267+
}
268+
238269
public async find(relation: v1.Relationship): Promise<v1.Relationship | undefined> {
239270
const relationships = await this.authorizer.readRelationships({
240271
consistency: v1.Consistency.create({

components/server/src/authorization/definitions.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,23 @@ import { v1 } from "@authzed/authzed-node";
1010

1111
const InstallationID = "1";
1212

13-
export type ResourceType = UserResourceType | InstallationResourceType | OrganizationResourceType | ProjectResourceType;
13+
export type ResourceType =
14+
| UserResourceType
15+
| InstallationResourceType
16+
| OrganizationResourceType
17+
| ProjectResourceType
18+
| WorkspaceResourceType;
1419

15-
export type Relation = UserRelation | InstallationRelation | OrganizationRelation | ProjectRelation;
20+
export const AllResourceTypes: ResourceType[] = ["user", "installation", "organization", "project", "workspace"];
1621

17-
export type Permission = UserPermission | InstallationPermission | OrganizationPermission | ProjectPermission;
22+
export type Relation = UserRelation | InstallationRelation | OrganizationRelation | ProjectRelation | WorkspaceRelation;
23+
24+
export type Permission =
25+
| UserPermission
26+
| InstallationPermission
27+
| OrganizationPermission
28+
| ProjectPermission
29+
| WorkspacePermission;
1830

1931
export type UserResourceType = "user";
2032

@@ -48,6 +60,7 @@ export type OrganizationPermission =
4860
| "write_git_provider"
4961
| "read_billing"
5062
| "write_billing"
63+
| "create_workspace"
5164
| "write_billing_admin";
5265

5366
export type ProjectResourceType = "project";
@@ -56,6 +69,12 @@ export type ProjectRelation = "org" | "editor" | "viewer";
5669

5770
export type ProjectPermission = "read_info" | "write_info" | "delete";
5871

72+
export type WorkspaceResourceType = "workspace";
73+
74+
export type WorkspaceRelation = "org" | "owner";
75+
76+
export type WorkspacePermission = "access" | "read_info";
77+
5978
export const rel = {
6079
user(id: string) {
6180
const result: Partial<v1.Relationship> = {
@@ -327,4 +346,54 @@ export const rel = {
327346
},
328347
};
329348
},
349+
350+
workspace(id: string) {
351+
const result: Partial<v1.Relationship> = {
352+
resource: {
353+
objectType: "workspace",
354+
objectId: id,
355+
},
356+
};
357+
return {
358+
get org() {
359+
const result2 = {
360+
...result,
361+
relation: "org",
362+
};
363+
return {
364+
organization(objectId: string) {
365+
return {
366+
...result2,
367+
subject: {
368+
object: {
369+
objectType: "organization",
370+
objectId: objectId,
371+
},
372+
},
373+
} as v1.Relationship;
374+
},
375+
};
376+
},
377+
378+
get owner() {
379+
const result2 = {
380+
...result,
381+
relation: "owner",
382+
};
383+
return {
384+
user(objectId: string) {
385+
return {
386+
...result2,
387+
subject: {
388+
object: {
389+
objectType: "user",
390+
objectId: objectId,
391+
},
392+
},
393+
} as v1.Relationship;
394+
},
395+
};
396+
},
397+
};
398+
},
330399
};

components/server/src/container-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ import { Redis } from "ioredis";
129129
import { RedisPublisher, newRedisClient } from "@gitpod/gitpod-db/lib";
130130
import { UserService } from "./user/user-service";
131131
import { RelationshipUpdater } from "./authorization/relationship-updater";
132+
import { WorkspaceService } from "./workspace/workspace-service";
132133

133134
export const productionContainerModule = new ContainerModule(
134135
(bind, unbind, isBound, rebind, unbindAsync, onActivation, onDeactivation) => {
@@ -159,6 +160,7 @@ export const productionContainerModule = new ContainerModule(
159160
bind(ConfigurationService).toSelf().inSingletonScope();
160161

161162
bind(SnapshotService).toSelf().inSingletonScope();
163+
bind(WorkspaceService).toSelf().inSingletonScope();
162164
bind(WorkspaceFactory).toSelf().inSingletonScope();
163165
bind(WorkspaceDeletionService).toSelf().inSingletonScope();
164166
bind(WorkspaceStarter).toSelf().inSingletonScope();

components/server/src/prebuilds/prebuild-manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
2222
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
2323
import { getCommitInfo, HostContextProvider } from "../auth/host-context-provider";
24-
import { WorkspaceFactory } from "../workspace/workspace-factory";
2524
import { ConfigProvider } from "../workspace/config-provider";
2625
import { WorkspaceStarter } from "../workspace/workspace-starter";
2726
import { Config } from "../config";
@@ -38,6 +37,7 @@ import { ErrorCodes, ApplicationError } from "@gitpod/gitpod-protocol/lib/messag
3837
import { UserAuthentication } from "../user/user-authentication";
3938
import { EntitlementService, MayStartWorkspaceResult } from "../billing/entitlement-service";
4039
import { EnvVarService } from "../workspace/env-var-service";
40+
import { WorkspaceService } from "../workspace/workspace-service";
4141

4242
export class WorkspaceRunningError extends Error {
4343
constructor(msg: string, public instance: WorkspaceInstance) {
@@ -56,7 +56,7 @@ export interface StartPrebuildParams {
5656
@injectable()
5757
export class PrebuildManager {
5858
@inject(TracedWorkspaceDB) protected readonly workspaceDB: DBWithTracing<WorkspaceDB>;
59-
@inject(WorkspaceFactory) protected readonly workspaceFactory: WorkspaceFactory;
59+
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
6060
@inject(WorkspaceStarter) protected readonly workspaceStarter: WorkspaceStarter;
6161
@inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;
6262
@inject(ConfigProvider) protected readonly configProvider: ConfigProvider;
@@ -224,7 +224,7 @@ export class PrebuildManager {
224224
}
225225
}
226226

227-
const workspace = await this.workspaceFactory.createForContext(
227+
const workspace = await this.workspaceService.createWorkspace(
228228
{ span },
229229
user,
230230
project.teamId,

0 commit comments

Comments
 (0)