From 448410964785ad21ff20f3cfc8c5ada611746809 Mon Sep 17 00:00:00 2001 From: Milan Pavlik Date: Mon, 20 Nov 2023 14:07:44 +0000 Subject: [PATCH] fix --- .../gitpod-protocol/go/gitpod-service.go | 21 --- components/gitpod-protocol/go/mock.go | 15 -- components/gitpod-protocol/src/analytics.ts | 4 +- .../gitpod-protocol/src/auth-providers.ts | 131 ++++++++++++++++++ .../gitpod-protocol/src/gitpod-service.ts | 4 +- components/gitpod-protocol/src/index.ts | 1 + .../gitpod-protocol/src/messaging/error.ts | 3 + .../src/public-api-converter.spec.ts | 1 + .../src/public-api-converter.ts | 4 + .../src/public-api-pagination.spec.ts | 56 ++++++++ .../src/public-api-pagination.ts | 37 +++++ .../gitpod-protocol/src/util/logging.ts | 11 +- 12 files changed, 238 insertions(+), 50 deletions(-) create mode 100644 components/gitpod-protocol/src/auth-providers.ts create mode 100644 components/gitpod-protocol/src/public-api-pagination.spec.ts create mode 100644 components/gitpod-protocol/src/public-api-pagination.ts diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index e33b2effb86113..e29fd81de497ca 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -42,7 +42,6 @@ type APIInterface interface { GetWorkspaceOwner(ctx context.Context, workspaceID string) (res *UserInfo, err error) GetWorkspaceUsers(ctx context.Context, workspaceID string) (res []*WorkspaceInstanceUser, err error) GetFeaturedRepositories(ctx context.Context) (res []*WhitelistedRepository, err error) - GetSuggestedContextURLs(ctx context.Context) (res []*string, err error) GetWorkspace(ctx context.Context, id string) (res *WorkspaceInfo, err error) GetIDEOptions(ctx context.Context) (res *IDEOptions, err error) IsWorkspaceOwner(ctx context.Context, workspaceID string) (res bool, err error) @@ -147,8 +146,6 @@ const ( FunctionGetWorkspaceUsers FunctionName = "getWorkspaceUsers" // FunctionGetFeaturedRepositories is the name of the getFeaturedRepositories function FunctionGetFeaturedRepositories FunctionName = "getFeaturedRepositories" - // FunctionGetSuggestedContextURLs is the name of the getSuggestedContextURLs function - FunctionGetSuggestedContextURLs FunctionName = "getSuggestedContextURLs" // FunctionGetWorkspace is the name of the getWorkspace function FunctionGetWorkspace FunctionName = "getWorkspace" // FunctionGetIDEOptions is the name of the getIDEOptions function @@ -1057,24 +1054,6 @@ func (gp *APIoverJSONRPC) ClosePort(ctx context.Context, workspaceID string, por return } -// GetSuggestedContextURLs calls getSuggestedContextURLs on the server -func (gp *APIoverJSONRPC) GetSuggestedContextURLs(ctx context.Context) (res []*string, err error) { - if gp == nil { - err = errNotConnected - return - } - var _params []interface{} - - var result []*string - err = gp.C.Call(ctx, "getSuggestedContextURLs", _params, &result) - if err != nil { - return - } - res = result - - return -} - // UpdateGitStatus calls UpdateGitStatus on the server func (gp *APIoverJSONRPC) UpdateGitStatus(ctx context.Context, workspaceID string, status *WorkspaceInstanceRepoStatus) (err error) { if gp == nil { diff --git a/components/gitpod-protocol/go/mock.go b/components/gitpod-protocol/go/mock.go index dd907add3a580b..84276f8097d068 100644 --- a/components/gitpod-protocol/go/mock.go +++ b/components/gitpod-protocol/go/mock.go @@ -521,21 +521,6 @@ func (mr *MockAPIInterfaceMockRecorder) GetSnapshots(ctx, workspaceID interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSnapshots", reflect.TypeOf((*MockAPIInterface)(nil).GetSnapshots), ctx, workspaceID) } -// GetSuggestedContextURLs mocks base method. -func (m *MockAPIInterface) GetSuggestedContextURLs(ctx context.Context) ([]*string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSuggestedContextURLs", ctx) - ret0, _ := ret[0].([]*string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSuggestedContextURLs indicates an expected call of GetSuggestedContextURLs. -func (mr *MockAPIInterfaceMockRecorder) GetSuggestedContextURLs(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSuggestedContextURLs", reflect.TypeOf((*MockAPIInterface)(nil).GetSuggestedContextURLs), ctx) -} - // GetSupportedWorkspaceClasses mocks base method. func (m *MockAPIInterface) GetSupportedWorkspaceClasses(ctx context.Context) ([]*SupportedWorkspaceClass, error) { m.ctrl.T.Helper() diff --git a/components/gitpod-protocol/src/analytics.ts b/components/gitpod-protocol/src/analytics.ts index 89cf67038f8814..12a6b9fc48426d 100644 --- a/components/gitpod-protocol/src/analytics.ts +++ b/components/gitpod-protocol/src/analytics.ts @@ -6,9 +6,7 @@ export const IAnalyticsWriter = Symbol("IAnalyticsWriter"); -type Identity = - | { userId: string | number; anonymousId?: string | number } - | { userId?: string | number; anonymousId: string | number }; +type Identity = { userId?: string | number; anonymousId?: string | number; subjectId?: string }; interface Message { messageId?: string; diff --git a/components/gitpod-protocol/src/auth-providers.ts b/components/gitpod-protocol/src/auth-providers.ts new file mode 100644 index 00000000000000..ce2baa69f649c0 --- /dev/null +++ b/components/gitpod-protocol/src/auth-providers.ts @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2023 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 { AuthProviderType } from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb"; + +export namespace GitLabScope { + export const READ_USER = "read_user"; + export const API = "api"; + export const READ_REPO = "read_repository"; + + export const ALL = [READ_USER, API, READ_REPO]; + /** + * Minimal required permission. + * GitLab API usage requires the permission of a user. + */ + export const DEFAULT = [READ_USER, API]; + export const REPO = [API, READ_REPO]; +} + +export namespace GitHubScope { + export const EMAIL = "user:email"; + export const READ_USER = "read:user"; + export const PUBLIC = "public_repo"; + export const PRIVATE = "repo"; + export const ORGS = "read:org"; + export const WORKFLOW = "workflow"; + + export const ALL = [EMAIL, READ_USER, PUBLIC, PRIVATE, ORGS, WORKFLOW]; + export const DEFAULT = ALL; + export const PUBLIC_REPO = ALL; + export const PRIVATE_REPO = ALL; +} + +export namespace BitbucketOAuthScopes { + // https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html + + /** Read user info like name, e-mail adresses etc. */ + export const ACCOUNT_READ = "account"; + /** Access repo info, clone repo over https, read and write issues */ + export const REPOSITORY_READ = "repository"; + /** Push over https, fork repo */ + export const REPOSITORY_WRITE = "repository:write"; + /** Lists and read pull requests */ + export const PULL_REQUEST_READ = "pullrequest"; + /** Create, comment and merge pull requests */ + export const PULL_REQUEST_WRITE = "pullrequest:write"; + /** Create, list web hooks */ + export const WEBHOOK = "webhook"; + + export const ALL = [ + ACCOUNT_READ, + REPOSITORY_READ, + REPOSITORY_WRITE, + PULL_REQUEST_READ, + PULL_REQUEST_WRITE, + WEBHOOK, + ]; + + export const DEFAULT = ALL; +} + +export namespace BitbucketServerOAuthScopes { + // https://confluence.atlassian.com/bitbucketserver/bitbucket-oauth-2-0-provider-api-1108483661.html#BitbucketOAuth2.0providerAPI-scopesScopes + + /** View projects and repositories that are publicly accessible, including pulling code and cloning repositories. */ + export const PUBLIC_REPOS = "PUBLIC_REPOS"; + /** View projects and repositories the user account can view, including pulling code, cloning, and forking repositories. Create and comment on pull requests. */ + export const REPO_READ = "REPO_READ"; + /** Push over https, fork repo */ + export const REPO_WRITE = "REPO_WRITE"; + + export const REPO_ADMIN = "REPO_ADMIN"; + export const PROJECT_ADMIN = "PROJECT_ADMIN"; + + export const ALL = [PUBLIC_REPOS, REPO_READ, REPO_WRITE, REPO_ADMIN, PROJECT_ADMIN]; + + export const DEFAULT = ALL; +} + +export function getScopesForAuthProviderType(type: AuthProviderType | string) { + switch (type) { + case AuthProviderType.GITHUB: + case "GitHub": + return GitHubScope.ALL; + case AuthProviderType.GITLAB: + case "GitLab": + return GitLabScope.ALL; + case AuthProviderType.BITBUCKET: + case "Bitbucket": + return BitbucketOAuthScopes.ALL; + case AuthProviderType.BITBUCKET_SERVER: + case "BitbucketServer": + return BitbucketServerOAuthScopes.ALL; + } +} + +export function getRequiredScopes(type: AuthProviderType | string) { + switch (type) { + case AuthProviderType.GITHUB: + case "GitHub": + return { + default: GitHubScope.DEFAULT, + publicRepo: GitHubScope.PUBLIC_REPO, + privateRepo: GitHubScope.PRIVATE_REPO, + }; + case AuthProviderType.GITLAB: + case "GitLab": + return { + default: GitLabScope.DEFAULT, + publicRepo: GitLabScope.DEFAULT, + privateRepo: GitLabScope.REPO, + }; + case AuthProviderType.BITBUCKET: + case "Bitbucket": + return { + default: BitbucketOAuthScopes.DEFAULT, + publicRepo: BitbucketOAuthScopes.DEFAULT, + privateRepo: BitbucketOAuthScopes.DEFAULT, + }; + case AuthProviderType.BITBUCKET_SERVER: + case "BitbucketServer": + return { + default: BitbucketServerOAuthScopes.DEFAULT, + publicRepo: BitbucketServerOAuthScopes.DEFAULT, + privateRepo: BitbucketServerOAuthScopes.DEFAULT, + }; + } +} diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 6705b60a63f6fd..6131975aec7eb0 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -110,7 +110,6 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, getWorkspaceOwner(workspaceId: string): Promise; getWorkspaceUsers(workspaceId: string): Promise; getFeaturedRepositories(): Promise; - getSuggestedContextURLs(): Promise; getSuggestedRepositories(organizationId: string): Promise; searchRepositories(params: SearchRepositoriesParams): Promise; /** @@ -188,6 +187,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, getOnboardingState(): Promise; // Projects + /** @deprecated no-op */ getProviderRepositoriesForUser( params: GetProviderRepositoriesParams, cancellationToken?: CancellationToken, @@ -212,7 +212,9 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, deleteGitpodToken(tokenHash: string): Promise; // misc + /** @deprecated always returns false */ isGitHubAppEnabled(): Promise; + /** @deprecated this is a no-op */ registerGithubApp(installationId: string): Promise; /** diff --git a/components/gitpod-protocol/src/index.ts b/components/gitpod-protocol/src/index.ts index 6c6edaa2af2589..f46bb9687720fb 100644 --- a/components/gitpod-protocol/src/index.ts +++ b/components/gitpod-protocol/src/index.ts @@ -18,3 +18,4 @@ export * from "./teams-projects-protocol"; export * from "./snapshot-url"; export * from "./webhook-event"; export * from "./redis"; +export * from "./auth-providers"; diff --git a/components/gitpod-protocol/src/messaging/error.ts b/components/gitpod-protocol/src/messaging/error.ts index 52f5bbef4b7d17..614f5b985d9346 100644 --- a/components/gitpod-protocol/src/messaging/error.ts +++ b/components/gitpod-protocol/src/messaging/error.ts @@ -65,6 +65,9 @@ export const ErrorCodes = { // 404 Not Found NOT_FOUND: 404 as const, + // 408 Request Timeout + REQUEST_TIMEOUT: 408 as const, + // 409 Conflict (e.g. already existing) CONFLICT: 409 as const, diff --git a/components/gitpod-protocol/src/public-api-converter.spec.ts b/components/gitpod-protocol/src/public-api-converter.spec.ts index fc1a52d1b6335c..5cf57f242391e7 100644 --- a/components/gitpod-protocol/src/public-api-converter.spec.ts +++ b/components/gitpod-protocol/src/public-api-converter.spec.ts @@ -666,6 +666,7 @@ describe("PublicAPIConverter", () => { expect(result.organizationId).to.equal(project.teamId); expect(result.name).to.equal(project.name); expect(result.cloneUrl).to.equal(project.cloneUrl); + expect(result.creationTime).to.deep.equal(Timestamp.fromDate(new Date(project.creationTime))); expect(result.workspaceSettings).to.deep.equal( new WorkspaceSettings({ workspaceClass: project.settings?.workspaceClasses?.regular, diff --git a/components/gitpod-protocol/src/public-api-converter.ts b/components/gitpod-protocol/src/public-api-converter.ts index b205614b8bfa64..512208c3c48d65 100644 --- a/components/gitpod-protocol/src/public-api-converter.ts +++ b/components/gitpod-protocol/src/public-api-converter.ts @@ -201,6 +201,9 @@ export class PublicAPIConverter { if (reason.code === ErrorCodes.INTERNAL_SERVER_ERROR) { return new ConnectError(reason.message, Code.Internal, metadata, undefined, reason); } + if (reason.code === ErrorCodes.REQUEST_TIMEOUT) { + return new ConnectError(reason.message, Code.Canceled, metadata, undefined, reason); + } return new ConnectError(reason.message, Code.InvalidArgument, metadata, undefined, reason); } return ConnectError.from(reason, Code.Internal); @@ -403,6 +406,7 @@ export class PublicAPIConverter { result.organizationId = project.teamId; result.name = project.name; result.cloneUrl = project.cloneUrl; + result.creationTime = Timestamp.fromDate(new Date(project.creationTime)); result.workspaceSettings = this.toWorkspaceSettings(project.settings?.workspaceClasses?.regular); result.prebuildSettings = this.toPrebuildSettings(project.settings?.prebuilds); return result; diff --git a/components/gitpod-protocol/src/public-api-pagination.spec.ts b/components/gitpod-protocol/src/public-api-pagination.spec.ts new file mode 100644 index 00000000000000..208b3ab48cd14d --- /dev/null +++ b/components/gitpod-protocol/src/public-api-pagination.spec.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/** + * Copyright (c) 2023 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 { expect } from "chai"; +import { parsePagination } from "./public-api-pagination"; + +describe("PublicAPIConverter", () => { + describe("parsePagination", () => { + it("Happy path", () => { + // first path is { page: 0 } + const result = parsePagination({ page: 1, pageSize: 50 }, 50); + expect(result.limit).to.equal(50); + expect(result.offset).to.equal(50); + }); + + it("Default is more than max, limit to max", () => { + const result = parsePagination({ page: 2 }, 5000); + expect(result.limit).to.equal(100); // MAX_PAGE_SIZE + expect(result.offset).to.equal(200); + }); + + it("All undefined", () => { + const result = parsePagination({}, 20); + expect(result.limit).to.equal(20); + expect(result.offset).to.equal(0); + }); + + it("All undefined, default undefined, go to default 50", () => { + const result = parsePagination({}); + expect(result.limit).to.equal(50); // DEFAULT_PAGE_SIZE + expect(result.offset).to.equal(0); + }); + + it("Page less than zero, should go to zero", () => { + const result = parsePagination({ page: -100, pageSize: 50 }); + expect(result.limit).to.equal(50); + expect(result.offset).to.equal(0); + }); + + it("Not integer page to zero", () => { + const result = parsePagination({ page: 0.1, pageSize: 20 }); + expect(result.limit).to.equal(20); + expect(result.offset).to.equal(0); + }); + + it("Not integer pageSize to default", () => { + const result = parsePagination({ page: 1, pageSize: 0.1 }); + expect(result.limit).to.equal(50); + expect(result.offset).to.equal(50); + }); + }); +}); diff --git a/components/gitpod-protocol/src/public-api-pagination.ts b/components/gitpod-protocol/src/public-api-pagination.ts new file mode 100644 index 00000000000000..7bdbd380d719bf --- /dev/null +++ b/components/gitpod-protocol/src/public-api-pagination.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2023 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 { PaginationRequest } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb"; + +export interface ParsedPagination { + offset: number; + limit: number; +} + +const MAX_PAGE_SIZE = 100; +const DEFAULT_PAGE_SIZE = 50; +export function parsePagination( + pagination: Partial | undefined, + defaultPageSize = DEFAULT_PAGE_SIZE, +): ParsedPagination { + let pageSize = pagination?.pageSize ?? defaultPageSize; + if (!Number.isInteger(pageSize)) { + pageSize = defaultPageSize; + } + if (pageSize < 0) { + pageSize = defaultPageSize; + } else if (pageSize > MAX_PAGE_SIZE) { + pageSize = MAX_PAGE_SIZE; + } + let page = pagination?.page ?? 0; + if (!Number.isInteger(page) || (page ?? 0) < 0) { + page = 0; + } + return { + offset: page * pageSize, + limit: pageSize, + }; +} diff --git a/components/gitpod-protocol/src/util/logging.ts b/components/gitpod-protocol/src/util/logging.ts index 662ae946fb15ea..3ed477f1bc3a92 100644 --- a/components/gitpod-protocol/src/util/logging.ts +++ b/components/gitpod-protocol/src/util/logging.ts @@ -5,7 +5,6 @@ */ import { scrubber } from "./scrubbing"; -import * as prometheusClient from "prom-client"; const inspect: (object: unknown) => string = require("util").inspect; // undefined in frontend @@ -18,6 +17,7 @@ export interface LogContext { organizationId?: string; sessionId?: string; userId?: string; + subjectId?: string; workspaceId?: string; instanceId?: string; } @@ -247,16 +247,7 @@ namespace GoogleLogSeverity { }; } -const logsCounter = new prometheusClient.Counter({ - name: "gitpod_logs_total", - help: "Total number of logs by level", - labelNames: ["level"], - registers: [prometheusClient.register], -}); - function doLog(calledViaConsole: boolean, consoleLog: ConsoleLog, severity: GoogleLogSeverity, args: unknown[]): void { - logsCounter.labels(severity).inc(); - if (!jsonLogging) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument consoleLog(...args);