Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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'));
Copy link
Contributor

@gtsiolis gtsiolis Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue(non-blocking): Just wanted to add a comment that I could not access the admin panel even after trying to add a user as an admin using this documentation[1]. This is probably missing a section heading for the settings but I could not verify using the preview environment. However, shipping this as is for now sounds also ok. ➿

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about adding a user as admin. In my environment where my user is an admin by default, it shows ok. As you say, I think it's probably ok as there will always be the initial admin. We can always raise as a bugfix if a wider problem

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the first user always becomes an admin. That was more like an observation that I could not test this in the preview environment properly either because the script to make someone admin does not work or because we could improve our code review process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've raised PR #7840 with a fix for leeway

const OAuthClientApproval = React.lazy(() => import(/* webpackPrefetch: true */ './OauthClientApproval'));

function Loading() {
Expand Down Expand Up @@ -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<boolean>(true);
const [ isWhatsNewShown, setWhatsNewShown ] = useState(false);
const [ isSetupRequired, setSetupRequired ] = useState(false);
const [loading, setLoading] = useState<boolean>(true);
const [isWhatsNewShown, setWhatsNewShown] = useState(false);
const [isSetupRequired, setSetupRequired] = useState(false);
const history = useHistory();

useEffect(() => {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -279,6 +287,7 @@ function App() {

<Route path="/admin/users" component={UserSearch} />
<Route path="/admin/workspaces" component={WorkspacesSearch} />
<Route path="/admin/settings" component={AdminSettings} />

<Route path={["/", "/login"]} exact>
<Redirect to={workspacesPathMain} />
Expand Down
26 changes: 26 additions & 0 deletions components/dashboard/src/admin-context.tsx
Original file line number Diff line number Diff line change
@@ -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<InstallationAdminSettings>,
}>({
setAdminSettings: () => null,
});

const AdminContextProvider: React.FC = ({ children }) => {
const [adminSettings, setAdminSettings] = useState<InstallationAdminSettings>();
return (
<AdminContext.Provider value={{ adminSettings, setAdminSettings }}>
{children}
</AdminContext.Provider>
);
};

export { AdminContext, AdminContextProvider };
37 changes: 37 additions & 0 deletions components/dashboard/src/admin/Settings.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<PageWithSubMenu subMenu={adminMenu} title="Settings" subtitle="Configure settings for your Gitpod cluster.">
<h3>Usage Statistics</h3>
<CheckBox
title="Enable Service Ping"
desc={<span>This is used to provide insights on how you use your cluster so we can provide a better overall experience. <a className="gp-link" href="https://www.gitpod.io/privacy">Read our Privacy Policy</a></span>}
checked={adminSettings?.sendTelemetry ?? false}
onChange={(evt) => actuallySetTelemetryPrefs({
sendTelemetry: evt.target.checked,
})} />
</PageWithSubMenu>
</div >
)
}
3 changes: 3 additions & 0 deletions components/dashboard/src/admin/admin-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ export const adminMenu = [{
}, {
title: 'Workspaces',
link: ['/admin/workspaces']
}, {
title: 'Settings',
link: ['/admin/settings']
},];
21 changes: 12 additions & 9 deletions components/dashboard/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,15 +19,17 @@ import "./index.css"
ReactDOM.render(
<React.StrictMode>
<UserContextProvider>
<TeamsContextProvider>
<ProjectContextProvider>
<ThemeContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeContextProvider>
</ProjectContextProvider>
</TeamsContextProvider>
<AdminContextProvider>
<TeamsContextProvider>
<ProjectContextProvider>
<ThemeContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeContextProvider>
</ProjectContextProvider>
</TeamsContextProvider>
</AdminContextProvider>
</UserContextProvider>
</React.StrictMode>,
document.getElementById('root')
Expand Down
5 changes: 3 additions & 2 deletions components/gitpod-db/src/installation-admin-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<InstallationAdmin>;
getData(): Promise<InstallationAdmin>;
setSettings(settings: Partial<InstallationAdminSettings>): Promise<void>;
}
24 changes: 14 additions & 10 deletions components/gitpod-db/src/typeorm/installation-admin-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -21,12 +20,7 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
}

protected async createDefaultRecord(): Promise<InstallationAdmin> {
const record: InstallationAdmin = {
id: uuidv4(),
settings: {
sendTelemetry: false,
},
};
const record = InstallationAdmin.createDefault();

const repo = await this.getInstallationAdminRepo();
return repo.save(record);
Expand All @@ -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<InstallationAdmin>
*/
async getTelemetryData(): Promise<InstallationAdmin> {
async getData(): Promise<InstallationAdmin> {
const repo = await this.getInstallationAdminRepo();
const [record] = await repo.find();

Expand All @@ -55,4 +49,14 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
/* Record not found - create one */
return this.createDefaultRecord();
}

async setSettings(settings: InstallationAdminSettings): Promise<void> {
const record = await this.getData();
record.settings = {
...settings,
}

const repo = await this.getInstallationAdminRepo();
await repo.save(record);
}
}
10 changes: 7 additions & 3 deletions components/gitpod-protocol/src/admin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<User>): Promise<AdminGetListResult<User>>;
Expand All @@ -29,6 +30,9 @@ export interface AdminServer {
adminIsStudent(userId: string): Promise<boolean>;
adminAddStudentEmailDomain(userId: string, domain: string): Promise<void>;
adminGrantExtraHours(userId: string, extraHours: number): Promise<void>;

adminGetSettings(): Promise<InstallationAdminSettings>
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>
}

export interface AdminGetListRequest<T> {
Expand Down Expand Up @@ -65,7 +69,7 @@ export interface AdminModifyPermanentWorkspaceFeatureFlagRequest {
}[]
}

export interface WorkspaceAndInstance extends Omit<Workspace, "id"|"creationTime">, Omit<WorkspaceInstance, "id"|"creationTime"> {
export interface WorkspaceAndInstance extends Omit<Workspace, "id" | "creationTime">, Omit<WorkspaceInstance, "id" | "creationTime"> {
workspaceId: string;
workspaceCreationTime: string;
instanceId: string;
Expand All @@ -78,7 +82,7 @@ export namespace WorkspaceAndInstance {
return {
id: wai.workspaceId,
creationTime: wai.workspaceCreationTime,
... wai
...wai
};
}

Expand All @@ -89,7 +93,7 @@ export namespace WorkspaceAndInstance {
return {
id: wai.instanceId,
creationTime: wai.instanceCreationTime,
... wai
...wai
};
}
}
Expand Down
5 changes: 5 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -131,6 +132,10 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
resetGenericInvite(inviteId: string): Promise<TeamMembershipInvite>;
deleteTeam(teamId: string, userId: string): Promise<void>;

// Admin Settings
adminGetSettings(): Promise<InstallationAdminSettings>;
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>;

// Projects
getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise<ProviderRepository[]>;
createProject(params: CreateProjectParams): Promise<Project>;
Expand Down
27 changes: 24 additions & 3 deletions components/gitpod-protocol/src/installation-admin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

export namespace InstallationAdmin {
export function createDefault(): InstallationAdmin {
return {
id: uuidv4(),
settings: {
...InstallationAdminSettingsPrototype,
}
};
}
}
11 changes: 1 addition & 10 deletions components/server/ee/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<WorkspaceCreationResult | PrebuiltWorkspaceContext | undefined> {
const ctx = TraceContext.childContext("findPrebuiltWorkspace", parentCtx);

Expand Down
2 changes: 2 additions & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading