Skip to content

Commit 3eb4df7

Browse files
committed
[server] Move some admin rpcs to non EE
1 parent 84fdedd commit 3eb4df7

File tree

2 files changed

+187
-32
lines changed

2 files changed

+187
-32
lines changed

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,9 @@ import {
1212
AdminGetListRequest,
1313
User,
1414
Team,
15-
TeamMemberInfo,
1615
AdminGetListResult,
1716
Permission,
1817
AdminBlockUserRequest,
19-
AdminModifyRoleOrPermissionRequest,
20-
RoleOrPermission,
21-
AdminModifyPermanentWorkspaceFeatureFlagRequest,
22-
UserFeatureSettings,
23-
AdminGetWorkspacesRequest,
24-
WorkspaceAndInstance,
2518
GetWorkspaceTimeoutResult,
2619
WorkspaceTimeoutDuration,
2720
SetWorkspaceTimeoutResult,
@@ -38,7 +31,6 @@ import {
3831
StartPrebuildResult,
3932
ClientHeaderFields,
4033
FindPrebuildsParams,
41-
TeamMemberRole,
4234
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
4335
PrebuildEvent,
4436
OpenPrebuildContext,
@@ -56,7 +48,6 @@ import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";
5648
import { LicenseValidationResult } from "@gitpod/gitpod-protocol/lib/license-protocol";
5749
import { PrebuildManager } from "../prebuilds/prebuild-manager";
5850
import { GuardedCostCenter, ResourceAccessGuard, ResourceAccessOp } from "../../../src/auth/resource-access";
59-
import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositories-protocol";
6051
import { CostCenterJSON, ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
6152
import {
6253
CostCenter,

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 187 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
SSHPublicKeyValue,
7777
UserSSHPublicKeyValue,
7878
PrebuildEvent,
79+
RoleOrPermission,
7980
} from "@gitpod/gitpod-protocol";
8081
import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositories-protocol";
8182
import {
@@ -154,6 +155,7 @@ import {
154155
EnvVarWithValue,
155156
LinkedInProfile,
156157
ProjectEnvVar,
158+
UserFeatureSettings,
157159
WorkspaceTimeoutSetting,
158160
} from "@gitpod/gitpod-protocol/lib/protocol";
159161
import { InstallationAdminSettings, TelemetryData } from "@gitpod/gitpod-protocol";
@@ -2891,19 +2893,46 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
28912893
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
28922894
}
28932895

2894-
adminGetBlockedRepositories(
2896+
async adminGetBlockedRepositories(
28952897
ctx: TraceContext,
28962898
req: AdminGetListRequest<BlockedRepository>,
28972899
): Promise<AdminGetListResult<BlockedRepository>> {
2898-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2900+
traceAPIParams(ctx, { req: censor(req, "searchTerm") }); // searchTerm may contain PII
2901+
2902+
await this.guardAdminAccess("adminGetBlockedRepositories", { req }, Permission.ADMIN_USERS);
2903+
2904+
try {
2905+
const res = await this.blockedRepostoryDB.findAllBlockedRepositories(
2906+
req.offset,
2907+
req.limit,
2908+
req.orderBy,
2909+
req.orderDir === "asc" ? "ASC" : "DESC",
2910+
req.searchTerm,
2911+
);
2912+
return res;
2913+
} catch (e) {
2914+
throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, e.toString());
2915+
}
28992916
}
29002917

2901-
adminCreateBlockedRepository(ctx: TraceContext, urlRegexp: string, blockUser: boolean): Promise<BlockedRepository> {
2902-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2918+
async adminCreateBlockedRepository(
2919+
ctx: TraceContext,
2920+
urlRegexp: string,
2921+
blockUser: boolean,
2922+
): Promise<BlockedRepository> {
2923+
traceAPIParams(ctx, { urlRegexp, blockUser });
2924+
2925+
await this.guardAdminAccess("adminCreateBlockedRepository", { urlRegexp, blockUser }, Permission.ADMIN_USERS);
2926+
2927+
return await this.blockedRepostoryDB.createBlockedRepository(urlRegexp, blockUser);
29032928
}
29042929

2905-
adminDeleteBlockedRepository(ctx: TraceContext, id: number): Promise<void> {
2906-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2930+
async adminDeleteBlockedRepository(ctx: TraceContext, id: number): Promise<void> {
2931+
traceAPIParams(ctx, { id });
2932+
2933+
await this.guardAdminAccess("adminDeleteBlockedRepository", { id }, Permission.ADMIN_USERS);
2934+
2935+
await this.blockedRepostoryDB.deleteBlockedRepository(id);
29072936
}
29082937

29092938
async adminGetUser(ctx: TraceContext, id: string): Promise<User> {
@@ -2914,34 +2943,120 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
29142943
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
29152944
}
29162945

2917-
async adminDeleteUser(ctx: TraceContext, _id: string): Promise<void> {
2918-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2946+
async adminDeleteUser(ctx: TraceContext, userId: string): Promise<void> {
2947+
traceAPIParams(ctx, { userId });
2948+
2949+
await this.guardAdminAccess("adminDeleteUser", { id: userId }, Permission.ADMIN_USERS);
2950+
2951+
try {
2952+
await this.userDeletionService.deleteUser(userId);
2953+
} catch (e) {
2954+
throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, e.toString());
2955+
}
29192956
}
29202957

2921-
async adminVerifyUser(ctx: TraceContext, _id: string): Promise<User> {
2922-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2958+
async adminVerifyUser(ctx: TraceContext, userId: string): Promise<User> {
2959+
await this.guardAdminAccess("adminVerifyUser", { id: userId }, Permission.ADMIN_USERS);
2960+
try {
2961+
const user = await this.userDB.findUserById(userId);
2962+
if (!user) {
2963+
throw new ResponseError(ErrorCodes.NOT_FOUND, `No user with id ${userId} found.`);
2964+
}
2965+
this.verificationService.markVerified(user);
2966+
await this.userDB.updateUserPartial(user);
2967+
return user;
2968+
} catch (e) {
2969+
throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, e.toString());
2970+
}
29232971
}
29242972

29252973
async adminModifyRoleOrPermission(ctx: TraceContext, req: AdminModifyRoleOrPermissionRequest): Promise<User> {
2926-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
2974+
traceAPIParams(ctx, { req });
2975+
2976+
await this.guardAdminAccess("adminModifyRoleOrPermission", { req }, Permission.ADMIN_USERS);
2977+
2978+
const target = await this.userDB.findUserById(req.id);
2979+
if (!target) {
2980+
throw new ResponseError(ErrorCodes.NOT_FOUND, "not found");
2981+
}
2982+
2983+
const rolesOrPermissions = new Set((target.rolesOrPermissions || []) as string[]);
2984+
req.rpp.forEach((e) => {
2985+
if (e.add) {
2986+
rolesOrPermissions.add(e.r as string);
2987+
} else {
2988+
rolesOrPermissions.delete(e.r as string);
2989+
}
2990+
});
2991+
target.rolesOrPermissions = Array.from(rolesOrPermissions.values()) as RoleOrPermission[];
2992+
2993+
await this.userDB.storeUser(target);
2994+
// For some reason, neither returning the result of `this.userDB.storeUser(target)` nor returning `target` work.
2995+
// The response never arrives the caller.
2996+
// Returning the following works at the cost of an additional DB query:
2997+
return this.censorUser((await this.userDB.findUserById(req.id))!);
29272998
}
29282999

29293000
async adminModifyPermanentWorkspaceFeatureFlag(
29303001
ctx: TraceContext,
29313002
req: AdminModifyPermanentWorkspaceFeatureFlagRequest,
29323003
): Promise<User> {
2933-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3004+
traceAPIParams(ctx, { req });
3005+
3006+
await this.guardAdminAccess("adminModifyPermanentWorkspaceFeatureFlag", { req }, Permission.ADMIN_USERS);
3007+
const target = await this.userDB.findUserById(req.id);
3008+
if (!target) {
3009+
throw new ResponseError(ErrorCodes.NOT_FOUND, "not found");
3010+
}
3011+
3012+
const featureSettings: UserFeatureSettings = target.featureFlags || {};
3013+
const featureFlags = new Set(featureSettings.permanentWSFeatureFlags || []);
3014+
3015+
req.changes.forEach((e) => {
3016+
if (e.add) {
3017+
featureFlags.add(e.featureFlag);
3018+
} else {
3019+
featureFlags.delete(e.featureFlag);
3020+
}
3021+
});
3022+
featureSettings.permanentWSFeatureFlags = Array.from(featureFlags);
3023+
target.featureFlags = featureSettings;
3024+
3025+
await this.userDB.storeUser(target);
3026+
// For some reason, returning the result of `this.userDB.storeUser(target)` does not work. The response never arrives the caller.
3027+
// Returning `target` instead (which should be equivalent).
3028+
return this.censorUser(target);
29343029
}
29353030

29363031
async adminGetWorkspaces(
29373032
ctx: TraceContext,
29383033
req: AdminGetWorkspacesRequest,
29393034
): Promise<AdminGetListResult<WorkspaceAndInstance>> {
2940-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3035+
traceAPIParams(ctx, { req });
3036+
3037+
await this.guardAdminAccess("adminGetWorkspaces", { req }, Permission.ADMIN_WORKSPACES);
3038+
3039+
return await this.workspaceDb
3040+
.trace(ctx)
3041+
.findAllWorkspaceAndInstances(
3042+
req.offset,
3043+
req.limit,
3044+
req.orderBy,
3045+
req.orderDir === "asc" ? "ASC" : "DESC",
3046+
req,
3047+
);
29413048
}
29423049

2943-
async adminGetWorkspace(ctx: TraceContext, id: string): Promise<WorkspaceAndInstance> {
2944-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3050+
async adminGetWorkspace(ctx: TraceContext, workspaceId: string): Promise<WorkspaceAndInstance> {
3051+
traceAPIParams(ctx, { workspaceId });
3052+
3053+
await this.guardAdminAccess("adminGetWorkspace", { id: workspaceId }, Permission.ADMIN_WORKSPACES);
3054+
3055+
const result = await this.workspaceDb.trace(ctx).findWorkspaceAndInstance(workspaceId);
3056+
if (!result) {
3057+
throw new ResponseError(ErrorCodes.NOT_FOUND, "not found");
3058+
}
3059+
return result;
29453060
}
29463061

29473062
async adminGetWorkspaceInstances(ctx: TraceContext, workspaceId: string): Promise<WorkspaceInstance[]> {
@@ -2952,27 +3067,74 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
29523067
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
29533068
}
29543069

2955-
async adminRestoreSoftDeletedWorkspace(ctx: TraceContext, id: string): Promise<void> {
2956-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3070+
async adminRestoreSoftDeletedWorkspace(ctx: TraceContext, workspaceId: string): Promise<void> {
3071+
traceAPIParams(ctx, { workspaceId });
3072+
3073+
await this.guardAdminAccess(
3074+
"adminRestoreSoftDeletedWorkspace",
3075+
{ id: workspaceId },
3076+
Permission.ADMIN_WORKSPACES,
3077+
);
3078+
3079+
await this.workspaceDb.trace(ctx).transaction(async (db) => {
3080+
const ws = await db.findById(workspaceId);
3081+
if (!ws) {
3082+
throw new ResponseError(ErrorCodes.NOT_FOUND, `No workspace with id '${workspaceId}' found.`);
3083+
}
3084+
if (!ws.softDeleted) {
3085+
return;
3086+
}
3087+
if (!!ws.contentDeletedTime) {
3088+
throw new ResponseError(ErrorCodes.NOT_FOUND, "The workspace content was already garbage-collected.");
3089+
}
3090+
// @ts-ignore
3091+
ws.softDeleted = null;
3092+
ws.softDeletedTime = "";
3093+
ws.pinned = true;
3094+
await db.store(ws);
3095+
});
29573096
}
29583097

29593098
async adminGetProjectsBySearchTerm(
29603099
ctx: TraceContext,
29613100
req: AdminGetListRequest<Project>,
29623101
): Promise<AdminGetListResult<Project>> {
2963-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3102+
await this.guardAdminAccess("adminGetProjectsBySearchTerm", { req }, Permission.ADMIN_PROJECTS);
3103+
return await this.projectDB.findProjectsBySearchTerm(
3104+
req.offset,
3105+
req.limit,
3106+
req.orderBy,
3107+
req.orderDir === "asc" ? "ASC" : "DESC",
3108+
req.searchTerm as string,
3109+
);
29643110
}
29653111

29663112
async adminGetProjectById(ctx: TraceContext, id: string): Promise<Project | undefined> {
2967-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3113+
await this.guardAdminAccess("adminGetProjectById", { id }, Permission.ADMIN_PROJECTS);
3114+
return await this.projectDB.findProjectById(id);
29683115
}
29693116

29703117
async adminGetTeams(ctx: TraceContext, req: AdminGetListRequest<Team>): Promise<AdminGetListResult<Team>> {
2971-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3118+
await this.guardAdminAccess("adminGetTeams", { req }, Permission.ADMIN_WORKSPACES);
3119+
3120+
return await this.teamDB.findTeams(
3121+
req.offset,
3122+
req.limit,
3123+
req.orderBy,
3124+
req.orderDir === "asc" ? "ASC" : "DESC",
3125+
req.searchTerm as string,
3126+
);
29723127
}
29733128

29743129
async adminGetTeamMembers(ctx: TraceContext, teamId: string): Promise<TeamMemberInfo[]> {
2975-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3130+
await this.guardAdminAccess("adminGetTeamMembers", { teamId }, Permission.ADMIN_WORKSPACES);
3131+
3132+
const team = await this.teamDB.findTeamById(teamId);
3133+
if (!team) {
3134+
throw new ResponseError(ErrorCodes.NOT_FOUND, "Team not found");
3135+
}
3136+
const members = await this.teamDB.findMembersByTeam(team.id);
3137+
return members;
29763138
}
29773139

29783140
async adminSetTeamMemberRole(
@@ -2981,11 +3143,13 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
29813143
userId: string,
29823144
role: TeamMemberRole,
29833145
): Promise<void> {
2984-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3146+
await this.guardAdminAccess("adminSetTeamMemberRole", { teamId, userId, role }, Permission.ADMIN_WORKSPACES);
3147+
return this.teamDB.setTeamMemberRole(userId, teamId, role);
29853148
}
29863149

29873150
async adminGetTeamById(ctx: TraceContext, id: string): Promise<Team | undefined> {
2988-
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
3151+
await this.guardAdminAccess("adminGetTeamById", { id }, Permission.ADMIN_WORKSPACES);
3152+
return await this.teamDB.findTeamById(id);
29893153
}
29903154

29913155
async adminFindPrebuilds(ctx: TraceContext, params: FindPrebuildsParams): Promise<PrebuildWithStatus[]> {

0 commit comments

Comments
 (0)