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
138 changes: 138 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,138 @@
/**
* 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);
}

const entry = await getGitpodService().server.updateAuthProvider(request.authProviderId, {
clientId,
clientSecret,
});
return new UpdateAuthProviderResponse({
authProvider: converter.toAuthProvider(entry),
});
}

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
82 changes: 82 additions & 0 deletions components/gitpod-protocol/src/public-api-converter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import {
PrebuildSettings,
WorkspaceSettings,
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
import { AuthProviderEntry, AuthProviderInfo } from "./protocol";
import {
AuthProvider,
AuthProviderDescription,
AuthProviderType,
} from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb";

describe("PublicAPIConverter", () => {
const converter = new PublicAPIConverter();
Expand Down Expand Up @@ -721,4 +727,80 @@ describe("PublicAPIConverter", () => {
expect(result).to.deep.equal(new WorkspaceSettings());
});
});

describe("toAuthProviderDescription", () => {
const info: AuthProviderInfo = {
authProviderId: "ap123",
authProviderType: "GitHub",
host: "localhost",
verified: true,
icon: "unused icon",
description: "unused description",
settingsUrl: "unused",
ownerId: "unused",
organizationId: "unused",
};
const description = new AuthProviderDescription({
id: info.authProviderId,
type: AuthProviderType.GITHUB,
host: info.host,
icon: info.icon,
description: info.description,
});
it("should convert an auth provider info to a description", () => {
const result = converter.toAuthProviderDescription(info);
expect(result).to.deep.equal(description);
});
});

describe("toAuthProvider", () => {
const entry: AuthProviderEntry = {
id: "ap123",
type: "GitHub",
host: "localhost",
status: "pending",
ownerId: "userId",
organizationId: "orgId123",
oauth: {
clientId: "clientId123",
clientSecret: "should not appear in result",
callBackUrl: "localhost/callback",
authorizationUrl: "auth.service/authorize",
tokenUrl: "auth.service/token",
},
};
const provider = new AuthProvider({
id: entry.id,
type: AuthProviderType.GITHUB,
host: entry.host,
oauth2Config: {
clientId: entry.oauth?.clientId,
clientSecret: entry.oauth?.clientSecret,
},
owner: {
case: "organizationId",
value: entry.organizationId!,
},
});
it("should convert an auth provider", () => {
const result = converter.toAuthProvider(entry);
expect(result).to.deep.equal(provider);
});
});

describe("toAuthProviderType", () => {
const mapping: { [key: string]: number } = {
GitHub: AuthProviderType.GITHUB,
GitLab: AuthProviderType.GITLAB,
Bitbucket: AuthProviderType.BITBUCKET,
BitbucketServer: AuthProviderType.BITBUCKET_SERVER,
Other: AuthProviderType.UNSPECIFIED,
};
it("should convert auth provider types", () => {
for (const k of Object.getOwnPropertyNames(mapping)) {
const result = converter.toAuthProviderType(k);
expect(result).to.deep.equal(mapping[k]);
}
});
});
});
Loading
Loading