From 1af2cdc32f4076884fac1b9849ca12c533fefdb8 Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Wed, 18 May 2022 09:35:15 +0300 Subject: [PATCH] chore: Apply git user data configmap to mount to /etc/gitconfig --- infrastructures/kubernetes/pom.xml | 4 + .../kubernetes/KubernetesInfraModule.java | 4 +- .../GitconfigUserDataConfigurator.java | 99 +++++++++++++ .../openshift/OpenShiftInfraModule.java | 4 +- .../bitbucket/BitbucketServerModule.java | 6 +- .../BitbucketServerUserDataFetcher.java | 77 ++++++++++ .../factory/server/github/GithubModule.java | 6 +- .../server/github/GithubUserDataFetcher.java | 94 ++++++++++++ .../factory/server/gitlab/GitlabModule.java | 6 +- .../server/gitlab/GitlabUserDataFetcher.java | 138 ++++++++++++++++++ .../api/factory/server/scm/GitUserData.java | 59 ++++++++ .../server/scm/GitUserDataFetcher.java | 29 ++++ 12 files changed, 521 insertions(+), 5 deletions(-) create mode 100644 infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java create mode 100644 wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcher.java create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java create mode 100644 wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java create mode 100644 wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserData.java create mode 100644 wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserDataFetcher.java diff --git a/infrastructures/kubernetes/pom.xml b/infrastructures/kubernetes/pom.xml index 77509eccdd..4406dde172 100644 --- a/infrastructures/kubernetes/pom.xml +++ b/infrastructures/kubernetes/pom.xml @@ -125,6 +125,10 @@ org.eclipse.che.core che-core-api-dto + + org.eclipse.che.core + che-core-api-factory + org.eclipse.che.core che-core-api-model diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java index e1636584e2..8f6245159f 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -49,6 +49,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.environment.Kubernete import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.RemoveNamespaceOnWorkspaceRemove; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator; +import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigUserDataConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.SshKeysConfigurator; @@ -118,6 +119,7 @@ public class KubernetesInfraModule extends AbstractModule { namespaceConfigurators.addBinding().to(UserProfileConfigurator.class); namespaceConfigurators.addBinding().to(UserPreferencesConfigurator.class); namespaceConfigurators.addBinding().to(SshKeysConfigurator.class); + namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class); bind(KubernetesNamespaceService.class); diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java new file mode 100644 index 0000000000..053ffac8c1 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.api.workspace.server.spi.InfrastructureException; +import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; +import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; + +public class GitconfigUserDataConfigurator implements NamespaceConfigurator { + private final KubernetesClientFactory clientFactory; + private final Set gitUserDataFetchers; + private static final String CONFIGMAP_NAME = "workspace-userdata-gitconfig"; + private static final String CONFIGMAP_DATA_KEY = "gitconfig"; + + @Inject + public GitconfigUserDataConfigurator( + KubernetesClientFactory clientFactory, Set gitUserDataFetchers) { + this.clientFactory = clientFactory; + this.gitUserDataFetchers = gitUserDataFetchers; + } + + @Override + public void configure(NamespaceResolutionContext namespaceResolutionContext, String namespaceName) + throws InfrastructureException { + var client = clientFactory.create(); + GitUserData gitUserData = null; + for (GitUserDataFetcher fetcher : gitUserDataFetchers) { + try { + gitUserData = fetcher.fetchGitUserData(); + break; + } catch (ScmUnauthorizedException | ScmCommunicationException ignored) { + } + } + Map annotations = + ImmutableMap.of( + "controller.devfile.io/mount-as", + "subpath", + "controller.devfile.io/mount-path", + "/etc/"); + Map labels = + ImmutableMap.of( + "controller.devfile.io/mount-to-devworkspace", + "true", + "controller.devfile.io/watch-configmap", + "true"); + if (gitUserData != null + && client.configMaps().inNamespace(namespaceName).withName(CONFIGMAP_NAME).get() == null + && client + .configMaps() + .inNamespace(namespaceName) + .withLabels(labels) + .list() + .getItems() + .stream() + .noneMatch( + configMap -> + configMap + .getMetadata() + .getAnnotations() + .entrySet() + .containsAll(annotations.entrySet()) + && configMap.getData().containsKey(CONFIGMAP_DATA_KEY))) { + ConfigMap configMap = + new ConfigMapBuilder() + .withNewMetadata() + .withName(CONFIGMAP_NAME) + .withLabels(labels) + .withAnnotations(annotations) + .endMetadata() + .build(); + configMap.setData( + ImmutableMap.of( + CONFIGMAP_DATA_KEY, + String.format( + "[user]\n\tname = %1$s\n\temail = %2$s", + gitUserData.getScmUsername(), gitUserData.getScmUserEmail()))); + client.configMaps().inNamespace(namespaceName).create(configMap); + } + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java index 488abb5519..f112e05376 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -54,6 +54,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.environment.Kubernete import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.CredentialsSecretConfigurator; +import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.GitconfigUserDataConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.NamespaceConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.PreferencesConfigMapConfigurator; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.configurator.SshKeysConfigurator; @@ -127,6 +128,7 @@ public class OpenShiftInfraModule extends AbstractModule { namespaceConfigurators.addBinding().to(OpenShiftWorkspaceServiceAccountConfigurator.class); namespaceConfigurators.addBinding().to(OpenShiftStopWorkspaceRoleConfigurator.class); namespaceConfigurators.addBinding().to(SshKeysConfigurator.class); + namespaceConfigurators.addBinding().to(GitconfigUserDataConfigurator.class); bind(KubernetesNamespaceService.class); diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java index 96a7b739f8..4ae3b5656b 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -14,6 +14,7 @@ package org.eclipse.che.api.factory.server.bitbucket; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.security.oauth1.BitbucketServerApiProvider; @@ -24,5 +25,8 @@ public class BitbucketServerModule extends AbstractModule { Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(BitbucketServerPersonalAccessTokenFetcher.class); bind(BitbucketServerApiClient.class).toProvider(BitbucketServerApiProvider.class); + Multibinder gitUserDataMultibinder = + Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); + gitUserDataMultibinder.addBinding().to(BitbucketServerUserDataFetcher.class); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcher.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcher.java new file mode 100644 index 0000000000..f6ba682832 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcher.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket; + +import static java.util.stream.Collectors.toList; + +import com.google.common.base.Splitter; +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.StringUtils; +import org.eclipse.che.commons.subject.Subject; + +/** Bitbucket git user data retriever. */ +public class BitbucketServerUserDataFetcher implements GitUserDataFetcher { + + /** Bitbucket API client. */ + private final BitbucketServerApiClient bitbucketServerApiClient; + + private final List registeredBitbucketEndpoints; + + @Inject + public BitbucketServerUserDataFetcher( + BitbucketServerApiClient bitbucketServerApiClient, + @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints) { + if (bitbucketEndpoints != null) { + this.registeredBitbucketEndpoints = + Splitter.on(",") + .splitToStream(bitbucketEndpoints) + .map(e -> StringUtils.trimEnd(e, '/')) + .collect(toList()); + } else { + this.registeredBitbucketEndpoints = Collections.emptyList(); + } + this.bitbucketServerApiClient = bitbucketServerApiClient; + } + + @Override + public GitUserData fetchGitUserData() throws ScmUnauthorizedException, ScmCommunicationException { + GitUserData gitUserData = null; + for (String bitbucketServerEndpoint : this.registeredBitbucketEndpoints) { + if (bitbucketServerApiClient.isConnected(bitbucketServerEndpoint)) { + Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); + try { + BitbucketUser user = bitbucketServerApiClient.getUser(cheSubject); + gitUserData = new GitUserData(user.getName(), user.getEmailAddress()); + } catch (ScmItemNotFoundException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + break; + } + } + if (gitUserData == null) { + throw new ScmCommunicationException("Failed to retrieve git user data from Bitbucket"); + } + return gitUserData; + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java index 0525e3104c..a34ce968b0 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.server.github; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class GithubModule extends AbstractModule { @@ -22,5 +23,8 @@ public class GithubModule extends AbstractModule { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcher.class); + Multibinder gitUserDataMultibinder = + Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); + gitUserDataMultibinder.addBinding().to(GithubUserDataFetcher.class); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java new file mode 100644 index 0000000000..ebfda1b18e --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.security.oauth.OAuthAPI; + +/** GitHub user data retriever. */ +public class GithubUserDataFetcher implements GitUserDataFetcher { + private final String apiEndpoint; + private final OAuthAPI oAuthAPI; + + /** GitHub API client. */ + private final GithubApiClient githubApiClient; + + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "github"; + + /** Collection of OAuth scopes required to make integration with GitHub work. */ + public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("repo"); + + @Inject + public GithubUserDataFetcher(@Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { + this.apiEndpoint = apiEndpoint; + this.oAuthAPI = oAuthAPI; + this.githubApiClient = new GithubApiClient(); + } + + @Override + public GitUserData fetchGitUserData() throws ScmUnauthorizedException, ScmCommunicationException { + OAuthToken oAuthToken; + try { + oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); + // Find the user associated to the OAuth token by querying the GitHub API. + GithubUser user = githubApiClient.getUser(oAuthToken.getToken()); + return new GitUserData(user.getName(), user.getEmail()); + } catch (UnauthorizedException e) { + Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); + throw new ScmUnauthorizedException( + cheSubject.getUserName() + + " is not authorized in " + + OAUTH_PROVIDER_NAME + + " OAuth provider.", + OAUTH_PROVIDER_NAME, + "2.0", + getLocalAuthenticateUrl()); + } catch (NotFoundException + | ServerException + | ForbiddenException + | BadRequestException + | ScmItemNotFoundException + | ScmBadRequestException + | ConflictException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + private String getLocalAuthenticateUrl() { + return apiEndpoint + + "/oauth/authenticate?oauth_provider=" + + OAUTH_PROVIDER_NAME + + "&scope=" + + Joiner.on(',').join(DEFAULT_TOKEN_SCOPES) + + "&request_method=POST&signature_method=rsa"; + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java index 8b3a52223d..bba050bf0e 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2022 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.server.gitlab; import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; public class GitlabModule extends AbstractModule { @@ -22,5 +23,8 @@ public class GitlabModule extends AbstractModule { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcher.class); + Multibinder gitUserDataMultibinder = + Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); + gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcher.class); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java new file mode 100644 index 0000000000..a666eb479c --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static java.lang.String.format; +import static java.util.stream.Collectors.toList; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.factory.server.scm.GitUserData; +import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.StringUtils; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.inject.ConfigurationException; +import org.eclipse.che.security.oauth.OAuthAPI; + +/** Gitlab OAuth token retriever. */ +public class GitlabUserDataFetcher implements GitUserDataFetcher { + private final String apiEndpoint; + private final OAuthAPI oAuthAPI; + + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "gitlab"; + + private final List registeredGitlabEndpoints; + + public static final Set DEFAULT_TOKEN_SCOPES = + ImmutableSet.of("api", "write_repository", "openid"); + + @Inject + public GitlabUserDataFetcher( + @Named("che.api") String apiEndpoint, + @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints, + @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint, + OAuthAPI oAuthAPI) { + this.apiEndpoint = apiEndpoint; + if (gitlabEndpoints != null) { + this.registeredGitlabEndpoints = + Splitter.on(",") + .splitToStream(gitlabEndpoints) + .map(e -> StringUtils.trimEnd(e, '/')) + .collect(toList()); + } else { + this.registeredGitlabEndpoints = Collections.emptyList(); + } + if (oauthEndpoint != null) { + if (!registeredGitlabEndpoints.contains(StringUtils.trimEnd(oauthEndpoint, '/'))) { + throw new ConfigurationException( + "GitLab OAuth integration endpoint must be present in registered GitLab endpoints list."); + } + this.oAuthAPI = oAuthAPI; + } else { + this.oAuthAPI = null; + } + } + + @Override + public GitUserData fetchGitUserData() throws ScmUnauthorizedException, ScmCommunicationException { + if (oAuthAPI == null) { + throw new ScmCommunicationException( + format( + "OAuth 2 is not configured for SCM provider [%s]. For details, refer " + + "the documentation in section of SCM providers configuration.", + OAUTH_PROVIDER_NAME)); + } + OAuthToken oAuthToken; + try { + oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); + } catch (UnauthorizedException e) { + Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); + throw new ScmUnauthorizedException( + cheSubject.getUserName() + + " is not authorized in " + + OAUTH_PROVIDER_NAME + + " OAuth provider.", + OAUTH_PROVIDER_NAME, + "2.0", + getLocalAuthenticateUrl()); + } catch (NotFoundException + | ServerException + | ForbiddenException + | BadRequestException + | ConflictException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + GitUserData gitUserData = null; + for (String gitlabServerEndpoint : this.registeredGitlabEndpoints) { + try { + GitlabUser user = new GitlabApiClient(gitlabServerEndpoint).getUser(oAuthToken.getToken()); + gitUserData = new GitUserData(user.getName(), user.getEmail()); + break; + } catch (ScmItemNotFoundException | ScmBadRequestException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + if (gitUserData == null) { + throw new ScmCommunicationException("Failed to retrieve git user data from Gitlab"); + } + return gitUserData; + } + + private String getLocalAuthenticateUrl() { + return apiEndpoint + + "/oauth/authenticate?oauth_provider=" + + OAUTH_PROVIDER_NAME + + "&scope=" + + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) + + "&request_method=POST&signature_method=rsa"; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserData.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserData.java new file mode 100644 index 0000000000..516486096f --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserData.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.scm; + +import java.util.Objects; + +/** Personal SCM user data such as `username` and `email`. Is used to sign git commits. */ +public class GitUserData { + private final String scmUsername; + private final String scmUserEmail; + + public GitUserData(String scmUsername, String scmUserEmail) { + this.scmUsername = scmUsername; + this.scmUserEmail = scmUserEmail; + } + + public String getScmUsername() { + return scmUsername; + } + + public String getScmUserEmail() { + return scmUserEmail; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GitUserData that = (GitUserData) o; + return Objects.equals(scmUsername, that.scmUsername) + && Objects.equals(scmUserEmail, that.scmUserEmail); + } + + @Override + public int hashCode() { + return Objects.hash(scmUsername, scmUserEmail); + } + + @Override + public String toString() { + return "GitUserData{" + + ", scmUsername='" + + scmUsername + + '\'' + + ", scmUserEmail='" + + scmUserEmail + + '\'' + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserDataFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserDataFetcher.java new file mode 100644 index 0000000000..dccd7951da --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/GitUserDataFetcher.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2022 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.scm; + +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; + +public interface GitUserDataFetcher { + + /** + * Retrieve a {@link GitUserData} object from concrete scm provider + * + * @return - {@link GitUserData} object. + * @throws ScmUnauthorizedException - in case if user is not authorized che server to create a new + * token. Further user interaction is needed before calling this method next time. + * @throws ScmCommunicationException - Some unexpected problem occurred during communication with + * scm provider. + */ + GitUserData fetchGitUserData() throws ScmUnauthorizedException, ScmCommunicationException; +}