Skip to content

Commit

Permalink
[server] blocklist repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexTugarev committed May 4, 2022
1 parent 612041c commit 79083ce
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
16 changes: 9 additions & 7 deletions components/server/ee/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {

const result = await this.eligibilityService.mayStartWorkspace(user, new Date(), runningInstances);
if (!result.enoughCredits) {
throw new ResponseError(ErrorCodes.NOT_ENOUGH_CREDIT, `Not enough monthly workspace hours. Please upgrade your account to get more hours for your workspaces.`);
throw new ResponseError(
ErrorCodes.NOT_ENOUGH_CREDIT,
`Not enough monthly workspace hours. Please upgrade your account to get more hours for your workspaces.`,
);
}
if (!!result.hitParallelWorkspaceLimit) {
throw new ResponseError(
Expand Down Expand Up @@ -565,14 +568,13 @@ export class GitpodServerEEImpl extends GitpodServerImpl {

await this.guardAdminAccess("adminBlockUser", { req }, Permission.ADMIN_USERS);

const target = await this.userDB.findUserById(req.id);
if (!target) {
let targetUser;
try {
targetUser = await this.userService.blockUser(req.id, req.blocked);
} catch (error) {
throw new ResponseError(ErrorCodes.NOT_FOUND, "not found");
}

target.blocked = !!req.blocked;
await this.userDB.storeUser(target);

const workspaceDb = this.workspaceDb.trace(ctx);
const workspaces = await workspaceDb.findWorkspacesByUser(req.id);
const isDefined = <T>(x: T | undefined): x is T => x !== undefined;
Expand All @@ -584,7 +586,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {

// For some reason, returning the result of `this.userDB.storeUser(target)` does not work. The response never arrives the caller.
// Returning `target` instead (which should be equivalent).
return this.censorUser(target);
return this.censorUser(targetUser);
}

async adminDeleteUser(ctx: TraceContext, userId: string): Promise<void> {
Expand Down
6 changes: 6 additions & 0 deletions components/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ export interface ConfigSerialized {
* Key '*' specifies the default rate limit for a cloneURL, unless overriden by a specific cloneURL.
*/
prebuildLimiter: { [cloneURL: string]: number } & { "*": number };

/**
* List of repositories not allowed to be used for workspace starts.
* `blockUser` attribute to control handling of the user's account.
*/
blockedRepositories?: { urlRegExp: string; blockUser: boolean }[];
}

export namespace ConfigFile {
Expand Down
10 changes: 10 additions & 0 deletions components/server/src/user/user-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ export class UserService {
return false;
}

async blockUser(targetUserId: string, block: boolean): Promise<User> {
const target = await this.userDb.findUserById(targetUserId);
if (!target) {
throw new Error("Not found.");
}

target.blocked = !!block;
return await this.userDb.storeUser(target);
}

async findUserForLogin(params: { candidate: Identity }) {
let user = await this.userDb.findUserByIdentity(params.candidate);
return user;
Expand Down
33 changes: 33 additions & 0 deletions components/server/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ export class WorkspaceStarter {

options = options || {};
try {
await this.checkBlockedRepository(user, workspace.contextURL);

// Some workspaces do not have an image source.
// Workspaces without image source are not only legacy, but also happened due to what looks like a bug.
// Whenever a such a workspace is re-started we'll give it an image source now. This is in line with how this thing used to work.
Expand Down Expand Up @@ -332,6 +334,37 @@ export class WorkspaceStarter {
}
}

protected blockedRepositories: { urlRegExp: RegExp; blockUser: boolean }[] | undefined;
protected getBlockedRepositories() {
if (!this.blockedRepositories) {
this.blockedRepositories = [];
if (this.config.blockedRepositories) {
for (const { blockUser, urlRegExp } of this.config.blockedRepositories) {
this.blockedRepositories.push({
blockUser,
urlRegExp: new RegExp(urlRegExp),
});
}
}
}
return this.blockedRepositories;
}

protected async checkBlockedRepository(user: User, contextURL: string) {
const hit = this.getBlockedRepositories().find((r) => !!contextURL && r.urlRegExp.test(contextURL));
if (!hit) {
return;
}
if (hit.blockUser) {
try {
await this.userService.blockUser(user.id, true);
} catch (error) {
log.error("Failed to block user.", error);
}
}
throw new Error(`${contextURL} is blocklisted on Gitpod.`);
}

// Note: this function does not expect to be awaited for by its caller. This means that it takes care of error handling itself.
protected async actuallyStartWorkspace(
ctx: TraceContext,
Expand Down

0 comments on commit 79083ce

Please sign in to comment.