diff --git a/components/dashboard/src/App.tsx b/components/dashboard/src/App.tsx index c49cab5dc3b0f6..74b68cc813d867 100644 --- a/components/dashboard/src/App.tsx +++ b/components/dashboard/src/App.tsx @@ -12,6 +12,7 @@ import { Login } from './Login'; import { UserContext } from './user-context'; import { TeamsContext } from './teams/teams-context'; import { ThemeContext } from './theme-context'; +import { AdminContext } from './admin-context'; import { getGitpodService } from './service/service'; import { shouldSeeWhatsNew, WhatsNew } from './whatsnew/WhatsNew'; import gitpodIcon from './icons/gitpod.svg'; @@ -52,6 +53,7 @@ const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ './ const FromReferrer = React.lazy(() => import(/* webpackPrefetch: true */ './FromReferrer')); const UserSearch = React.lazy(() => import(/* webpackPrefetch: true */ './admin/UserSearch')); const WorkspacesSearch = React.lazy(() => import(/* webpackPrefetch: true */ './admin/WorkspacesSearch')); +const AdminSettings = React.lazy(() => import(/* webpackPrefetch: true */ './admin/Settings')); const OAuthClientApproval = React.lazy(() => import(/* webpackPrefetch: true */ './OauthClientApproval')); function Loading() { @@ -98,11 +100,12 @@ export function getURLHash() { function App() { const { user, setUser } = useContext(UserContext); const { teams, setTeams } = useContext(TeamsContext); + const { setAdminSettings } = useContext(AdminContext); const { setIsDark } = useContext(ThemeContext); - const [ loading, setLoading ] = useState(true); - const [ isWhatsNewShown, setWhatsNewShown ] = useState(false); - const [ isSetupRequired, setSetupRequired ] = useState(false); + const [loading, setLoading] = useState(true); + const [isWhatsNewShown, setWhatsNewShown] = useState(false); + const [isSetupRequired, setSetupRequired] = useState(false); const history = useHistory(); useEffect(() => { @@ -132,6 +135,11 @@ function App() { } } setTeams(teams); + + if (user?.rolesOrPermissions?.includes('admin')) { + const adminSettings = await getGitpodService().server.adminGetSettings(); + setAdminSettings(adminSettings); + } } catch (error) { console.error(error); if (error && "code" in error) { @@ -279,6 +287,7 @@ function App() { + diff --git a/components/dashboard/src/admin-context.tsx b/components/dashboard/src/admin-context.tsx new file mode 100644 index 00000000000000..7355c500eead50 --- /dev/null +++ b/components/dashboard/src/admin-context.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import React, { createContext, useState } from 'react'; +import { InstallationAdminSettings } from "@gitpod/gitpod-protocol"; + +const AdminContext = createContext<{ + adminSettings?: InstallationAdminSettings, + setAdminSettings: React.Dispatch, +}>({ + setAdminSettings: () => null, +}); + +const AdminContextProvider: React.FC = ({ children }) => { + const [adminSettings, setAdminSettings] = useState(); + return ( + + {children} + + ); +}; + +export { AdminContext, AdminContextProvider }; diff --git a/components/dashboard/src/admin/Settings.tsx b/components/dashboard/src/admin/Settings.tsx new file mode 100644 index 00000000000000..3744ffa87cc8ea --- /dev/null +++ b/components/dashboard/src/admin/Settings.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { useContext } from "react"; +import { InstallationAdminSettings } from "@gitpod/gitpod-protocol"; +import { AdminContext } from "../admin-context"; +import CheckBox from "../components/CheckBox"; +import { PageWithSubMenu } from "../components/PageWithSubMenu"; +import { getGitpodService } from "../service/service"; +import { adminMenu } from "./admin-menu"; + +export default function Settings() { + const { adminSettings, setAdminSettings } = useContext(AdminContext); + + const actuallySetTelemetryPrefs = async (value: InstallationAdminSettings) => { + await getGitpodService().server.adminUpdateSettings(value); + setAdminSettings(value); + } + + return ( +
+ +

Usage Statistics

+ This is used to provide insights on how you use your cluster so we can provide a better overall experience. Read our Privacy Policy} + checked={adminSettings?.sendTelemetry ?? false} + onChange={(evt) => actuallySetTelemetryPrefs({ + sendTelemetry: evt.target.checked, + })} /> +
+
+ ) +} diff --git a/components/dashboard/src/admin/admin-menu.ts b/components/dashboard/src/admin/admin-menu.ts index c6d4f6f2ccf00f..a5c5fea86b21bc 100644 --- a/components/dashboard/src/admin/admin-menu.ts +++ b/components/dashboard/src/admin/admin-menu.ts @@ -10,4 +10,7 @@ export const adminMenu = [{ }, { title: 'Workspaces', link: ['/admin/workspaces'] +}, { + title: 'Settings', + link: ['/admin/settings'] },]; \ No newline at end of file diff --git a/components/dashboard/src/index.tsx b/components/dashboard/src/index.tsx index 8fb34ef8b70765..5057321cacf1ae 100644 --- a/components/dashboard/src/index.tsx +++ b/components/dashboard/src/index.tsx @@ -8,6 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { UserContextProvider } from './user-context'; +import { AdminContextProvider } from './admin-context'; import { TeamsContextProvider } from './teams/teams-context'; import { ProjectContextProvider } from './projects/project-context'; import { ThemeContextProvider } from './theme-context'; @@ -18,15 +19,17 @@ import "./index.css" ReactDOM.render( - - - - - - - - - + + + + + + + + + + + , document.getElementById('root') diff --git a/components/gitpod-db/src/installation-admin-db.ts b/components/gitpod-db/src/installation-admin-db.ts index 78976218e1558e..a23fefd0810a80 100644 --- a/components/gitpod-db/src/installation-admin-db.ts +++ b/components/gitpod-db/src/installation-admin-db.ts @@ -4,9 +4,10 @@ * See License-AGPL.txt in the project root for license information. */ -import { InstallationAdmin } from "@gitpod/gitpod-protocol"; +import { InstallationAdmin, InstallationAdminSettings } from "@gitpod/gitpod-protocol"; export const InstallationAdminDB = Symbol('InstallationAdminDB'); export interface InstallationAdminDB { - getTelemetryData(): Promise; + getData(): Promise; + setSettings(settings: Partial): Promise; } diff --git a/components/gitpod-db/src/typeorm/installation-admin-db-impl.ts b/components/gitpod-db/src/typeorm/installation-admin-db-impl.ts index 24ff7660358890..8f0157e9d7500c 100644 --- a/components/gitpod-db/src/typeorm/installation-admin-db-impl.ts +++ b/components/gitpod-db/src/typeorm/installation-admin-db-impl.ts @@ -5,8 +5,7 @@ */ import { inject, injectable, } from 'inversify'; -import { InstallationAdmin } from '@gitpod/gitpod-protocol'; -import { v4 as uuidv4 } from 'uuid'; +import { InstallationAdmin, InstallationAdminSettings } from '@gitpod/gitpod-protocol'; import { Repository } from 'typeorm'; import { TypeORM } from './typeorm'; import { InstallationAdminDB } from '../installation-admin-db'; @@ -21,12 +20,7 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB { } protected async createDefaultRecord(): Promise { - const record: InstallationAdmin = { - id: uuidv4(), - settings: { - sendTelemetry: false, - }, - }; + const record = InstallationAdmin.createDefault(); const repo = await this.getInstallationAdminRepo(); return repo.save(record); @@ -37,14 +31,14 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB { } /** - * Get Telemetry Data + * Get Data * * Returns the first record found or creates a * new record. * * @returns Promise */ - async getTelemetryData(): Promise { + async getData(): Promise { const repo = await this.getInstallationAdminRepo(); const [record] = await repo.find(); @@ -55,4 +49,14 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB { /* Record not found - create one */ return this.createDefaultRecord(); } + + async setSettings(settings: InstallationAdminSettings): Promise { + const record = await this.getData(); + record.settings = { + ...settings, + } + + const repo = await this.getInstallationAdminRepo(); + await repo.save(record); + } } diff --git a/components/gitpod-protocol/src/admin-protocol.ts b/components/gitpod-protocol/src/admin-protocol.ts index c5281f3515a739..0c20ad8d2c01cc 100644 --- a/components/gitpod-protocol/src/admin-protocol.ts +++ b/components/gitpod-protocol/src/admin-protocol.ts @@ -8,6 +8,7 @@ import { User, Workspace, NamedWorkspaceFeatureFlag } from "./protocol"; import { WorkspaceInstance, WorkspaceInstancePhase } from "./workspace-instance"; import { RoleOrPermission } from "./permission"; import { AccountStatement } from "./accounting-protocol"; +import { InstallationAdminSettings } from "./installation-admin-protocol"; export interface AdminServer { adminGetUsers(req: AdminGetListRequest): Promise>; @@ -29,6 +30,9 @@ export interface AdminServer { adminIsStudent(userId: string): Promise; adminAddStudentEmailDomain(userId: string, domain: string): Promise; adminGrantExtraHours(userId: string, extraHours: number): Promise; + + adminGetSettings(): Promise + adminUpdateSettings(settings: InstallationAdminSettings): Promise } export interface AdminGetListRequest { @@ -65,7 +69,7 @@ export interface AdminModifyPermanentWorkspaceFeatureFlagRequest { }[] } -export interface WorkspaceAndInstance extends Omit, Omit { +export interface WorkspaceAndInstance extends Omit, Omit { workspaceId: string; workspaceCreationTime: string; instanceId: string; @@ -78,7 +82,7 @@ export namespace WorkspaceAndInstance { return { id: wai.workspaceId, creationTime: wai.workspaceCreationTime, - ... wai + ...wai }; } @@ -89,7 +93,7 @@ export namespace WorkspaceAndInstance { return { id: wai.instanceId, creationTime: wai.instanceCreationTime, - ... wai + ...wai }; } } diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index b1a51a895681f3..bd2c7d42563d57 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -30,6 +30,7 @@ import { GithubUpgradeURL, PlanCoupon } from './payment-protocol'; import { TeamSubscription, TeamSubscriptionSlot, TeamSubscriptionSlotResolved } from './team-subscription-protocol'; import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from './analytics'; import { IDEServer } from './ide-protocol'; +import { InstallationAdminSettings } from './installation-admin-protocol'; export interface GitpodClient { onInstanceUpdate(instance: WorkspaceInstance): void; @@ -131,6 +132,10 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, resetGenericInvite(inviteId: string): Promise; deleteTeam(teamId: string, userId: string): Promise; + // Admin Settings + adminGetSettings(): Promise; + adminUpdateSettings(settings: InstallationAdminSettings): Promise; + // Projects getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise; createProject(params: CreateProjectParams): Promise; diff --git a/components/gitpod-protocol/src/installation-admin-protocol.ts b/components/gitpod-protocol/src/installation-admin-protocol.ts index 62b68ce3e33555..2a3bbdd84c0577 100644 --- a/components/gitpod-protocol/src/installation-admin-protocol.ts +++ b/components/gitpod-protocol/src/installation-admin-protocol.ts @@ -4,11 +4,32 @@ * See License-AGPL.txt in the project root for license information. */ -export interface InstallationAdminSettings { - sendTelemetry: boolean; +import { v4 as uuidv4 } from 'uuid'; + +const InstallationAdminSettingsPrototype = { + sendTelemetry: true +} + +export type InstallationAdminSettings = typeof InstallationAdminSettingsPrototype; + +export namespace InstallationAdminSettings { + export function fields(): (keyof InstallationAdminSettings)[] { + return Object.keys(InstallationAdminSettingsPrototype) as (keyof InstallationAdminSettings)[]; + } } export interface InstallationAdmin { id: string; settings: InstallationAdminSettings; -} \ No newline at end of file +} + +export namespace InstallationAdmin { + export function createDefault(): InstallationAdmin { + return { + id: uuidv4(), + settings: { + ...InstallationAdminSettingsPrototype, + } + }; + } +} diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index d076c83277ba83..20f375a310575a 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -7,7 +7,7 @@ import { injectable, inject } from "inversify"; import { GitpodServerImpl, traceAPIParams, traceWI, censor } from "../../../src/workspace/gitpod-server-impl"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; -import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, PermissionName, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue, PrebuildWithStatus, CreateProjectParams, Project, StartPrebuildResult, ClientHeaderFields, Workspace } from "@gitpod/gitpod-protocol"; +import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue, PrebuildWithStatus, CreateProjectParams, Project, StartPrebuildResult, ClientHeaderFields, Workspace } from "@gitpod/gitpod-protocol"; import { ResponseError } from "vscode-jsonrpc"; import { TakeSnapshotRequest, AdmissionLevel, ControlAdmissionRequest, StopWorkspacePolicy, DescribeWorkspaceRequest, SetTimeoutRequest } from "@gitpod/ws-manager/lib"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; @@ -602,15 +602,6 @@ export class GitpodServerEEImpl extends GitpodServerImpl { }); } - protected async guardAdminAccess(method: string, params: any, requiredPermission: PermissionName) { - const user = this.checkAndBlockUser(method); - if (!this.authorizationService.hasPermission(user, requiredPermission)) { - log.warn({ userId: this.user?.id }, "unauthorised admin access", { authorised: false, method, params }); - throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "not allowed"); - } - log.info({ userId: this.user?.id }, "admin access", { authorised: true, method, params }); - } - protected async findPrebuiltWorkspace(parentCtx: TraceContext, user: User, context: WorkspaceContext, mode: CreateWorkspaceMode): Promise { const ctx = TraceContext.childContext("findPrebuiltWorkspace", parentCtx); diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index 3440fc220ab3cd..8d90c9cf9c2087 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -136,6 +136,8 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig { "adminForceStopWorkspace": { group: "default", points: 1 }, "adminRestoreSoftDeletedWorkspace": { group: "default", points: 1 }, "adminSetLicense": { group: "default", points: 1 }, + "adminGetSettings": { group: "default", points: 1 }, + "adminUpdateSettings": { group: "default", points: 1 }, "validateLicense": { group: "default", points: 1 }, "getLicenseInfo": { group: "default", points: 1 }, diff --git a/components/server/src/installation-admin/installation-admin-controller.ts b/components/server/src/installation-admin/installation-admin-controller.ts index 8b209b1fa6fb61..d30d2ceaa79bfa 100644 --- a/components/server/src/installation-admin/installation-admin-controller.ts +++ b/components/server/src/installation-admin/installation-admin-controller.ts @@ -16,9 +16,9 @@ export class InstallationAdminController { const app = express(); app.get('/data', async (req: express.Request, res: express.Response) => { - const data = await this.installationAdminDb.getTelemetryData(); + const data = await this.installationAdminDb.getData(); - res.send(data); + res.status(200).json(data); }); return app; diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 5023a29215cbd5..e17c34e7327386 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -5,8 +5,8 @@ */ import { DownloadUrlRequest, DownloadUrlResponse, UploadUrlRequest, UploadUrlResponse } from '@gitpod/content-service/lib/blobs_pb'; -import { AppInstallationDB, UserDB, UserMessageViewsDB, WorkspaceDB, DBWithTracing, TracedWorkspaceDB, DBGitpodToken, DBUser, UserStorageResourcesDB, TeamDB } from '@gitpod/gitpod-db/lib'; -import { AuthProviderEntry, AuthProviderInfo, CommitContext, Configuration, CreateWorkspaceMode, DisposableCollection, GetWorkspaceTimeoutResult, GitpodClient as GitpodApiClient, GitpodServer, GitpodToken, GitpodTokenType, InstallPluginsParams, PermissionName, PortVisibility, PrebuiltWorkspace, PrebuiltWorkspaceContext, PreparePluginUploadParams, ResolvedPlugins, ResolvePluginsParams, SetWorkspaceTimeoutResult, StartPrebuildContext, StartWorkspaceResult, Terms, Token, UninstallPluginParams, User, UserEnvVar, UserEnvVarValue, UserInfo, WhitelistedRepository, Workspace, WorkspaceContext, WorkspaceCreationResult, WorkspaceImageBuild, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstanceUser, WorkspaceTimeoutDuration, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo, TeamMembershipInvite, CreateProjectParams, Project, ProviderRepository, TeamMemberRole, WithDefaultConfig, FindPrebuildsParams, PrebuildWithStatus, StartPrebuildResult, ClientHeaderFields } from '@gitpod/gitpod-protocol'; +import { AppInstallationDB, UserDB, UserMessageViewsDB, WorkspaceDB, DBWithTracing, TracedWorkspaceDB, DBGitpodToken, DBUser, UserStorageResourcesDB, TeamDB, InstallationAdminDB } from '@gitpod/gitpod-db/lib'; +import { AuthProviderEntry, AuthProviderInfo, CommitContext, Configuration, CreateWorkspaceMode, DisposableCollection, GetWorkspaceTimeoutResult, GitpodClient as GitpodApiClient, GitpodServer, GitpodToken, GitpodTokenType, InstallPluginsParams, PermissionName, PortVisibility, PrebuiltWorkspace, PrebuiltWorkspaceContext, PreparePluginUploadParams, ResolvedPlugins, ResolvePluginsParams, SetWorkspaceTimeoutResult, StartPrebuildContext, StartWorkspaceResult, Terms, Token, UninstallPluginParams, User, UserEnvVar, UserEnvVarValue, UserInfo, WhitelistedRepository, Workspace, WorkspaceContext, WorkspaceCreationResult, WorkspaceImageBuild, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstanceUser, WorkspaceTimeoutDuration, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo, TeamMembershipInvite, CreateProjectParams, Project, ProviderRepository, TeamMemberRole, WithDefaultConfig, FindPrebuildsParams, PrebuildWithStatus, StartPrebuildResult, ClientHeaderFields, Permission } from '@gitpod/gitpod-protocol'; import { AccountStatement } from "@gitpod/gitpod-protocol/lib/accounting-protocol"; import { AdminBlockUserRequest, AdminGetListRequest, AdminGetListResult, AdminGetWorkspacesRequest, AdminModifyPermanentWorkspaceFeatureFlagRequest, AdminModifyRoleOrPermissionRequest, WorkspaceAndInstance } from '@gitpod/gitpod-protocol/lib/admin-protocol'; import { GetLicenseInfoResult, LicenseFeature, LicenseValidationResult } from '@gitpod/gitpod-protocol/lib/license-protocol'; @@ -57,6 +57,7 @@ import { PartialProject } from '@gitpod/gitpod-protocol/src/teams-projects-proto import { ClientMetadata } from '../websocket/websocket-connection-manager'; import { ConfigurationService } from '../config/configuration-service'; import { ProjectEnvVar } from '@gitpod/gitpod-protocol/src/protocol'; +import { InstallationAdminSettings } from '@gitpod/gitpod-protocol'; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -77,6 +78,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { @inject(ContextParser) protected contextParser: ContextParser; @inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider; @inject(GitpodFileParser) protected readonly gitpodParser: GitpodFileParser; + @inject(InstallationAdminDB) protected readonly installationAdminDb: InstallationAdminDB; @inject(WorkspaceStarter) protected readonly workspaceStarter: WorkspaceStarter; @inject(WorkspaceManagerClientProvider) protected readonly workspaceManagerClientProvider: WorkspaceManagerClientProvider; @@ -456,7 +458,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { await this.guardAccess({ kind: "workspace", subject: workspace }, "get"); const latestInstance = await this.workspaceDb.trace(ctx).findCurrentInstance(workspaceId); - this.guardAccess({ kind: "workspaceInstance", subject: latestInstance, workspace}, "get"); + this.guardAccess({ kind: "workspaceInstance", subject: latestInstance, workspace }, "get"); const ownerToken = latestInstance?.status.ownerToken; if (!ownerToken) { @@ -564,6 +566,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { await this.internalStopWorkspaceInstance(ctx, instance.id, instance.region, policy); } + protected async guardAdminAccess(method: string, params: any, requiredPermission: PermissionName) { + const user = this.checkAndBlockUser(method); + if (!this.authorizationService.hasPermission(user, requiredPermission)) { + log.warn({ userId: this.user?.id }, "unauthorised admin access", { authorised: false, method, params }); + throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "not allowed"); + } + log.info({ userId: this.user?.id }, "admin access", { authorised: true, method, params }); + } + protected async internalStopWorkspaceInstance(ctx: TraceContext, instanceId: string, instanceRegion: string, policy?: StopWorkspacePolicy): Promise { const req = new StopWorkspaceRequest(); req.setId(instanceId); @@ -643,7 +654,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { includeHeadless: false, }); await Promise.all(res.map(ws => this.guardAccess({ kind: "workspace", subject: ws.workspace }, "get"))); - await Promise.all(res.map(ws => this.guardAccess({ kind: "workspaceInstance", subject: ws.latestInstance, workspace: ws.workspace}, "get"))); + await Promise.all(res.map(ws => this.guardAccess({ kind: "workspaceInstance", subject: ws.latestInstance, workspace: ws.workspace }, "get"))); return res; } @@ -1875,7 +1886,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } public async getGitpodTokenScopes(ctx: TraceContext, tokenHash: string): Promise { - traceAPIParams(ctx, { }); // do not trace tokenHash + traceAPIParams(ctx, {}); // do not trace tokenHash const user = this.checkAndBlockUser("getGitpodTokenScopes"); let token: GitpodToken | undefined; @@ -1893,7 +1904,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } public async deleteGitpodToken(ctx: TraceContext, tokenHash: string): Promise { - traceAPIParams(ctx, { }); // do not trace tokenHash + traceAPIParams(ctx, {}); // do not trace tokenHash const user = this.checkAndBlockUser("deleteGitpodToken"); const existingTokens = await this.getGitpodTokens(ctx); // all tokens for logged in user @@ -2006,6 +2017,32 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`); } + async adminGetSettings(ctx: TraceContext): Promise { + traceAPIParams(ctx, {}); + + await this.guardAdminAccess("adminGetSettings", {}, Permission.ADMIN_API); + + const settings = await this.installationAdminDb.getData(); + + return settings.settings; + } + + async adminUpdateSettings(ctx: TraceContext, settings: InstallationAdminSettings): Promise { + traceAPIParams(ctx, {}); + + await this.guardAdminAccess("adminUpdateSettings", {}, Permission.ADMIN_API); + + const newSettings: Partial = {}; + + for (const p of InstallationAdminSettings.fields()) { + if (p in settings) { + newSettings[p] = settings[p]; + } + } + + await this.installationAdminDb.setSettings(newSettings); + } + async getLicenseInfo(): Promise { throw new ResponseError(ErrorCodes.EE_FEATURE, `Licensing is implemented in Gitpod's Enterprise Edition`); } @@ -2062,7 +2099,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { protected validHostNameRegexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(\/([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$/; async updateOwnAuthProvider(ctx: TraceContext, { entry }: GitpodServer.UpdateOwnAuthProviderParams): Promise { - traceAPIParams(ctx, { }); // entry contains PII + traceAPIParams(ctx, {}); // entry contains PII let userId: string; try {