Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[public-api] Add AuthProviderService service #19008

Merged
merged 9 commits into from
Nov 13, 2023
136 changes: 136 additions & 0 deletions components/dashboard/src/service/json-rpc-authprovider-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* 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 { PartialMessage } from "@bufbuild/protobuf";
import { Code, ConnectError, PromiseClient } from "@connectrpc/connect";
import { AuthProviderService } from "@gitpod/public-api/lib/gitpod/v1/authprovider_connect";
import {
CreateAuthProviderRequest,
CreateAuthProviderResponse,
DeleteAuthProviderRequest,
DeleteAuthProviderResponse,
GetAuthProviderRequest,
GetAuthProviderResponse,
ListAuthProviderDescriptionsRequest,
ListAuthProviderDescriptionsResponse,
ListAuthProvidersRequest,
ListAuthProvidersResponse,
UpdateAuthProviderRequest,
UpdateAuthProviderResponse,
} from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import { converter } from "./public-api";
import { getGitpodService } from "./service";

export class JsonRpcAuthProviderClient implements PromiseClient<typeof AuthProviderService> {
async createAuthProvider(request: PartialMessage<CreateAuthProviderRequest>): Promise<CreateAuthProviderResponse> {
const ownerId = request.owner?.case === "ownerId" ? request.owner.value : undefined;
const organizationId = request.owner?.case === "organizationId" ? request.owner.value : undefined;

if (!organizationId && !ownerId) {
throw new ConnectError("organizationId or ownerId is required", Code.InvalidArgument);
}
if (!request.type) {
throw new ConnectError("type is required", Code.InvalidArgument);
}
if (!request.host) {
throw new ConnectError("host is required", Code.InvalidArgument);
}

if (organizationId) {
const result = await getGitpodService().server.createOrgAuthProvider({
entry: {
organizationId,
host: request.host,
type: converter.fromAuthProviderType(request.type),
clientId: request.oauth2Config?.clientId,
clientSecret: request.oauth2Config?.clientSecret,
},
});
return new CreateAuthProviderResponse({ authProvider: converter.toAuthProvider(result) });
}
if (ownerId) {
const result = await getGitpodService().server.updateOwnAuthProvider({
entry: {
host: request.host,
ownerId,
type: converter.fromAuthProviderType(request.type),
clientId: request.oauth2Config?.clientId,
clientSecret: request.oauth2Config?.clientSecret,
},
});
return new CreateAuthProviderResponse({ authProvider: converter.toAuthProvider(result) });
}

throw new ConnectError("organizationId or ownerId is required", Code.InvalidArgument);
}

async getAuthProvider(request: PartialMessage<GetAuthProviderRequest>): Promise<GetAuthProviderResponse> {
if (!request.authProviderId) {
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
}

const provider = await getGitpodService().server.getAuthProvider(request.authProviderId);
return new GetAuthProviderResponse({
authProvider: converter.toAuthProvider(provider),
});
}

async listAuthProviders(request: PartialMessage<ListAuthProvidersRequest>): Promise<ListAuthProvidersResponse> {
if (!request.id?.case) {
throw new ConnectError("id is required", Code.InvalidArgument);
}
const organizationId = request.id.case === "organizationId" ? request.id.value : undefined;
const userId = request.id.case === "userId" ? request.id.value : undefined;

if (!organizationId && !userId) {
throw new ConnectError("organizationId or userId is required", Code.InvalidArgument);
}

const authProviders = !!organizationId
? await getGitpodService().server.getOrgAuthProviders({
organizationId,
})
: await getGitpodService().server.getOwnAuthProviders();
const response = new ListAuthProvidersResponse({
authProviders: authProviders.map(converter.toAuthProvider),
});
return response;
}

async listAuthProviderDescriptions(
request: PartialMessage<ListAuthProviderDescriptionsRequest>,
): Promise<ListAuthProviderDescriptionsResponse> {
const aps = await getGitpodService().server.getAuthProviders();
return new ListAuthProviderDescriptionsResponse({
descriptions: aps.map((ap) => converter.toAuthProviderDescription(ap)),
});
}

async updateAuthProvider(request: PartialMessage<UpdateAuthProviderRequest>): Promise<UpdateAuthProviderResponse> {
if (!request.authProviderId) {
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
}
const clientId = request?.clientId;
const clientSecret = request?.clientSecret;
if (!clientId || !clientSecret) {
throw new ConnectError("clientId or clientSecret are required", Code.InvalidArgument);
}

await getGitpodService().server.updateAuthProvider(request.authProviderId, {
clientId,
clientSecret,
});
return new UpdateAuthProviderResponse();
}

async deleteAuthProvider(request: PartialMessage<DeleteAuthProviderRequest>): Promise<DeleteAuthProviderResponse> {
if (!request.authProviderId) {
throw new ConnectError("authProviderId is required", Code.InvalidArgument);
}
await getGitpodService().server.deleteAuthProvider(request.authProviderId);
return new DeleteAuthProviderResponse();
}
}
4 changes: 4 additions & 0 deletions components/dashboard/src/service/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { getMetricsInterceptor } from "@gitpod/public-api/lib/metrics";
import { getExperimentsClient } from "../experiments/client";
import { JsonRpcOrganizationClient } from "./json-rpc-organization-client";
import { JsonRpcWorkspaceClient } from "./json-rpc-workspace-client";
import { JsonRpcAuthProviderClient } from "./json-rpc-authprovider-client";
import { AuthProviderService } from "@gitpod/public-api/lib/gitpod/v1/authprovider_connect";

const transport = createConnectTransport({
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
Expand All @@ -49,6 +51,8 @@ export const organizationClient = createServiceClient(
// No jsonrcp client for the configuration service as it's only used in new UI of the dashboard
export const configurationClient = createServiceClient(ConfigurationService);

export const authProviderClient = createServiceClient(AuthProviderService, new JsonRpcAuthProviderClient());

export async function listAllProjects(opts: { orgId: string }): Promise<ProtocolProject[]> {
let pagination = {
page: 1,
Expand Down
126 changes: 102 additions & 24 deletions components/gitpod-protocol/src/public-api-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,26 @@
* See License.AGPL.txt in the project root for license information.
*/

import { Code, ConnectError } from "@connectrpc/connect";
import { Timestamp } from "@bufbuild/protobuf";
import { Code, ConnectError } from "@connectrpc/connect";
import {
AuthProvider,
AuthProviderDescription,
AuthProviderType,
OAuth2Config,
} from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";
import {
BranchMatchingStrategy,
Configuration,
PrebuildSettings,
WorkspaceSettings,
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import {
Organization,
OrganizationMember,
OrganizationRole,
OrganizationSettings,
} from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import {
AdmissionLevel,
EditorReference,
Expand All @@ -20,45 +38,35 @@ import {
WorkspacePort_Protocol,
WorkspaceStatus,
} from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";
import {
Organization,
OrganizationMember,
OrganizationRole,
OrganizationSettings,
} from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import {
BranchMatchingStrategy,
Configuration,
PrebuildSettings,
WorkspaceSettings,
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { ContextURL } from "./context-url";
import { ApplicationError, ErrorCode, ErrorCodes } from "./messaging/error";
import {
AuthProviderEntry as AuthProviderProtocol,
AuthProviderInfo,
CommitContext,
EnvVarWithValue,
Workspace as ProtocolWorkspace,
WithEnvvarsContext,
WithPrebuild,
WorkspaceContext,
WorkspaceInfo,
Workspace as ProtocolWorkspace,
} from "./protocol";
import {
OrgMemberInfo,
OrgMemberRole,
OrganizationSettings as OrganizationSettingsProtocol,
PrebuildSettings as PrebuildSettingsProtocol,
Project,
Organization as ProtocolOrganization,
} from "./teams-projects-protocol";
import { TrustedValue } from "./util/scrubbing";
import {
ConfigurationIdeConfig,
PortProtocol,
WorkspaceInstance,
WorkspaceInstanceConditions,
WorkspaceInstancePort,
} from "./workspace-instance";
import { ContextURL } from "./context-url";
import { TrustedValue } from "./util/scrubbing";
import {
Organization as ProtocolOrganization,
OrgMemberInfo,
OrgMemberRole,
OrganizationSettings as OrganizationSettingsProtocol,
Project,
PrebuildSettings as PrebuildSettingsProtocol,
} from "./teams-projects-protocol";

const applicationErrorCode = "application-error-code";
const applicationErrorData = "application-error-data";
Expand Down Expand Up @@ -427,4 +435,74 @@ export class PublicAPIConverter {
}
return result;
}

toAuthProviderDescription(ap: AuthProviderInfo): AuthProviderDescription {
AlexTugarev marked this conversation as resolved.
Show resolved Hide resolved
const result = new AuthProviderDescription({
id: ap.authProviderId,
host: ap.host,
type: this.toAuthProviderType(ap.authProviderType),
});
return result;
}

toAuthProvider(ap: AuthProviderProtocol): AuthProvider {
const result = new AuthProvider({
id: ap.id,
host: ap.host,
type: this.toAuthProviderType(ap.type),
verified: ap.status === "verified",
settingsUrl: ap.oauth?.settingsUrl,
scopes: ap.oauth?.scope?.split(ap.oauth?.scopeSeparator || " ") || [],
});
if (ap.organizationId) {
result.owner = {
case: "organizationId",
value: ap.organizationId,
};
} else {
result.owner = {
case: "ownerId",
value: ap.ownerId,
};
}
result.oauth2Config = this.toOAuth2Config(ap);
return result;
}

toOAuth2Config(ap: AuthProviderProtocol): OAuth2Config {
return new OAuth2Config({
clientId: ap.oauth?.clientId,
clientSecret: ap.oauth?.clientSecret,
});
}

toAuthProviderType(type: string): AuthProviderType {
switch (type) {
case "GitHub":
return AuthProviderType.GITHUB;
case "GitLab":
return AuthProviderType.GITLAB;
case "Bitbucket":
return AuthProviderType.BITBUCKET;
case "BitbucketServer":
return AuthProviderType.BITBUCKET_SERVER;
default:
return AuthProviderType.UNSPECIFIED; // not allowed
}
}

fromAuthProviderType(type: AuthProviderType): string {
switch (type) {
case AuthProviderType.GITHUB:
return "GitHub";
case AuthProviderType.GITLAB:
return "GitLab";
case AuthProviderType.BITBUCKET:
return "Bitbucket";
case AuthProviderType.BITBUCKET_SERVER:
return "BitbucketServer";
default:
return ""; // not allowed
}
}
}
Loading
Loading