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/AbstractWorkspaceServiceAccount.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java
index 39c097ef6b..b7624fff94 100644
--- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.java
+++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/AbstractWorkspaceServiceAccount.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/
@@ -53,6 +53,7 @@ public abstract class AbstractWorkspaceServiceAccount<
public static final String CONFIGMAPS_ROLE_NAME = "workspace-configmaps";
public static final String CREDENTIALS_SECRET_NAME = "workspace-credentials-secret";
public static final String PREFERENCES_CONFIGMAP_NAME = "workspace-preferences-configmap";
+ public static final String GIT_USERDATA_CONFIGMAP_NAME = "workspace-userdata-gitconfig-configmap";
protected final String namespace;
protected final String serviceAccountName;
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..13a23a643b
--- /dev/null
+++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserDataConfigurator.java
@@ -0,0 +1,111 @@
+/*
+ * 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 static java.util.Collections.singletonMap;
+import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.GIT_USERDATA_CONFIGMAP_NAME;
+
+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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GitconfigUserDataConfigurator implements NamespaceConfigurator {
+ private static final Logger LOG = LoggerFactory.getLogger(GitconfigUserDataConfigurator.class);
+ private final KubernetesClientFactory clientFactory;
+ private final Set gitUserDataFetchers;
+ 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 e) {
+ LOG.debug("No GitUserDataFetcher is configured. " + e.getMessage());
+ }
+ }
+
+ 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(GIT_USERDATA_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(GIT_USERDATA_CONFIGMAP_NAME)
+ .withLabels(labels)
+ .withAnnotations(annotations)
+ .endMetadata()
+ .build();
+ configMap.setData(
+ singletonMap(
+ 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/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java
new file mode 100644
index 0000000000..af4064bdfd
--- /dev/null
+++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/GitconfigUserdataConfiguratorTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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 static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.AbstractWorkspaceServiceAccount.GIT_USERDATA_CONFIGMAP_NAME;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+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;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class GitconfigUserdataConfiguratorTest {
+
+ private NamespaceConfigurator configurator;
+
+ @Mock private KubernetesClientFactory clientFactory;
+ @Mock private GitUserDataFetcher gitUserDataFetcher;
+ private KubernetesServer serverMock;
+
+ private NamespaceResolutionContext namespaceResolutionContext;
+ private final String TEST_NAMESPACE_NAME = "namespace123";
+ private final String TEST_WORKSPACE_ID = "workspace123";
+ private final String TEST_USER_ID = "user123";
+ private final String TEST_USERNAME = "jondoe";
+
+ @BeforeMethod
+ public void setUp()
+ throws InfrastructureException, ScmCommunicationException, ScmUnauthorizedException {
+ configurator = new GitconfigUserDataConfigurator(clientFactory, Set.of(gitUserDataFetcher));
+
+ serverMock = new KubernetesServer(true, true);
+ serverMock.before();
+ KubernetesClient client = spy(serverMock.getClient());
+ when(clientFactory.create()).thenReturn(client);
+
+ namespaceResolutionContext =
+ new NamespaceResolutionContext(TEST_WORKSPACE_ID, TEST_USER_ID, TEST_USERNAME);
+ }
+
+ @Test
+ public void createUserdataConfigmapWhenDoesNotExist()
+ throws ScmCommunicationException, ScmUnauthorizedException, InfrastructureException,
+ InterruptedException {
+ // given
+ when(gitUserDataFetcher.fetchGitUserData()).thenReturn(new GitUserData("gitUser", "gitEmail"));
+
+ // when
+ configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
+
+ // then create a secret
+ Assert.assertEquals(serverMock.getLastRequest().getMethod(), "POST");
+ Assert.assertNotNull(
+ serverMock
+ .getClient()
+ .configMaps()
+ .inNamespace(TEST_NAMESPACE_NAME)
+ .withName(GIT_USERDATA_CONFIGMAP_NAME)
+ .get());
+ }
+
+ @Test
+ public void doNothingWhenGitUserDataIsNull()
+ throws InfrastructureException, InterruptedException {
+ // when
+ configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
+
+ // then - don't create the configmap
+ var configMaps =
+ serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
+ Assert.assertEquals(configMaps.size(), 0);
+ }
+
+ @Test
+ public void doNothingWhenSecretAlreadyExists()
+ throws InfrastructureException, InterruptedException, ScmCommunicationException,
+ ScmUnauthorizedException {
+ // given
+ when(gitUserDataFetcher.fetchGitUserData()).thenReturn(new GitUserData("gitUser", "gitEmail"));
+ Map annotations =
+ ImmutableMap.of(
+ "controller.devfile.io/mount-as",
+ "subpath",
+ "controller.devfile.io/mount-path",
+ "/etc/",
+ "already",
+ "created");
+ Map labels =
+ ImmutableMap.of(
+ "controller.devfile.io/mount-to-devworkspace",
+ "true",
+ "controller.devfile.io/watch-configmap",
+ "true",
+ "already",
+ "created");
+ ConfigMap configMap =
+ new ConfigMapBuilder()
+ .withNewMetadata()
+ .withName(GIT_USERDATA_CONFIGMAP_NAME)
+ .withLabels(labels)
+ .withAnnotations(annotations)
+ .endMetadata()
+ .build();
+ configMap.setData(Collections.singletonMap("gitconfig", "empty"));
+ serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).create(configMap);
+
+ // when
+ configurator.configure(namespaceResolutionContext, TEST_NAMESPACE_NAME);
+
+ // then - don't create the configmap
+ Assert.assertEquals(serverMock.getLastRequest().getMethod(), "GET");
+ var configMaps =
+ serverMock.getClient().configMaps().inNamespace(TEST_NAMESPACE_NAME).list().getItems();
+ Assert.assertEquals(configMaps.size(), 1);
+ Assert.assertEquals(configMaps.get(0).getMetadata().getAnnotations().get("already"), "created");
+ }
+}
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-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java
new file mode 100644
index 0000000000..91dedfa9e5
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerUserDataFetcherTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+import java.net.MalformedURLException;
+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.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.commons.subject.SubjectImpl;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class BitbucketServerUserDataFetcherTest {
+ String someBitbucketURL = "https://some.bitbucketserver.com";
+ Subject subject;
+ @Mock BitbucketServerApiClient bitbucketServerApiClient;
+ BitbucketUser bitbucketUser;
+ BitbucketServerUserDataFetcher fetcher;
+
+ @BeforeMethod
+ public void setup() throws MalformedURLException {
+ subject = new SubjectImpl("another_user", "user987", "token111", false);
+ bitbucketUser =
+ new BitbucketUser("User", "user", 32423523, "NORMAL", true, "user", "user@users.com");
+ fetcher = new BitbucketServerUserDataFetcher(bitbucketServerApiClient, someBitbucketURL);
+ EnvironmentContext context = new EnvironmentContext();
+ context.setSubject(subject);
+ EnvironmentContext.setCurrent(context);
+ }
+
+ @Test
+ public void shouldBeAbleToFetchPersonalAccessToken()
+ throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException,
+ ScmBadRequestException {
+ // given
+ when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true);
+ when(bitbucketServerApiClient.getUser(eq(subject))).thenReturn(bitbucketUser);
+ // when
+ GitUserData gitUserData = fetcher.fetchGitUserData();
+ // then
+ assertEquals(gitUserData.getScmUsername(), "user");
+ assertEquals(gitUserData.getScmUserEmail(), "user@users.com");
+ }
+}
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..9d97bf5e79
--- /dev/null
+++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java
@@ -0,0 +1,100 @@
+/*
+ * 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, oAuthAPI, new GithubApiClient());
+ }
+
+ /** Constructor used for testing only. */
+ public GithubUserDataFetcher(
+ String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient) {
+ this.apiEndpoint = apiEndpoint;
+ this.oAuthAPI = oAuthAPI;
+ this.githubApiClient = 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-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java
new file mode 100644
index 0000000000..d4d3fe77f8
--- /dev/null
+++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubGitUserDataFetcherTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.eclipse.che.dto.server.DtoFactory.newDto;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.common.Slf4jNotifier;
+import com.google.common.net.HttpHeaders;
+import org.eclipse.che.api.auth.shared.dto.OAuthToken;
+import org.eclipse.che.api.factory.server.scm.GitUserData;
+import org.eclipse.che.security.oauth.OAuthAPI;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class GithubGitUserDataFetcherTest {
+
+ @Mock OAuthAPI oAuthAPI;
+ GithubUserDataFetcher githubGUDFetcher;
+
+ final int httpPort = 3301;
+ WireMockServer wireMockServer;
+ WireMock wireMock;
+
+ final String githubOauthToken = "gho_token1";
+
+ @BeforeMethod
+ void start() {
+ wireMockServer =
+ new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort));
+ wireMockServer.start();
+ WireMock.configureFor("localhost", httpPort);
+ wireMock = new WireMock("localhost", httpPort);
+ githubGUDFetcher =
+ new GithubUserDataFetcher(
+ "http://che.api", oAuthAPI, new GithubApiClient(wireMockServer.url("/")));
+ stubFor(
+ get(urlEqualTo("/user"))
+ .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken))
+ .willReturn(
+ aResponse()
+ .withHeader("Content-Type", "application/json; charset=utf-8")
+ .withHeader(GithubApiClient.GITHUB_OAUTH_SCOPES_HEADER, "repo")
+ .withBodyFile("github/rest/user/response.json")));
+ }
+
+ @AfterMethod
+ void stop() {
+ wireMockServer.stop();
+ }
+
+ @Test
+ public void shouldFetchGitUserData() throws Exception {
+ OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope("repo");
+ when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken);
+
+ GitUserData gitUserData = githubGUDFetcher.fetchGitUserData();
+
+ assertEquals(gitUserData.getScmUsername(), "Github User");
+ assertEquals(gitUserData.getScmUserEmail(), "github-user@acme.com");
+ }
+}
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..a0e36f3cfb
--- /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(
+ @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints,
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint,
+ @Named("che.api") String apiEndpoint,
+ 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-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java
new file mode 100644
index 0000000000..4a04050b52
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.eclipse.che.dto.server.DtoFactory.newDto;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.common.Slf4jNotifier;
+import com.google.common.net.HttpHeaders;
+import org.eclipse.che.api.auth.shared.dto.OAuthToken;
+import org.eclipse.che.api.factory.server.scm.GitUserData;
+import org.eclipse.che.security.oauth.OAuthAPI;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class GitlabUserDataFetcherTest {
+
+ @Mock OAuthAPI oAuthAPI;
+
+ GitlabUserDataFetcher gitlabUserDataFetcher;
+
+ WireMockServer wireMockServer;
+ WireMock wireMock;
+
+ @BeforeMethod
+ void start() {
+ wireMockServer =
+ new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort());
+ wireMockServer.start();
+ WireMock.configureFor("localhost", wireMockServer.port());
+ wireMock = new WireMock("localhost", wireMockServer.port());
+ gitlabUserDataFetcher =
+ new GitlabUserDataFetcher(
+ wireMockServer.url("/"), wireMockServer.url("/"), "http://che.api", oAuthAPI);
+
+ stubFor(
+ get(urlEqualTo("/api/v4/user"))
+ .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer oauthtoken"))
+ .willReturn(
+ aResponse()
+ .withHeader("Content-Type", "application/json; charset=utf-8")
+ .withBodyFile("gitlab/rest/api/v4/user/response.json")));
+ }
+
+ @AfterMethod
+ void stop() {
+ wireMockServer.stop();
+ }
+
+ @Test
+ public void shouldFetchGitUserData() throws Exception {
+ OAuthToken oAuthToken =
+ newDto(OAuthToken.class).withToken("oauthtoken").withScope("api write_repository openid");
+ when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken);
+
+ GitUserData gitUserData = gitlabUserDataFetcher.fetchGitUserData();
+ assertEquals(gitUserData.getScmUsername(), "John Smith");
+ assertEquals(gitUserData.getScmUserEmail(), "john@example.com");
+ }
+}
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;
+}