|
| 1 | +/** |
| 2 | + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. |
| 3 | + * Licensed under the GNU Affero General Public License (AGPL). |
| 4 | + * See License.AGPL.txt in the project root for license information. |
| 5 | + */ |
| 6 | + |
| 7 | +import { Code, ConnectError, HandlerContext, ServiceImpl } from "@connectrpc/connect"; |
| 8 | +import { AuthProviderService as AuthProviderServiceInterface } from "@gitpod/public-api/lib/gitpod/v1/authprovider_connect"; |
| 9 | +import { inject, injectable } from "inversify"; |
| 10 | +import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter"; |
| 11 | +import { |
| 12 | + CreateAuthProviderRequest, |
| 13 | + CreateAuthProviderResponse, |
| 14 | + GetAuthProviderRequest, |
| 15 | + GetAuthProviderResponse, |
| 16 | + ListAuthProvidersRequest, |
| 17 | + ListAuthProvidersResponse, |
| 18 | + ListAuthProviderDescriptionsRequest, |
| 19 | + ListAuthProviderDescriptionsResponse, |
| 20 | + UpdateAuthProviderRequest, |
| 21 | + UpdateAuthProviderResponse, |
| 22 | + DeleteAuthProviderRequest, |
| 23 | + DeleteAuthProviderResponse, |
| 24 | +} from "@gitpod/public-api/lib/gitpod/v1/authprovider_pb"; |
| 25 | +import { AuthProviderService } from "../auth/auth-provider-service"; |
| 26 | +import { AuthProviderEntry, AuthProviderInfo } from "@gitpod/gitpod-protocol"; |
| 27 | +import { Unauthenticated } from "./unauthenticated"; |
| 28 | + |
| 29 | +@injectable() |
| 30 | +export class AuthProviderServiceAPI implements ServiceImpl<typeof AuthProviderServiceInterface> { |
| 31 | + constructor( |
| 32 | + @inject(PublicAPIConverter) private readonly apiConverter: PublicAPIConverter, |
| 33 | + @inject(AuthProviderService) private readonly authProviderService: AuthProviderService, |
| 34 | + ) {} |
| 35 | + |
| 36 | + async createAuthProvider( |
| 37 | + request: CreateAuthProviderRequest, |
| 38 | + context: HandlerContext, |
| 39 | + ): Promise<CreateAuthProviderResponse> { |
| 40 | + const ownerId = request.owner.case === "ownerId" ? request.owner.value : undefined; |
| 41 | + const organizationId = request.owner.case === "organizationId" ? request.owner.value : undefined; |
| 42 | + |
| 43 | + if (!organizationId && !ownerId) { |
| 44 | + throw new ConnectError("organizationId or ownerId is required", Code.InvalidArgument); |
| 45 | + } |
| 46 | + |
| 47 | + if (organizationId) { |
| 48 | + const result = await this.authProviderService.createOrgAuthProvider(context.user.id, { |
| 49 | + organizationId, |
| 50 | + host: request.host, |
| 51 | + ownerId: context.user.id, |
| 52 | + type: this.apiConverter.fromAuthProviderType(request.type), |
| 53 | + clientId: request.oauth2Config?.clientId, |
| 54 | + clientSecret: request.oauth2Config?.clientSecret, |
| 55 | + }); |
| 56 | + |
| 57 | + return new CreateAuthProviderResponse({ authProvider: this.apiConverter.toAuthProvider(result) }); |
| 58 | + } else { |
| 59 | + const result = await this.authProviderService.createAuthProviderOfUser(context.user.id, { |
| 60 | + host: request.host, |
| 61 | + ownerId: context.user.id, |
| 62 | + type: this.apiConverter.fromAuthProviderType(request.type), |
| 63 | + clientId: request.oauth2Config?.clientId, |
| 64 | + clientSecret: request.oauth2Config?.clientSecret, |
| 65 | + }); |
| 66 | + |
| 67 | + return new CreateAuthProviderResponse({ authProvider: this.apiConverter.toAuthProvider(result) }); |
| 68 | + } |
| 69 | + } |
| 70 | + async getAuthProvider(request: GetAuthProviderRequest, context: HandlerContext): Promise<GetAuthProviderResponse> { |
| 71 | + if (!request.authProviderId) { |
| 72 | + throw new ConnectError("authProviderId is required", Code.InvalidArgument); |
| 73 | + } |
| 74 | + |
| 75 | + const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId); |
| 76 | + if (!authProvider) { |
| 77 | + throw new ConnectError("Provider not found.", Code.NotFound); |
| 78 | + } |
| 79 | + |
| 80 | + return new GetAuthProviderResponse({ |
| 81 | + authProvider: this.apiConverter.toAuthProvider(authProvider), |
| 82 | + }); |
| 83 | + } |
| 84 | + |
| 85 | + async listAuthProviders( |
| 86 | + request: ListAuthProvidersRequest, |
| 87 | + context: HandlerContext, |
| 88 | + ): Promise<ListAuthProvidersResponse> { |
| 89 | + const target = request.id; |
| 90 | + const ownerId = target.case === "userId" ? target.value : undefined; |
| 91 | + const organizationId = target.case === "organizationId" ? target.value : undefined; |
| 92 | + |
| 93 | + if (!organizationId && !ownerId) { |
| 94 | + throw new ConnectError("organizationId or ownerId is required", Code.InvalidArgument); |
| 95 | + } |
| 96 | + |
| 97 | + const authProviders = organizationId |
| 98 | + ? await this.authProviderService.getAuthProvidersOfOrg(context.user.id, organizationId) |
| 99 | + : await this.authProviderService.getAuthProvidersOfUser(context.user.id); |
| 100 | + |
| 101 | + const redacted = authProviders.map(AuthProviderEntry.redact.bind(AuthProviderEntry)); |
| 102 | + |
| 103 | + const result = new ListAuthProvidersResponse({ |
| 104 | + authProviders: redacted.map((ap) => this.apiConverter.toAuthProvider(ap)), |
| 105 | + }); |
| 106 | + return result; |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Listing descriptions of auth providers doesn't require authentication. |
| 111 | + */ |
| 112 | + @Unauthenticated() |
| 113 | + async listAuthProviderDescriptions( |
| 114 | + _request: ListAuthProviderDescriptionsRequest, |
| 115 | + context: HandlerContext, |
| 116 | + ): Promise<ListAuthProviderDescriptionsResponse> { |
| 117 | + const user = context.user; |
| 118 | + const aps = user |
| 119 | + ? await this.authProviderService.getAuthProviderDescriptions(user) |
| 120 | + : await this.authProviderService.getAuthProviderDescriptionsUnauthenticated(); |
| 121 | + |
| 122 | + return new ListAuthProviderDescriptionsResponse({ |
| 123 | + descriptions: aps.map((ap: AuthProviderInfo) => this.apiConverter.toAuthProviderDescription(ap)), |
| 124 | + }); |
| 125 | + } |
| 126 | + |
| 127 | + async updateAuthProvider( |
| 128 | + request: UpdateAuthProviderRequest, |
| 129 | + context: HandlerContext, |
| 130 | + ): Promise<UpdateAuthProviderResponse> { |
| 131 | + if (!request.authProviderId) { |
| 132 | + throw new ConnectError("authProviderId is required", Code.InvalidArgument); |
| 133 | + } |
| 134 | + const clientId = request.clientId; |
| 135 | + const clientSecret = request.clientSecret; |
| 136 | + if (!clientId || typeof clientSecret === "undefined") { |
| 137 | + throw new ConnectError("clientId or clientSecret are required", Code.InvalidArgument); |
| 138 | + } |
| 139 | + |
| 140 | + const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId); |
| 141 | + if (!authProvider) { |
| 142 | + throw new ConnectError("Provider not found.", Code.NotFound); |
| 143 | + } |
| 144 | + |
| 145 | + if (authProvider.organizationId) { |
| 146 | + await this.authProviderService.updateOrgAuthProvider(context.user.id, { |
| 147 | + id: request.authProviderId, |
| 148 | + organizationId: authProvider.organizationId, |
| 149 | + clientId: clientId, |
| 150 | + clientSecret: clientSecret, |
| 151 | + }); |
| 152 | + } else { |
| 153 | + await this.authProviderService.updateAuthProviderOfUser(context.user.id, { |
| 154 | + id: request.authProviderId, |
| 155 | + ownerId: context.user.id, |
| 156 | + clientId: clientId, |
| 157 | + clientSecret: clientSecret, |
| 158 | + }); |
| 159 | + } |
| 160 | + |
| 161 | + return new UpdateAuthProviderResponse(); |
| 162 | + } |
| 163 | + |
| 164 | + async deleteAuthProvider( |
| 165 | + request: DeleteAuthProviderRequest, |
| 166 | + context: HandlerContext, |
| 167 | + ): Promise<DeleteAuthProviderResponse> { |
| 168 | + if (!request.authProviderId) { |
| 169 | + throw new ConnectError("authProviderId is required", Code.InvalidArgument); |
| 170 | + } |
| 171 | + |
| 172 | + const authProvider = await this.authProviderService.getAuthProvider(context.user.id, request.authProviderId); |
| 173 | + if (!authProvider) { |
| 174 | + throw new ConnectError("Provider not found.", Code.NotFound); |
| 175 | + } |
| 176 | + |
| 177 | + if (authProvider.organizationId) { |
| 178 | + await this.authProviderService.deleteAuthProviderOfOrg( |
| 179 | + context.user.id, |
| 180 | + authProvider.organizationId, |
| 181 | + request.authProviderId, |
| 182 | + ); |
| 183 | + } else { |
| 184 | + await this.authProviderService.deleteAuthProviderOfUser(context.user.id, request.authProviderId); |
| 185 | + } |
| 186 | + |
| 187 | + return new DeleteAuthProviderResponse(); |
| 188 | + } |
| 189 | +} |
0 commit comments