Skip to content

Commit 3f0fc73

Browse files
Fix spicedb throwing on invalid arguments (#20269)
1 parent dae1529 commit 3f0fc73

File tree

4 files changed

+37
-24
lines changed

4 files changed

+37
-24
lines changed

components/gitpod-protocol/src/util/gitpod-host-url.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const baseWorkspaceIDRegex =
1616
"(([a-f][0-9a-f]{7}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|([0-9a-z]{2,16}-[0-9a-z]{2,16}-[0-9a-z]{8,11}))";
1717

1818
// this pattern matches v4 UUIDs as well as the new generated workspace ids (e.g. pink-panda-ns35kd21)
19-
const workspaceIDRegex = RegExp(`^(?:debug-)?${baseWorkspaceIDRegex}$`);
19+
export const workspaceIDRegex = RegExp(`^(?:debug-)?${baseWorkspaceIDRegex}$`);
2020

2121
// this pattern matches URL prefixes of workspaces
2222
const workspaceUrlPrefixRegex = RegExp(`^(([0-9]{4,6}|debug)-)?${baseWorkspaceIDRegex}\\.`);

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

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messag
5858
import { ContextService } from "../workspace/context-service";
5959
import { UserService } from "../user/user-service";
6060
import { ContextParser } from "../workspace/context-parser-service";
61+
import { workspaceIDRegex } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";
62+
63+
const isWorkspaceId = (workspaceId?: string) => {
64+
if (!workspaceId) {
65+
return false;
66+
}
67+
68+
return workspaceIDRegex.test(workspaceId);
69+
};
6170

6271
@injectable()
6372
export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceInterface> {
@@ -68,8 +77,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
6877
@inject(ContextParser) private contextParser: ContextParser;
6978

7079
async getWorkspace(req: GetWorkspaceRequest, _: HandlerContext): Promise<GetWorkspaceResponse> {
71-
if (!req.workspaceId) {
72-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
80+
if (!isWorkspaceId(req.workspaceId)) {
81+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
7382
}
7483
const info = await this.workspaceService.getWorkspace(ctxUserId(), req.workspaceId);
7584
const response = new GetWorkspaceResponse();
@@ -198,8 +207,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
198207

199208
async startWorkspace(req: StartWorkspaceRequest): Promise<StartWorkspaceResponse> {
200209
// We rely on FGA to do the permission checking
201-
if (!req.workspaceId) {
202-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
210+
if (!isWorkspaceId(req.workspaceId)) {
211+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
203212
}
204213
const user = await this.userService.findUserById(ctxUserId(), ctxUserId());
205214
const { workspace, latestInstance: instance } = await this.workspaceService.getWorkspace(
@@ -227,8 +236,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
227236
req: GetWorkspaceDefaultImageRequest,
228237
_: HandlerContext,
229238
): Promise<GetWorkspaceDefaultImageResponse> {
230-
if (!req.workspaceId) {
231-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
239+
if (!isWorkspaceId(req.workspaceId)) {
240+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
232241
}
233242
const result = await this.workspaceService.getWorkspaceDefaultImage(ctxUserId(), req.workspaceId);
234243
const response = new GetWorkspaceDefaultImageResponse({
@@ -246,8 +255,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
246255
}
247256

248257
async sendHeartBeat(req: SendHeartBeatRequest, _: HandlerContext): Promise<SendHeartBeatResponse> {
249-
if (!req.workspaceId) {
250-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
258+
if (!isWorkspaceId(req.workspaceId)) {
259+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
251260
}
252261
const info = await this.workspaceService.getWorkspace(ctxUserId(), req.workspaceId);
253262
if (!info.latestInstance?.id || info.latestInstance.status.phase !== "running") {
@@ -265,8 +274,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
265274
req: GetWorkspaceOwnerTokenRequest,
266275
_: HandlerContext,
267276
): Promise<GetWorkspaceOwnerTokenResponse> {
268-
if (!req.workspaceId) {
269-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
277+
if (!isWorkspaceId(req.workspaceId)) {
278+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
270279
}
271280
const ownerToken = await this.workspaceService.getOwnerToken(ctxUserId(), req.workspaceId);
272281
const response = new GetWorkspaceOwnerTokenResponse();
@@ -278,8 +287,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
278287
req: GetWorkspaceEditorCredentialsRequest,
279288
_: HandlerContext,
280289
): Promise<GetWorkspaceEditorCredentialsResponse> {
281-
if (!req.workspaceId) {
282-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
290+
if (!isWorkspaceId(req.workspaceId)) {
291+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
283292
}
284293
const credentials = await this.workspaceService.getIDECredentials(ctxUserId(), req.workspaceId);
285294
const response = new GetWorkspaceEditorCredentialsResponse();
@@ -288,8 +297,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
288297
}
289298

290299
async updateWorkspace(req: UpdateWorkspaceRequest): Promise<UpdateWorkspaceResponse> {
291-
if (!req.workspaceId) {
292-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
300+
if (!isWorkspaceId(req.workspaceId)) {
301+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
293302
}
294303
if (req.spec?.timeout?.inactivity?.seconds || (req.spec?.sshPublicKeys && req.spec?.sshPublicKeys.length > 0)) {
295304
throw new ApplicationError(ErrorCodes.UNIMPLEMENTED, "not implemented");
@@ -363,17 +372,17 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
363372
}
364373

365374
async stopWorkspace(req: StopWorkspaceRequest): Promise<StopWorkspaceResponse> {
366-
if (!req.workspaceId) {
367-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
375+
if (!isWorkspaceId(req.workspaceId)) {
376+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
368377
}
369378
await this.workspaceService.stopWorkspace(ctxUserId(), req.workspaceId, "stopped via API");
370379
const response = new StopWorkspaceResponse();
371380
return response;
372381
}
373382

374383
async deleteWorkspace(req: DeleteWorkspaceRequest): Promise<DeleteWorkspaceResponse> {
375-
if (!req.workspaceId) {
376-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
384+
if (!isWorkspaceId(req.workspaceId)) {
385+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
377386
}
378387
await this.workspaceService.deleteWorkspace(ctxUserId(), req.workspaceId, "user");
379388
const response = new DeleteWorkspaceResponse();
@@ -389,8 +398,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
389398
}
390399

391400
async createWorkspaceSnapshot(req: CreateWorkspaceSnapshotRequest): Promise<CreateWorkspaceSnapshotResponse> {
392-
if (!req.workspaceId) {
393-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
401+
if (!isWorkspaceId(req.workspaceId)) {
402+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
394403
}
395404
const snapshot = await this.workspaceService.takeSnapshot(ctxUserId(), {
396405
workspaceId: req.workspaceId,
@@ -410,8 +419,8 @@ export class WorkspaceServiceAPI implements ServiceImpl<typeof WorkspaceServiceI
410419
}
411420

412421
async updateWorkspacePort(req: UpdateWorkspacePortRequest): Promise<UpdateWorkspacePortResponse> {
413-
if (!req.workspaceId) {
414-
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "workspaceId is required");
422+
if (!isWorkspaceId(req.workspaceId)) {
423+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "a valid workspaceId is required");
415424
}
416425
if (!req.port) {
417426
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "port is required");

components/server/src/authorization/spicedb-authorizer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { base64decode } from "@jmondi/oauth2-server";
1515
import { DecodedZedToken } from "@gitpod/spicedb-impl/lib/impl/v1/impl.pb";
1616
import { ctxTryGetCache, ctxTrySetCache } from "../util/request-context";
1717
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
18+
import { isGrpcError } from "@gitpod/gitpod-protocol/lib/util/grpc";
1819

1920
async function tryThree<T>(errMessage: string, code: (attempt: number) => Promise<T>): Promise<T> {
2021
let attempt = 0;
@@ -104,6 +105,9 @@ export class SpiceDBAuthorizer {
104105
const permitted = response.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION;
105106
return { permitted, checkedAt: response.checkedAt?.token };
106107
} catch (err) {
108+
if (isGrpcError(err) && err.code === grpc.status.INVALID_ARGUMENT) {
109+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, `Invalid request for permission check: ${err}`);
110+
}
107111
error = err;
108112
log.error("[spicedb] Failed to perform authorization check.", err, {
109113
request: new TrustedValue(req),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export class WorkspaceService {
248248
const res = await this.db.find({
249249
limit: 20,
250250
...options,
251-
userId, // gpl: We probably want to removed this limitation in the future, butkeeping the old behavior for now due to focus on FGA
251+
userId, // gpl: We probably want to removed this limitation in the future, but keeping the old behavior for now due to focus on FGA
252252
includeHeadless: false,
253253
});
254254

0 commit comments

Comments
 (0)