Skip to content

Commit 6c5616c

Browse files
committed
Enable collaborators
1 parent 18b92d8 commit 6c5616c

File tree

6 files changed

+32
-15
lines changed

6 files changed

+32
-15
lines changed

components/server/src/projects/projects-service.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ export class ProjectsService {
6262
@inject(InstallationService) private readonly installationService: InstallationService,
6363
) {}
6464

65-
async getProject(userId: string, projectId: string): Promise<Project> {
66-
await this.auth.checkPermissionOnProject(userId, "read_info", projectId);
65+
/**
66+
* Returns a project by its ID.
67+
* @param skipPermissionCheck useful either when the caller already checked permissions or when we need to do something purely server-side (e.g. looking up a project when starting a workspace by a collaborator)
68+
*/
69+
async getProject(userId: string, projectId: string, skipPermissionCheck?: boolean): Promise<Project> {
70+
if (!skipPermissionCheck) {
71+
await this.auth.checkPermissionOnProject(userId, "read_info", projectId);
72+
}
6773
const project = await this.projectDB.findProjectById(projectId);
6874
if (!project) {
6975
throw new ApplicationError(ErrorCodes.NOT_FOUND, `Project ${projectId} not found.`);
@@ -132,11 +138,18 @@ export class ProjectsService {
132138
return filteredProjects;
133139
}
134140

135-
async findProjectsByCloneUrl(userId: string, cloneUrl: string, organizationId?: string): Promise<Project[]> {
141+
async findProjectsByCloneUrl(
142+
userId: string,
143+
cloneUrl: string,
144+
organizationId?: string,
145+
skipPermissionCheck?: boolean,
146+
): Promise<Project[]> {
136147
const projects = await this.projectDB.findProjectsByCloneUrl(cloneUrl, organizationId);
137148
const result: Project[] = [];
138149
for (const project of projects) {
139-
if (await this.auth.hasPermissionOnProject(userId, "read_info", project.id)) {
150+
const hasPermission =
151+
skipPermissionCheck || (await this.auth.hasPermissionOnProject(userId, "read_info", project.id));
152+
if (hasPermission) {
140153
result.push(project);
141154
}
142155
}

components/server/src/workspace/context-service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ export class ContextService {
158158
user.id,
159159
context.repository.cloneUrl,
160160
options?.organizationId,
161+
true,
161162
);
163+
// todo(ft): solve for this case with collaborators who can't select projects directly
162164
if (projects.length > 1) {
163165
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "Multiple projects found for clone URL.");
164166
}

components/server/src/workspace/suggested-repos-sorter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const sortSuggestedRepositories = (repos: SuggestedRepositoryWithSorting[
2020
// This allows us to consider the lastUse of a recently used project when sorting
2121
// as it will may have an entry for the project (no lastUse), and another for recent workspaces (w/ lastUse)
2222

23-
const projectURLs: string[] = [];
23+
let projectURLs: string[] = [];
2424
let uniqueRepositories: SuggestedRepositoryWithSorting[] = [];
2525

2626
for (const repo of repos) {
@@ -88,7 +88,7 @@ export const sortSuggestedRepositories = (repos: SuggestedRepositoryWithSorting[
8888
uniqueRepositories = uniqueRepositories.map((repo) => {
8989
if (repo.projectId && !repo.projectName) {
9090
delete repo.projectId;
91-
delete projectURLs[projectURLs.indexOf(repo.url)];
91+
projectURLs = projectURLs.filter((url) => url !== repo.url);
9292
}
9393

9494
return repo;

components/server/src/workspace/workspace-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ export class WorkspaceService {
819819
}
820820

821821
const projectPromise = workspace.projectId
822-
? ApplicationError.notFoundToUndefined(this.projectsService.getProject(user.id, workspace.projectId))
822+
? ApplicationError.notFoundToUndefined(this.projectsService.getProject(user.id, workspace.projectId, true))
823823
: Promise.resolve(undefined);
824824

825825
await mayStartPromise;

components/server/src/workspace/workspace-starter.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,8 +566,8 @@ export class WorkspaceStarter {
566566
return;
567567
}
568568

569-
// implicit project (existing on the same clone URL)
570-
const projects = await this.projectService.findProjectsByCloneUrl(user.id, contextURL, organizationId);
569+
// implicit project (existing on the same clone URL). We skip the permission check so that collaborators are not stuck
570+
const projects = await this.projectService.findProjectsByCloneUrl(user.id, contextURL, organizationId, true);
571571
if (projects.length === 0) {
572572
throw new ApplicationError(
573573
ErrorCodes.PRECONDITION_FAILED,
@@ -1951,10 +1951,12 @@ export class WorkspaceStarter {
19511951
{},
19521952
);
19531953
if (isEnabledPrebuildFullClone) {
1954-
const project = await this.projectService.getProject(user.id, workspace.projectId).catch((err) => {
1955-
log.error("failed to get project", err);
1956-
return undefined;
1957-
});
1954+
const project = await this.projectService
1955+
.getProject(user.id, workspace.projectId, true)
1956+
.catch((err) => {
1957+
log.error("failed to get project", err);
1958+
return undefined;
1959+
});
19581960
if (project && project.settings?.prebuilds?.cloneSettings?.fullClone) {
19591961
result.setFullClone(true);
19601962
}

components/spicedb/schema/schema.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ schema: |-
9292
permission read_billing = member + owner + installation->admin
9393
permission write_billing = owner + installation->admin
9494
95-
permission read_prebuild = member + owner + installation->admin
95+
permission read_prebuild = collaborator + member + owner + installation->admin
9696
9797
permission create_workspace = member + collaborator
9898
@@ -118,7 +118,7 @@ schema: |-
118118
permission write_info = editor + org->owner + org->installation_admin
119119
permission delete = editor + org->owner + org->installation_admin
120120
121-
permission read_env_var = viewer + editor + org->owner + org->installation_admin
121+
permission read_env_var = viewer + editor + org->collaborator + org->owner + org->installation_admin
122122
permission write_env_var = editor + org->owner + org->installation_admin
123123
124124
permission read_prebuild = viewer + editor + org->owner + org->installation_admin

0 commit comments

Comments
 (0)