diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java
index 965dfec5cf7..c0f0cf18acb 100644
--- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java
+++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java
@@ -49,7 +49,9 @@
import org.eclipse.che.api.factory.server.github.GithubScmFileResolver;
import org.eclipse.che.api.factory.server.github.GithubScmFileResolverSecond;
import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolver;
+import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolverSecond;
import org.eclipse.che.api.factory.server.gitlab.GitlabScmFileResolver;
+import org.eclipse.che.api.factory.server.gitlab.GitlabScmFileResolverSecond;
import org.eclipse.che.api.system.server.ServiceTermination;
import org.eclipse.che.api.system.server.SystemModule;
import org.eclipse.che.api.user.server.NotImplementedTokenValidator;
@@ -157,6 +159,9 @@ protected void configure() {
.addBinding()
.to(BitbucketServerAuthorizingFactoryParametersResolver.class);
factoryParametersResolverMultibinder.addBinding().to(GitlabFactoryParametersResolver.class);
+ factoryParametersResolverMultibinder
+ .addBinding()
+ .to(GitlabFactoryParametersResolverSecond.class);
factoryParametersResolverMultibinder.addBinding().to(BitbucketFactoryParametersResolver.class);
factoryParametersResolverMultibinder
.addBinding()
@@ -172,6 +177,7 @@ protected void configure() {
scmFileResolverResolverMultibinder.addBinding().to(GithubScmFileResolverSecond.class);
scmFileResolverResolverMultibinder.addBinding().to(BitbucketScmFileResolver.class);
scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolver.class);
+ scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolverSecond.class);
scmFileResolverResolverMultibinder.addBinding().to(BitbucketServerScmFileResolver.class);
scmFileResolverResolverMultibinder.addBinding().to(AzureDevOpsScmFileResolver.class);
scmFileResolverResolverMultibinder.addBinding().to(GitSshScmFileResolver.class);
diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
index d6b2b544c49..8a27612ca33 100644
--- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
+++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties
@@ -647,19 +647,27 @@ che.workspace.devfile.async.storage.plugin=eclipse/che-async-pv-plugin/latest
che.integration.bitbucket.server_endpoints=NULL
# GitLab endpoints used for factory integrations.
-# A comma separated list of GitLab server URLs or `NULL` if no integration is expected.
-che.integration.gitlab.server_endpoints=NULL
# The address of the GitLab server with configured OAuth 2 integration.
che.integration.gitlab.oauth_endpoint=NULL
+# The address of the GitLab server with configured OAuth 2 integration. (The second GitLab instance).
+che.integration.gitlab.oauth_endpoint_2=NULL
+
# Configuration of GitLab OAuth2 client. Used to obtain personal access tokens.
# Location of the file with GitLab client ID.
che.oauth2.gitlab.clientid_filepath=NULL
+# Configuration of GitLab OAuth2 client. Used to obtain personal access tokens.
+# Location of the file with GitLab client ID. (The second GitLab instance).
+che.oauth2.gitlab.clientid_filepath_2=NULL
+
# Location of the file with GitLab client secret.
che.oauth2.gitlab.clientsecret_filepath=NULL
+# Location of the file with GitLab client secret. (The second GitLab instance).
+che.oauth2.gitlab.clientsecret_filepath_2=NULL
+
### Advanced authorization
# Comma separated list of users allowed to access Che.
che.infra.kubernetes.advanced_authorization.allow_users=NULL
diff --git a/pom.xml b/pom.xml
index 7deff6f2f70..0e45c55663e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -706,6 +706,11 @@
che-core-api-auth-gitlab
${che.version}
+
+ org.eclipse.che.core
+ che-core-api-auth-gitlab-common
+ ${che.version}
+
org.eclipse.che.core
che-core-api-auth-openshift
@@ -793,6 +798,11 @@
che-core-api-factory-gitlab
${che.version}
+
+ org.eclipse.che.core
+ che-core-api-factory-gitlab-common
+ ${che.version}
+
org.eclipse.che.core
che-core-api-factory-shared
diff --git a/wsmaster/che-core-api-auth-gitlab-common/pom.xml b/wsmaster/che-core-api-auth-gitlab-common/pom.xml
new file mode 100644
index 00000000000..302825ec9e1
--- /dev/null
+++ b/wsmaster/che-core-api-auth-gitlab-common/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+ 4.0.0
+
+ che-master-parent
+ org.eclipse.che.core
+ 7.94.0-SNAPSHOT
+
+ che-core-api-auth-gitlab-common
+ jar
+ Che Core :: API :: Authentication GitLab Common
+
+
+ com.google.guava
+ guava
+
+
+ com.google.http-client
+ google-http-client
+
+
+ jakarta.inject
+ jakarta.inject-api
+
+
+ org.eclipse.che.core
+ che-core-api-auth
+
+
+ org.eclipse.che.core
+ che-core-api-auth-shared
+
+
+ org.eclipse.che.core
+ che-core-commons-json
+
+
+ org.eclipse.che.core
+ che-core-commons-lang
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.testng
+ testng
+ test
+
+
+ org.wiremock
+ wiremock-standalone
+ test
+
+
+
diff --git a/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java
new file mode 100644
index 00000000000..50389add71f
--- /dev/null
+++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012-2024 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.security.oauth;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import javax.inject.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration.
+ *
+ * @author Pavol Baran
+ */
+public class AbstractGitLabOAuthAuthenticatorProvider implements Provider {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(AbstractGitLabOAuthAuthenticatorProvider.class);
+ private final OAuthAuthenticator authenticator;
+ private final String providerName;
+
+ public AbstractGitLabOAuthAuthenticatorProvider(
+ String clientIdPath,
+ String clientSecretPath,
+ String gitlabEndpoint,
+ String cheApiEndpoint,
+ String providerName)
+ throws IOException {
+ this.providerName = providerName;
+ authenticator =
+ getOAuthAuthenticator(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint);
+ LOG.debug("{} GitLab OAuth Authenticator is used.", authenticator);
+ }
+
+ @Override
+ public OAuthAuthenticator get() {
+ return authenticator;
+ }
+
+ private OAuthAuthenticator getOAuthAuthenticator(
+ String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint)
+ throws IOException {
+ if (!isNullOrEmpty(clientIdPath)
+ && !isNullOrEmpty(clientSecretPath)
+ && !isNullOrEmpty(gitlabEndpoint)) {
+ String clientId = Files.readString(Path.of(clientIdPath));
+ String clientSecret = Files.readString(Path.of(clientSecretPath));
+ if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
+ return new GitLabOAuthAuthenticator(
+ clientId, clientSecret, gitlabEndpoint, cheApiEndpoint, providerName);
+ }
+ }
+ return new NoopOAuthAuthenticator();
+ }
+
+ static class NoopOAuthAuthenticator extends OAuthAuthenticator {
+
+ @Override
+ public String getOAuthProvider() {
+ return "Noop";
+ }
+
+ @Override
+ public String getEndpointUrl() {
+ return "Noop";
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java
similarity index 94%
rename from wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java
rename to wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java
index 4acece02350..6e78b7281b7 100644
--- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java
+++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java
@@ -40,13 +40,19 @@ public class GitLabOAuthAuthenticator extends OAuthAuthenticator {
private final String clientId;
private final String clientSecret;
private final String gitlabEndpoint;
+ private final String providerName;
public GitLabOAuthAuthenticator(
- String clientId, String clientSecret, String gitlabEndpoint, String cheApiEndpoint)
+ String clientId,
+ String clientSecret,
+ String gitlabEndpoint,
+ String cheApiEndpoint,
+ String providerName)
throws IOException {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.gitlabEndpoint = trimEnd(gitlabEndpoint, '/');
+ this.providerName = providerName;
String trimmedGitlabEndpoint = trimEnd(gitlabEndpoint, '/');
this.gitlabUserEndpoint = trimmedGitlabEndpoint + "/api/v4/user";
this.cheApiEndpoint = cheApiEndpoint;
@@ -61,7 +67,7 @@ public GitLabOAuthAuthenticator(
@Override
public String getOAuthProvider() {
- return "gitlab";
+ return providerName;
}
@Override
diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java
similarity index 96%
rename from wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java
rename to wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java
index 078f847ef4a..1b6c9f70652 100644
--- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java
+++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-auth-gitlab/pom.xml b/wsmaster/che-core-api-auth-gitlab/pom.xml
index 79c702aaff0..fd9748af1d2 100644
--- a/wsmaster/che-core-api-auth-gitlab/pom.xml
+++ b/wsmaster/che-core-api-auth-gitlab/pom.xml
@@ -49,23 +49,15 @@
org.eclipse.che.core
- che-core-api-auth-shared
-
-
- org.eclipse.che.core
- che-core-commons-annotations
+ che-core-api-auth-gitlab-common
org.eclipse.che.core
- che-core-commons-json
+ che-core-api-auth-shared
org.eclipse.che.core
- che-core-commons-lang
-
-
- org.slf4j
- slf4j-api
+ che-core-commons-annotations
org.testng
diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java
index ef3951a3dc5..29b3c190716 100644
--- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java
+++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
@@ -25,5 +25,6 @@ protected void configure() {
Multibinder oAuthAuthenticators =
Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProvider.class);
+ oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProviderSecond.class);
}
}
diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java
index a8a11015ec5..17965bb3170 100644
--- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java
+++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
@@ -11,18 +11,11 @@
*/
package org.eclipse.che.security.oauth;
-import static com.google.common.base.Strings.isNullOrEmpty;
-
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import javax.inject.Inject;
import javax.inject.Named;
-import javax.inject.Provider;
import javax.inject.Singleton;
import org.eclipse.che.commons.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration.
@@ -30,9 +23,8 @@
* @author Pavol Baran
*/
@Singleton
-public class GitLabOAuthAuthenticatorProvider implements Provider {
- private static final Logger LOG = LoggerFactory.getLogger(GitLabOAuthAuthenticatorProvider.class);
- private final OAuthAuthenticator authenticator;
+public class GitLabOAuthAuthenticatorProvider extends AbstractGitLabOAuthAuthenticatorProvider {
+ private static final String PROVIDER_NAME = "gitlab";
@Inject
public GitLabOAuthAuthenticatorProvider(
@@ -41,41 +33,6 @@ public GitLabOAuthAuthenticatorProvider(
@Nullable @Named("che.integration.gitlab.oauth_endpoint") String gitlabEndpoint,
@Named("che.api") String cheApiEndpoint)
throws IOException {
- authenticator =
- getOAuthAuthenticator(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint);
- LOG.debug("{} GitLab OAuth Authenticator is used.", authenticator);
- }
-
- @Override
- public OAuthAuthenticator get() {
- return authenticator;
- }
-
- private OAuthAuthenticator getOAuthAuthenticator(
- String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint)
- throws IOException {
- if (!isNullOrEmpty(clientIdPath)
- && !isNullOrEmpty(clientSecretPath)
- && !isNullOrEmpty(gitlabEndpoint)) {
- String clientId = Files.readString(Path.of(clientIdPath));
- String clientSecret = Files.readString(Path.of(clientSecretPath));
- if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
- return new GitLabOAuthAuthenticator(clientId, clientSecret, gitlabEndpoint, cheApiEndpoint);
- }
- }
- return new NoopOAuthAuthenticator();
- }
-
- static class NoopOAuthAuthenticator extends OAuthAuthenticator {
-
- @Override
- public String getOAuthProvider() {
- return "Noop";
- }
-
- @Override
- public String getEndpointUrl() {
- return "Noop";
- }
+ super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME);
}
}
diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java
new file mode 100644
index 00000000000..42abe5d5427
--- /dev/null
+++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2012-2024 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.security.oauth;
+
+import java.io.IOException;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import org.eclipse.che.commons.annotation.Nullable;
+
+/**
+ * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration.
+ *
+ * @author Pavol Baran
+ */
+@Singleton
+public class GitLabOAuthAuthenticatorProviderSecond
+ extends AbstractGitLabOAuthAuthenticatorProvider {
+ private static final String PROVIDER_NAME = "gitlab_2";
+
+ @Inject
+ public GitLabOAuthAuthenticatorProviderSecond(
+ @Nullable @Named("che.oauth2.gitlab.clientid_filepath_2") String clientIdPath,
+ @Nullable @Named("che.oauth2.gitlab.clientsecret_filepath_2") String clientSecretPath,
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String gitlabEndpoint,
+ @Named("che.api") String cheApiEndpoint)
+ throws IOException {
+ super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME);
+ }
+}
diff --git a/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java b/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java
index dba4c89e4d6..2fcb9bfe5aa 100644
--- a/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java
+++ b/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java
@@ -49,7 +49,7 @@ public void shouldGetToken() throws Exception {
// given
GitLabOAuthAuthenticator gitLabOAuthAuthenticator =
new GitLabOAuthAuthenticator(
- "id", "secret", wireMockServer.url("/"), "https://che.api.com");
+ "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab");
Field flowField = OAuthAuthenticator.class.getDeclaredField("flow");
Field credentialDataStoreField =
((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore");
@@ -74,7 +74,7 @@ public void shouldGetEmptyToken() throws Exception {
// given
GitLabOAuthAuthenticator gitLabOAuthAuthenticator =
new GitLabOAuthAuthenticator(
- "id", "secret", wireMockServer.url("/"), "https://che.api.com");
+ "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab");
Field flowField = OAuthAuthenticator.class.getDeclaredField("flow");
Field credentialDataStoreField =
((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore");
diff --git a/wsmaster/che-core-api-factory-gitlab-common/pom.xml b/wsmaster/che-core-api-factory-gitlab-common/pom.xml
new file mode 100644
index 00000000000..ddc3653370b
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/pom.xml
@@ -0,0 +1,141 @@
+
+
+
+ 4.0.0
+
+ che-master-parent
+ org.eclipse.che.core
+ 7.94.0-SNAPSHOT
+
+ che-core-api-factory-gitlab-common
+ jar
+ Che Core :: API :: Factory Resolver Gitlab Common
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.guava
+ guava
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ org.eclipse.che.core
+ che-core-api-auth
+
+
+ org.eclipse.che.core
+ che-core-api-auth-shared
+
+
+ org.eclipse.che.core
+ che-core-api-core
+
+
+ org.eclipse.che.core
+ che-core-api-dto
+
+
+ org.eclipse.che.core
+ che-core-api-factory
+
+
+ org.eclipse.che.core
+ che-core-api-factory-shared
+
+
+ org.eclipse.che.core
+ che-core-api-workspace
+
+
+ org.eclipse.che.core
+ che-core-commons-lang
+
+
+ org.slf4j
+ slf4j-api
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ provided
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+ com.github.tomakehurst
+ wiremock-jre8-standalone
+ test
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+ test
+
+
+ org.eclipse.che.core
+ che-core-commons-json
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-testng
+ test
+
+
+ org.slf4j
+ jcl-over-slf4j
+ test
+
+
+ org.testng
+ testng
+ test
+
+
+ org.wiremock
+ wiremock-standalone
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 8
+
+
+
+
+
diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java
new file mode 100644
index 00000000000..8a42b8420a5
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2012-2024 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 org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME;
+import static org.eclipse.che.dto.server.DtoFactory.newDto;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.Map;
+import org.eclipse.che.api.core.ApiException;
+import org.eclipse.che.api.core.BadRequestException;
+import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver;
+import org.eclipse.che.api.factory.server.FactoryParametersResolver;
+import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
+import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
+import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
+import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
+import org.eclipse.che.api.factory.shared.dto.FactoryVisitor;
+import org.eclipse.che.api.factory.shared.dto.ScmInfoDto;
+import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
+
+/**
+ * Provides Factory Parameters resolver for Gitlab repositories.
+ *
+ * @author Max Shaposhnyk
+ */
+public class AbstractGitlabFactoryParametersResolver extends BaseFactoryParameterResolver
+ implements FactoryParametersResolver {
+
+ private final URLFetcher urlFetcher;
+ private final AbstractGitlabUrlParser gitlabURLParser;
+ private final PersonalAccessTokenManager personalAccessTokenManager;
+ private final String providerName;
+
+ public AbstractGitlabFactoryParametersResolver(
+ URLFactoryBuilder urlFactoryBuilder,
+ URLFetcher urlFetcher,
+ AbstractGitlabUrlParser gitlabURLParser,
+ PersonalAccessTokenManager personalAccessTokenManager,
+ AuthorisationRequestManager authorisationRequestManager,
+ String providerName) {
+ super(authorisationRequestManager, urlFactoryBuilder, providerName);
+ this.urlFetcher = urlFetcher;
+ this.gitlabURLParser = gitlabURLParser;
+ this.personalAccessTokenManager = personalAccessTokenManager;
+ this.providerName = providerName;
+ }
+
+ /**
+ * Check if this resolver can be used with the given parameters.
+ *
+ * @param factoryParameters map of parameters dedicated to factories
+ * @return true if it will be accepted by the resolver implementation or false if it is not
+ * accepted
+ */
+ @Override
+ public boolean accept(@NotNull final Map factoryParameters) {
+ return factoryParameters.containsKey(URL_PARAMETER_NAME)
+ && gitlabURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME));
+ }
+
+ @Override
+ public String getProviderName() {
+ return providerName;
+ }
+
+ /**
+ * Create factory object based on provided parameters
+ *
+ * @param factoryParameters map containing factory data parameters provided through URL
+ * @throws BadRequestException when data are invalid
+ */
+ @Override
+ public FactoryMetaDto createFactory(@NotNull final Map factoryParameters)
+ throws ApiException {
+ // no need to check null value of url parameter as accept() method has performed the check
+ final GitlabUrl gitlabUrl = gitlabURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME));
+ // create factory from the following location if location exists, else create default factory
+ return createFactory(
+ factoryParameters,
+ gitlabUrl,
+ new GitlabFactoryVisitor(gitlabUrl),
+ new GitlabAuthorizingFileContentProvider(
+ gitlabUrl, urlFetcher, personalAccessTokenManager));
+ }
+
+ /**
+ * Visitor that puts the default devfile or updates devfile projects into the Gitlab Factory, if
+ * needed.
+ */
+ private class GitlabFactoryVisitor implements FactoryVisitor {
+
+ private final GitlabUrl gitlabUrl;
+
+ private GitlabFactoryVisitor(GitlabUrl gitlabUrl) {
+ this.gitlabUrl = gitlabUrl;
+ }
+
+ @Override
+ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) {
+ ScmInfoDto scmInfo =
+ newDto(ScmInfoDto.class)
+ .withScmProviderName(gitlabUrl.getProviderName())
+ .withRepositoryUrl(gitlabUrl.repositoryLocation());
+ if (gitlabUrl.getBranch() != null) {
+ scmInfo.withBranch(gitlabUrl.getBranch());
+ }
+ return factoryDto.withScmInfo(scmInfo);
+ }
+ }
+
+ @Override
+ public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException {
+ return gitlabURLParser.parse(factoryUrl);
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java
new file mode 100644
index 00000000000..05949fb1729
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2012-2024 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 org.eclipse.che.commons.lang.StringUtils.trimEnd;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Optional;
+import java.util.Set;
+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.PersonalAccessToken;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams;
+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.api.factory.server.scm.exception.UnknownScmProviderException;
+import org.eclipse.che.commons.lang.NameGenerator;
+import org.eclipse.che.commons.lang.Pair;
+import org.eclipse.che.commons.subject.Subject;
+import org.eclipse.che.security.oauth.OAuthAPI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** GitLab OAuth token retriever. */
+public class AbstractGitlabOAuthTokenFetcher implements PersonalAccessTokenFetcher {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractGitlabOAuthTokenFetcher.class);
+ public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository");
+
+ private final OAuthAPI oAuthAPI;
+ private final String serverUrl;
+ private final String apiEndpoint;
+ private final String providerName;
+
+ public AbstractGitlabOAuthTokenFetcher(
+ String serverUrl, String apiEndpoint, OAuthAPI oAuthAPI, String providerName) {
+ this.serverUrl = trimEnd(serverUrl, '/');
+ this.apiEndpoint = apiEndpoint;
+ this.providerName = providerName;
+ this.oAuthAPI = oAuthAPI;
+ }
+
+ @Override
+ public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl)
+ throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
+ return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true);
+ }
+
+ @Override
+ public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl)
+ throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
+ return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false);
+ }
+
+ private PersonalAccessToken fetchOrRefreshPersonalAccessToken(
+ Subject cheSubject, String scmServerUrl, boolean forceRefreshToken)
+ throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
+ scmServerUrl = trimEnd(scmServerUrl, '/');
+ GitlabApiClient gitlabApiClient = getApiClient(scmServerUrl);
+ if (gitlabApiClient == null || !gitlabApiClient.isConnected(scmServerUrl)) {
+ LOG.debug("not a valid url {} for current fetcher ", scmServerUrl);
+ return null;
+ }
+ 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.",
+ providerName));
+ }
+ OAuthToken oAuthToken;
+ try {
+ oAuthToken =
+ forceRefreshToken
+ ? oAuthAPI.refreshToken(providerName)
+ : oAuthAPI.getOrRefreshToken(providerName);
+ String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5);
+ String tokenId = NameGenerator.generate("id-", 5);
+ Optional> valid =
+ isValid(
+ new PersonalAccessTokenParams(
+ scmServerUrl, providerName, tokenName, tokenId, oAuthToken.getToken(), null));
+ if (valid.isEmpty()) {
+ throw buildScmUnauthorizedException(cheSubject);
+ } else if (!valid.get().first) {
+ throw new ScmCommunicationException(
+ "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: "
+ + DEFAULT_TOKEN_SCOPES);
+ }
+ return new PersonalAccessToken(
+ scmServerUrl,
+ providerName,
+ cheSubject.getUserId(),
+ valid.get().second,
+ tokenName,
+ tokenId,
+ oAuthToken.getToken());
+ } catch (UnauthorizedException e) {
+ throw buildScmUnauthorizedException(cheSubject);
+ } catch (NotFoundException nfe) {
+ throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl);
+ } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) {
+ LOG.warn(e.getMessage());
+ throw new ScmCommunicationException(e.getMessage(), e);
+ }
+ }
+
+ private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) {
+ return new ScmUnauthorizedException(
+ cheSubject.getUserName() + " is not authorized in " + providerName + " OAuth provider.",
+ providerName,
+ "2.0",
+ getLocalAuthenticateUrl());
+ }
+
+ @Override
+ public Optional isValid(PersonalAccessToken personalAccessToken) {
+ GitlabApiClient gitlabApiClient = getApiClient(personalAccessToken.getScmProviderUrl());
+ if (gitlabApiClient == null
+ || !gitlabApiClient.isConnected(personalAccessToken.getScmProviderUrl())) {
+ if (personalAccessToken.getScmTokenName().equals(providerName)) {
+ gitlabApiClient = new GitlabApiClient(personalAccessToken.getScmProviderUrl());
+ } else {
+ LOG.debug(
+ "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl());
+ return Optional.empty();
+ }
+ }
+ if (personalAccessToken.getScmTokenName() != null
+ && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) {
+ // validation OAuth token by special API call
+ try {
+ GitlabOauthTokenInfo info =
+ gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken());
+ return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES));
+ } catch (ScmItemNotFoundException | ScmCommunicationException | ScmUnauthorizedException e) {
+ return Optional.of(Boolean.FALSE);
+ }
+ } else {
+ // validating personal access token from secret. Since PAT API is accessible only in
+ // latest GitLab version, we just perform check by accessing something from API.
+ try {
+ GitlabUser user = gitlabApiClient.getUser(personalAccessToken.getToken());
+ if (personalAccessToken.getScmUserName().equals(user.getUsername())) {
+ return Optional.of(Boolean.TRUE);
+ } else {
+ return Optional.of(Boolean.FALSE);
+ }
+ } catch (ScmItemNotFoundException
+ | ScmCommunicationException
+ | ScmBadRequestException
+ | ScmUnauthorizedException e) {
+ return Optional.of(Boolean.FALSE);
+ }
+ }
+ }
+
+ @Override
+ public Optional> isValid(PersonalAccessTokenParams params)
+ throws ScmCommunicationException {
+ GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl());
+ if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) {
+ if (providerName.equals(params.getScmTokenName())) {
+ gitlabApiClient = new GitlabApiClient(params.getScmProviderUrl());
+ } else {
+ LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl());
+ return Optional.empty();
+ }
+ }
+ try {
+ GitlabUser user = gitlabApiClient.getUser(params.getToken());
+ if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) {
+ // validation OAuth token by special API call
+ GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(params.getToken());
+ return Optional.of(
+ Pair.of(
+ Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)
+ ? Boolean.TRUE
+ : Boolean.FALSE,
+ user.getUsername()));
+ }
+ // validating personal access token from secret. Since PAT API is accessible only in
+ // latest GitLab version, we just perform check by accessing something from API.
+ // TODO: add PAT scope validation
+ return Optional.of(Pair.of(Boolean.TRUE, user.getUsername()));
+ } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) {
+ return Optional.empty();
+ }
+ }
+
+ private String getLocalAuthenticateUrl() {
+ return apiEndpoint
+ + "/oauth/authenticate?oauth_provider="
+ + providerName
+ + "&scope="
+ + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES)
+ + "&request_method=POST&signature_method=rsa";
+ }
+
+ private GitlabApiClient getApiClient(String serverUrl) {
+ return serverUrl.equals(this.serverUrl) ? new GitlabApiClient(serverUrl) : null;
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java
new file mode 100644
index 00000000000..58d7f19e296
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012-2024 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 org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
+
+import java.io.IOException;
+import org.eclipse.che.api.core.ApiException;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.factory.server.ScmFileResolver;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
+import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
+
+/** GitLab specific SCM file resolver. */
+public class AbstractGitlabScmFileResolver implements ScmFileResolver {
+
+ private final AbstractGitlabUrlParser gitlabUrlParser;
+ private final URLFetcher urlFetcher;
+ private final PersonalAccessTokenManager personalAccessTokenManager;
+
+ public AbstractGitlabScmFileResolver(
+ AbstractGitlabUrlParser gitlabUrlParser,
+ URLFetcher urlFetcher,
+ PersonalAccessTokenManager personalAccessTokenManager) {
+ this.gitlabUrlParser = gitlabUrlParser;
+ this.urlFetcher = urlFetcher;
+ this.personalAccessTokenManager = personalAccessTokenManager;
+ }
+
+ @Override
+ public boolean accept(String repository) {
+ return gitlabUrlParser.isValid(repository);
+ }
+
+ @Override
+ public String fileContent(String repository, String filePath) throws ApiException {
+ GitlabUrl gitlabUrl = gitlabUrlParser.parse(repository);
+
+ try {
+ return fetchContent(gitlabUrl, filePath, false);
+ } catch (DevfileException exception) {
+ // This catch might mean that the authentication was rejected by user, try to repeat the fetch
+ // without authentication flow.
+ try {
+ return fetchContent(gitlabUrl, filePath, true);
+ } catch (DevfileException devfileException) {
+ throw toApiException(devfileException);
+ }
+ }
+ }
+
+ private String fetchContent(GitlabUrl gitlabUrl, String filePath, boolean skipAuthentication)
+ throws DevfileException, NotFoundException {
+ try {
+ GitlabAuthorizingFileContentProvider contentProvider =
+ new GitlabAuthorizingFileContentProvider(
+ gitlabUrl, urlFetcher, personalAccessTokenManager);
+ return skipAuthentication
+ ? contentProvider.fetchContentWithoutAuthentication(filePath)
+ : contentProvider.fetchContent(filePath);
+ } catch (IOException e) {
+ throw new NotFoundException(e.getMessage());
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java
new file mode 100644
index 00000000000..76aa1e13270
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2012-2024 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.google.common.base.Strings.isNullOrEmpty;
+import static java.lang.String.format;
+import static java.util.regex.Pattern.compile;
+import static org.eclipse.che.commons.lang.StringUtils.trimEnd;
+
+import jakarta.validation.constraints.NotNull;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
+import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
+import org.eclipse.che.commons.env.EnvironmentContext;
+
+/**
+ * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects.
+ *
+ * @author Max Shaposhnyk
+ */
+public class AbstractGitlabUrlParser {
+
+ private final DevfileFilenamesProvider devfileFilenamesProvider;
+ private final PersonalAccessTokenManager personalAccessTokenManager;
+ private final String providerName;
+ private static final List gitlabUrlPatternTemplates =
+ List.of(
+ "^(?%s)://(?%s)/(?([^/]++/?)+)/-/tree/(?.++)(/)?",
+ "^(?%s)://(?%s)/(?.*)"); // a wider one, should be the last in
+ // the list
+ private final String gitlabSSHPatternTemplate = "^git@(?%s):(?.*)$";
+ // list
+ private final List gitlabUrlPatterns = new ArrayList<>();
+
+ public AbstractGitlabUrlParser(
+ String serverUrl,
+ DevfileFilenamesProvider devfileFilenamesProvider,
+ PersonalAccessTokenManager personalAccessTokenManager,
+ String providerName) {
+ this.devfileFilenamesProvider = devfileFilenamesProvider;
+ this.personalAccessTokenManager = personalAccessTokenManager;
+ this.providerName = providerName;
+ if (isNullOrEmpty(serverUrl)) {
+ gitlabUrlPatternTemplates.forEach(
+ t -> gitlabUrlPatterns.add(compile(format(t, "https", "gitlab.com"))));
+ gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, "gitlab.com")));
+ } else {
+ String trimmedEndpoint = trimEnd(serverUrl, '/');
+ URI uri = URI.create(trimmedEndpoint);
+ String schema = uri.getScheme();
+ String host = uri.getHost();
+ for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) {
+ gitlabUrlPatterns.add(compile(format(gitlabUrlPatternTemplate, schema, host)));
+ }
+ gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, host)));
+ }
+ }
+
+ private boolean isUserTokenPresent(String repositoryUrl) {
+ Optional serverUrlOptional = getServerUrl(repositoryUrl);
+ if (serverUrlOptional.isPresent()) {
+ String serverUrl = serverUrlOptional.get();
+ try {
+ Optional token =
+ personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl);
+ if (token.isPresent()) {
+ PersonalAccessToken accessToken = token.get();
+ return accessToken.getScmTokenName().equals(providerName);
+ }
+ } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ public boolean isValid(@NotNull String url) {
+ return gitlabUrlPatterns.stream()
+ .anyMatch(pattern -> pattern.matcher(trimEnd(url, '/')).matches())
+ // If the Gitlab URL is not configured, try to find it in a manually added user namespace
+ // token.
+ || isUserTokenPresent(url)
+ // Try to call an API request to see if the URL matches Gitlab.
+ || isApiRequestRelevant(url);
+ }
+
+ private boolean isApiRequestRelevant(String repositoryUrl) {
+ Optional serverUrlOptional = getServerUrl(repositoryUrl);
+ if (serverUrlOptional.isPresent()) {
+ GitlabApiClient gitlabApiClient = new GitlabApiClient(serverUrlOptional.get());
+ try {
+ // If the token request catches the unauthorised error, it means that the provided url
+ // belongs to Gitlab.
+ gitlabApiClient.getOAuthTokenInfo("");
+ } catch (ScmUnauthorizedException e) {
+ return true;
+ } catch (ScmItemNotFoundException | IllegalArgumentException | ScmCommunicationException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private Optional getPatternMatcherByUrl(String url) {
+ URI uri =
+ URI.create(
+ url.matches(format(gitlabSSHPatternTemplate, ".*"))
+ ? "ssh://" + url.replace(":", "/")
+ : url);
+ String scheme = uri.getScheme();
+ String host = uri.getHost();
+ return gitlabUrlPatternTemplates.stream()
+ .map(t -> compile(format(t, scheme, host)).matcher(url))
+ .filter(Matcher::matches)
+ .findAny()
+ .or(
+ () -> {
+ Matcher matcher = compile(format(gitlabSSHPatternTemplate, host)).matcher(url);
+ if (matcher.matches()) {
+ return Optional.of(matcher);
+ }
+ return Optional.empty();
+ });
+ }
+
+ private Optional getServerUrl(String repositoryUrl) {
+ if (repositoryUrl.startsWith("git@")) {
+ String substring = repositoryUrl.substring(4);
+ return Optional.of("https://" + substring.substring(0, substring.indexOf(":")));
+ }
+ Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl);
+ if (serverUrlMatcher.find()) {
+ return Optional.of(
+ repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1));
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Parses url-s like https://gitlab.apps.cluster-327a.327a.example.opentlc.com/root/proj1.git into
+ * {@link GitlabUrl} objects.
+ */
+ public GitlabUrl parse(String url) {
+ String trimmedUrl = trimEnd(url, '/');
+ Optional matcherOptional =
+ gitlabUrlPatterns.stream()
+ .map(pattern -> pattern.matcher(trimmedUrl))
+ .filter(Matcher::matches)
+ .findFirst()
+ .or(() -> getPatternMatcherByUrl(trimmedUrl));
+ if (matcherOptional.isPresent()) {
+ return parse(matcherOptional.get()).withUrl(trimmedUrl);
+ } else {
+ throw new UnsupportedOperationException(
+ "The gitlab integration is not configured properly and cannot be used at this moment."
+ + "Please refer to docs to check the Gitlab integration instructions");
+ }
+ }
+
+ private GitlabUrl parse(Matcher matcher) {
+ String scheme = null;
+ try {
+ scheme = matcher.group("scheme");
+ } catch (IllegalArgumentException e) {
+ // ok no such group
+ }
+ String host = matcher.group("host");
+ String subGroups = trimEnd(matcher.group("subgroups"), '/');
+ if (subGroups.endsWith(".git")) {
+ subGroups = subGroups.substring(0, subGroups.length() - 4);
+ }
+
+ String branch = null;
+ try {
+ branch = matcher.group("branch");
+ } catch (IllegalArgumentException e) {
+ // ok no such group
+ }
+
+ return new GitlabUrl()
+ .withHostName(host)
+ .withScheme(scheme)
+ .withSubGroups(subGroups)
+ .withBranch(branch)
+ .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames());
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java
new file mode 100644
index 00000000000..2ed3a454b8a
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2012-2024 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 com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.eclipse.che.api.factory.server.scm.*;
+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;
+
+/** Gitlab OAuth token retriever. */
+public class AbstractGitlabUserDataFetcher extends AbstractGitUserDataFetcher {
+
+ private final String serverUrl;
+ private final String apiEndpoint;
+ private final String providerName;
+
+ public static final Set DEFAULT_TOKEN_SCOPES =
+ ImmutableSet.of("api", "write_repository", "openid");
+
+ public AbstractGitlabUserDataFetcher(
+ String serverUrl,
+ String apiEndpoint,
+ PersonalAccessTokenManager personalAccessTokenManager,
+ String providerName) {
+ super(providerName, serverUrl, personalAccessTokenManager);
+ this.serverUrl = serverUrl;
+ this.apiEndpoint = apiEndpoint;
+ this.providerName = providerName;
+ }
+
+ @Override
+ protected GitUserData fetchGitUserDataWithOAuthToken(String token)
+ throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException,
+ ScmUnauthorizedException {
+ GitlabUser user = new GitlabApiClient(serverUrl).getUser(token);
+ return new GitUserData(user.getName(), user.getEmail());
+ }
+
+ @Override
+ protected GitUserData fetchGitUserDataWithPersonalAccessToken(
+ PersonalAccessToken personalAccessToken)
+ throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException,
+ ScmUnauthorizedException {
+ GitlabUser user =
+ new GitlabApiClient(personalAccessToken.getScmProviderUrl())
+ .getUser(personalAccessToken.getToken());
+ return new GitUserData(user.getName(), user.getEmail());
+ }
+
+ protected String getLocalAuthenticateUrl() {
+ return apiEndpoint
+ + "/oauth/authenticate?oauth_provider="
+ + providerName
+ + "&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/GitlabApiClient.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java
similarity index 100%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java
similarity index 96%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java
index 1137407b943..9cb713abf7f 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java
similarity index 98%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java
index 9a1c32d28e3..3116ae59ba6 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2021 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java
similarity index 98%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java
index ebfcf700f59..0114029d876 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java
similarity index 99%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java
index f0bda151871..a30e6dbdd5f 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java
similarity index 98%
rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java
rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java
index b0f1bc201f9..2937e4d863d 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java
+++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2021 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
diff --git a/wsmaster/che-core-api-factory-gitlab/pom.xml b/wsmaster/che-core-api-factory-gitlab/pom.xml
index dd44899d23b..fced7ce4b8e 100644
--- a/wsmaster/che-core-api-factory-gitlab/pom.xml
+++ b/wsmaster/che-core-api-factory-gitlab/pom.xml
@@ -23,14 +23,6 @@
jar
Che Core :: API :: Factory Resolver Gitlab
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
com.google.guava
guava
@@ -43,10 +35,6 @@
jakarta.inject
jakarta.inject-api
-
- jakarta.validation
- jakarta.validation-api
-
org.eclipse.che.core
che-core-api-auth
@@ -67,6 +55,10 @@
org.eclipse.che.core
che-core-api-factory
+
+ org.eclipse.che.core
+ che-core-api-factory-gitlab-common
+
org.eclipse.che.core
che-core-api-factory-shared
@@ -83,18 +75,10 @@
org.eclipse.che.core
che-core-commons-annotations
-
- org.eclipse.che.core
- che-core-commons-inject
-
org.eclipse.che.core
che-core-commons-lang
-
- org.slf4j
- slf4j-api
-
jakarta.servlet
jakarta.servlet-api
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java
index b9bfd2024a1..1991f90f40c 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java
@@ -11,25 +11,12 @@
*/
package org.eclipse.che.api.factory.server.gitlab;
-import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME;
-import static org.eclipse.che.dto.server.DtoFactory.newDto;
-
-import jakarta.validation.constraints.NotNull;
-import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.eclipse.che.api.core.ApiException;
-import org.eclipse.che.api.core.BadRequestException;
-import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver;
import org.eclipse.che.api.factory.server.FactoryParametersResolver;
import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
-import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
-import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
-import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
-import org.eclipse.che.api.factory.shared.dto.FactoryVisitor;
-import org.eclipse.che.api.factory.shared.dto.ScmInfoDto;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
/**
@@ -38,16 +25,11 @@
* @author Max Shaposhnyk
*/
@Singleton
-public class GitlabFactoryParametersResolver extends BaseFactoryParameterResolver
+public class GitlabFactoryParametersResolver extends AbstractGitlabFactoryParametersResolver
implements FactoryParametersResolver {
private static final String PROVIDER_NAME = "gitlab";
- private final URLFactoryBuilder urlFactoryBuilder;
- private final URLFetcher urlFetcher;
- private final GitlabUrlParser gitlabURLParser;
- private final PersonalAccessTokenManager personalAccessTokenManager;
-
@Inject
public GitlabFactoryParametersResolver(
URLFactoryBuilder urlFactoryBuilder,
@@ -55,78 +37,12 @@ public GitlabFactoryParametersResolver(
GitlabUrlParser gitlabURLParser,
PersonalAccessTokenManager personalAccessTokenManager,
AuthorisationRequestManager authorisationRequestManager) {
- super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME);
- this.urlFactoryBuilder = urlFactoryBuilder;
- this.urlFetcher = urlFetcher;
- this.gitlabURLParser = gitlabURLParser;
- this.personalAccessTokenManager = personalAccessTokenManager;
- }
-
- /**
- * Check if this resolver can be used with the given parameters.
- *
- * @param factoryParameters map of parameters dedicated to factories
- * @return true if it will be accepted by the resolver implementation or false if it is not
- * accepted
- */
- @Override
- public boolean accept(@NotNull final Map factoryParameters) {
- return factoryParameters.containsKey(URL_PARAMETER_NAME)
- && gitlabURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME));
- }
-
- @Override
- public String getProviderName() {
- return PROVIDER_NAME;
- }
-
- /**
- * Create factory object based on provided parameters
- *
- * @param factoryParameters map containing factory data parameters provided through URL
- * @throws BadRequestException when data are invalid
- */
- @Override
- public FactoryMetaDto createFactory(@NotNull final Map factoryParameters)
- throws ApiException {
- // no need to check null value of url parameter as accept() method has performed the check
- final GitlabUrl gitlabUrl = gitlabURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME));
- // create factory from the following location if location exists, else create default factory
- return createFactory(
- factoryParameters,
- gitlabUrl,
- new GitlabFactoryVisitor(gitlabUrl),
- new GitlabAuthorizingFileContentProvider(
- gitlabUrl, urlFetcher, personalAccessTokenManager));
- }
-
- /**
- * Visitor that puts the default devfile or updates devfile projects into the Gitlab Factory, if
- * needed.
- */
- private class GitlabFactoryVisitor implements FactoryVisitor {
-
- private final GitlabUrl gitlabUrl;
-
- private GitlabFactoryVisitor(GitlabUrl gitlabUrl) {
- this.gitlabUrl = gitlabUrl;
- }
-
- @Override
- public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) {
- ScmInfoDto scmInfo =
- newDto(ScmInfoDto.class)
- .withScmProviderName(gitlabUrl.getProviderName())
- .withRepositoryUrl(gitlabUrl.repositoryLocation());
- if (gitlabUrl.getBranch() != null) {
- scmInfo.withBranch(gitlabUrl.getBranch());
- }
- return factoryDto.withScmInfo(scmInfo);
- }
- }
-
- @Override
- public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException {
- return gitlabURLParser.parse(factoryUrl);
+ super(
+ urlFactoryBuilder,
+ urlFetcher,
+ gitlabURLParser,
+ personalAccessTokenManager,
+ authorisationRequestManager,
+ PROVIDER_NAME);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java
new file mode 100644
index 00000000000..c9b6019adc8
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012-2024 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 javax.inject.Inject;
+import javax.inject.Singleton;
+import org.eclipse.che.api.factory.server.FactoryParametersResolver;
+import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
+import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
+
+/**
+ * Provides Factory Parameters resolver for Gitlab repositories.
+ *
+ * @author Max Shaposhnyk
+ */
+@Singleton
+public class GitlabFactoryParametersResolverSecond extends AbstractGitlabFactoryParametersResolver
+ implements FactoryParametersResolver {
+
+ private static final String PROVIDER_NAME = "gitlab_2";
+
+ @Inject
+ public GitlabFactoryParametersResolverSecond(
+ URLFactoryBuilder urlFactoryBuilder,
+ URLFetcher urlFetcher,
+ GitlabUrlParserSecond gitlabURLParser,
+ PersonalAccessTokenManager personalAccessTokenManager,
+ AuthorisationRequestManager authorisationRequestManager) {
+ super(
+ urlFactoryBuilder,
+ urlFetcher,
+ gitlabURLParser,
+ personalAccessTokenManager,
+ authorisationRequestManager,
+ PROVIDER_NAME);
+ }
+}
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 3fbca073b94..9207f6fa17a 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-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
@@ -23,8 +23,11 @@ protected void configure() {
Multibinder tokenFetcherMultibinder =
Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class);
tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcher.class);
+ tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcherSecond.class);
+
Multibinder gitUserDataMultibinder =
Multibinder.newSetBinder(binder(), GitUserDataFetcher.class);
gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcher.class);
+ gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcherSecond.class);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java
index 9444afff638..747554955de 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java
@@ -11,250 +11,21 @@
*/
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 com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-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.PersonalAccessToken;
-import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher;
-import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams;
-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.api.factory.server.scm.exception.UnknownScmProviderException;
import org.eclipse.che.commons.annotation.Nullable;
-import org.eclipse.che.commons.lang.NameGenerator;
-import org.eclipse.che.commons.lang.Pair;
-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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/** GitLab OAuth token retriever. */
-public class GitlabOAuthTokenFetcher implements PersonalAccessTokenFetcher {
+public class GitlabOAuthTokenFetcher extends AbstractGitlabOAuthTokenFetcher {
- private static final Logger LOG = LoggerFactory.getLogger(GitlabOAuthTokenFetcher.class);
private static final String OAUTH_PROVIDER_NAME = "gitlab";
- public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository");
-
- private final List registeredGitlabEndpoints;
- private final OAuthAPI oAuthAPI;
- private final String apiEndpoint;
@Inject
public GitlabOAuthTokenFetcher(
- @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints,
- @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint,
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl,
@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 PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl)
- throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
- return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true);
- }
-
- @Override
- public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl)
- throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
- return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false);
- }
-
- private PersonalAccessToken fetchOrRefreshPersonalAccessToken(
- Subject cheSubject, String scmServerUrl, boolean forceRefreshToken)
- throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException {
- scmServerUrl = StringUtils.trimEnd(scmServerUrl, '/');
- GitlabApiClient gitlabApiClient = getApiClient(scmServerUrl);
- if (gitlabApiClient == null || !gitlabApiClient.isConnected(scmServerUrl)) {
- LOG.debug("not a valid url {} for current fetcher ", scmServerUrl);
- return null;
- }
- 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 =
- forceRefreshToken
- ? oAuthAPI.refreshToken(OAUTH_PROVIDER_NAME)
- : oAuthAPI.getOrRefreshToken(OAUTH_PROVIDER_NAME);
- String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5);
- String tokenId = NameGenerator.generate("id-", 5);
- Optional> valid =
- isValid(
- new PersonalAccessTokenParams(
- scmServerUrl,
- OAUTH_PROVIDER_NAME,
- tokenName,
- tokenId,
- oAuthToken.getToken(),
- null));
- if (valid.isEmpty()) {
- throw buildScmUnauthorizedException(cheSubject);
- } else if (!valid.get().first) {
- throw new ScmCommunicationException(
- "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: "
- + DEFAULT_TOKEN_SCOPES);
- }
- return new PersonalAccessToken(
- scmServerUrl,
- OAUTH_PROVIDER_NAME,
- cheSubject.getUserId(),
- valid.get().second,
- tokenName,
- tokenId,
- oAuthToken.getToken());
- } catch (UnauthorizedException e) {
- throw buildScmUnauthorizedException(cheSubject);
- } catch (NotFoundException nfe) {
- throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl);
- } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) {
- LOG.warn(e.getMessage());
- throw new ScmCommunicationException(e.getMessage(), e);
- }
- }
-
- private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) {
- return new ScmUnauthorizedException(
- cheSubject.getUserName()
- + " is not authorized in "
- + OAUTH_PROVIDER_NAME
- + " OAuth provider.",
- OAUTH_PROVIDER_NAME,
- "2.0",
- getLocalAuthenticateUrl());
- }
-
- @Override
- public Optional isValid(PersonalAccessToken personalAccessToken) {
- GitlabApiClient gitlabApiClient = getApiClient(personalAccessToken.getScmProviderUrl());
- if (gitlabApiClient == null
- || !gitlabApiClient.isConnected(personalAccessToken.getScmProviderUrl())) {
- if (personalAccessToken.getScmTokenName().equals(OAUTH_PROVIDER_NAME)) {
- gitlabApiClient = new GitlabApiClient(personalAccessToken.getScmProviderUrl());
- } else {
- LOG.debug(
- "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl());
- return Optional.empty();
- }
- }
- if (personalAccessToken.getScmTokenName() != null
- && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) {
- // validation OAuth token by special API call
- try {
- GitlabOauthTokenInfo info =
- gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken());
- return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES));
- } catch (ScmItemNotFoundException | ScmCommunicationException | ScmUnauthorizedException e) {
- return Optional.of(Boolean.FALSE);
- }
- } else {
- // validating personal access token from secret. Since PAT API is accessible only in
- // latest GitLab version, we just perform check by accessing something from API.
- try {
- GitlabUser user = gitlabApiClient.getUser(personalAccessToken.getToken());
- if (personalAccessToken.getScmUserName().equals(user.getUsername())) {
- return Optional.of(Boolean.TRUE);
- } else {
- return Optional.of(Boolean.FALSE);
- }
- } catch (ScmItemNotFoundException
- | ScmCommunicationException
- | ScmBadRequestException
- | ScmUnauthorizedException e) {
- return Optional.of(Boolean.FALSE);
- }
- }
- }
-
- @Override
- public Optional> isValid(PersonalAccessTokenParams params)
- throws ScmCommunicationException {
- GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl());
- if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) {
- if (OAUTH_PROVIDER_NAME.equals(params.getScmTokenName())) {
- gitlabApiClient = new GitlabApiClient(params.getScmProviderUrl());
- } else {
- LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl());
- return Optional.empty();
- }
- }
- try {
- GitlabUser user = gitlabApiClient.getUser(params.getToken());
- if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) {
- // validation OAuth token by special API call
- GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(params.getToken());
- return Optional.of(
- Pair.of(
- Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)
- ? Boolean.TRUE
- : Boolean.FALSE,
- user.getUsername()));
- }
- // validating personal access token from secret. Since PAT API is accessible only in
- // latest GitLab version, we just perform check by accessing something from API.
- // TODO: add PAT scope validation
- return Optional.of(Pair.of(Boolean.TRUE, user.getUsername()));
- } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) {
- return Optional.empty();
- }
- }
-
- 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";
- }
-
- private GitlabApiClient getApiClient(String scmServerUrl) {
- return registeredGitlabEndpoints.contains(scmServerUrl)
- ? new GitlabApiClient(scmServerUrl)
- : null;
+ super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java
new file mode 100644
index 00000000000..a2cf599aab7
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012-2024 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 javax.inject.Inject;
+import javax.inject.Named;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.security.oauth.OAuthAPI;
+
+/** GitLab OAuth token retriever. */
+public class GitlabOAuthTokenFetcherSecond extends AbstractGitlabOAuthTokenFetcher {
+
+ private static final String OAUTH_PROVIDER_NAME = "gitlab_2";
+
+ @Inject
+ public GitlabOAuthTokenFetcherSecond(
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl,
+ @Named("che.api") String apiEndpoint,
+ OAuthAPI oAuthAPI) {
+ super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME);
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java
index 8a2ee17c7b6..4cfb64b9b4a 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
@@ -11,67 +11,18 @@
*/
package org.eclipse.che.api.factory.server.gitlab;
-import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException;
-
-import java.io.IOException;
import javax.inject.Inject;
-import org.eclipse.che.api.core.ApiException;
-import org.eclipse.che.api.core.NotFoundException;
-import org.eclipse.che.api.factory.server.ScmFileResolver;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
-import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
/** GitLab specific SCM file resolver. */
-public class GitlabScmFileResolver implements ScmFileResolver {
-
- private final GitlabUrlParser gitlabUrlParser;
- private final URLFetcher urlFetcher;
- private final PersonalAccessTokenManager personalAccessTokenManager;
+public class GitlabScmFileResolver extends AbstractGitlabScmFileResolver {
@Inject
public GitlabScmFileResolver(
GitlabUrlParser gitlabUrlParser,
URLFetcher urlFetcher,
PersonalAccessTokenManager personalAccessTokenManager) {
- this.gitlabUrlParser = gitlabUrlParser;
- this.urlFetcher = urlFetcher;
- this.personalAccessTokenManager = personalAccessTokenManager;
- }
-
- @Override
- public boolean accept(String repository) {
- return gitlabUrlParser.isValid(repository);
- }
-
- @Override
- public String fileContent(String repository, String filePath) throws ApiException {
- GitlabUrl gitlabUrl = gitlabUrlParser.parse(repository);
-
- try {
- return fetchContent(gitlabUrl, filePath, false);
- } catch (DevfileException exception) {
- // This catch might mean that the authentication was rejected by user, try to repeat the fetch
- // without authentication flow.
- try {
- return fetchContent(gitlabUrl, filePath, true);
- } catch (DevfileException devfileException) {
- throw toApiException(devfileException);
- }
- }
- }
-
- private String fetchContent(GitlabUrl gitlabUrl, String filePath, boolean skipAuthentication)
- throws DevfileException, NotFoundException {
- try {
- GitlabAuthorizingFileContentProvider contentProvider =
- new GitlabAuthorizingFileContentProvider(
- gitlabUrl, urlFetcher, personalAccessTokenManager);
- return skipAuthentication
- ? contentProvider.fetchContentWithoutAuthentication(filePath)
- : contentProvider.fetchContent(filePath);
- } catch (IOException e) {
- throw new NotFoundException(e.getMessage());
- }
+ super(gitlabUrlParser, urlFetcher, personalAccessTokenManager);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java
new file mode 100644
index 00000000000..aa9a0cb7d7b
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012-2024 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 javax.inject.Inject;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
+
+/** GitLab specific SCM file resolver. */
+public class GitlabScmFileResolverSecond extends AbstractGitlabScmFileResolver {
+
+ @Inject
+ public GitlabScmFileResolverSecond(
+ GitlabUrlParserSecond gitlabUrlParser,
+ URLFetcher urlFetcher,
+ PersonalAccessTokenManager personalAccessTokenManager) {
+ super(gitlabUrlParser, urlFetcher, personalAccessTokenManager);
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java
index 53faa2663da..6d5b6a29f68 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java
@@ -11,200 +11,26 @@
*/
package org.eclipse.che.api.factory.server.gitlab;
-import static java.lang.String.format;
-import static java.util.regex.Pattern.compile;
-import static org.eclipse.che.commons.lang.StringUtils.trimEnd;
-
-import com.google.common.base.Splitter;
-import jakarta.validation.constraints.NotNull;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Named;
-import org.eclipse.che.api.factory.server.scm.PersonalAccessToken;
import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
-import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
-import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
-import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
-import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
import org.eclipse.che.commons.annotation.Nullable;
-import org.eclipse.che.commons.env.EnvironmentContext;
/**
* Parser of String Gitlab URLs and provide {@link GitlabUrl} objects.
*
* @author Max Shaposhnyk
*/
-public class GitlabUrlParser {
+public class GitlabUrlParser extends AbstractGitlabUrlParser {
- private final DevfileFilenamesProvider devfileFilenamesProvider;
- private final PersonalAccessTokenManager personalAccessTokenManager;
- private static final List gitlabUrlPatternTemplates =
- List.of(
- "^(?%s)://(?%s)/(?([^/]++/?)+)/-/tree/(?.++)(/)?",
- "^(?%s)://(?%s)/(?.*)"); // a wider one, should be the last in
- // the list
- private final String gitlabSSHPatternTemplate = "^git@(?%s):(?.*)$";
- // list
- private final List gitlabUrlPatterns = new ArrayList<>();
private static final String OAUTH_PROVIDER_NAME = "gitlab";
@Inject
public GitlabUrlParser(
- @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints,
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl,
DevfileFilenamesProvider devfileFilenamesProvider,
PersonalAccessTokenManager personalAccessTokenManager) {
- this.devfileFilenamesProvider = devfileFilenamesProvider;
- this.personalAccessTokenManager = personalAccessTokenManager;
- if (gitlabEndpoints != null) {
- for (String gitlabEndpoint : Splitter.on(",").split(gitlabEndpoints)) {
- String trimmedEndpoint = trimEnd(gitlabEndpoint, '/');
- URI uri = URI.create(trimmedEndpoint);
- String schema = uri.getScheme();
- String host = uri.getHost();
- for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) {
- gitlabUrlPatterns.add(compile(format(gitlabUrlPatternTemplate, schema, host)));
- }
- gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, host)));
- }
- } else {
- gitlabUrlPatternTemplates.forEach(
- t -> gitlabUrlPatterns.add(compile(format(t, "https", "gitlab.com"))));
- gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, "gitlab.com")));
- }
- }
-
- private boolean isUserTokenPresent(String repositoryUrl) {
- Optional serverUrlOptional = getServerUrl(repositoryUrl);
- if (serverUrlOptional.isPresent()) {
- String serverUrl = serverUrlOptional.get();
- try {
- Optional token =
- personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl);
- if (token.isPresent()) {
- PersonalAccessToken accessToken = token.get();
- return accessToken.getScmTokenName().equals(OAUTH_PROVIDER_NAME);
- }
- } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) {
- return false;
- }
- }
- return false;
- }
-
- public boolean isValid(@NotNull String url) {
- return gitlabUrlPatterns.stream()
- .anyMatch(pattern -> pattern.matcher(trimEnd(url, '/')).matches())
- // If the Gitlab URL is not configured, try to find it in a manually added user namespace
- // token.
- || isUserTokenPresent(url)
- // Try to call an API request to see if the URL matches Gitlab.
- || isApiRequestRelevant(url);
- }
-
- private boolean isApiRequestRelevant(String repositoryUrl) {
- Optional serverUrlOptional = getServerUrl(repositoryUrl);
- if (serverUrlOptional.isPresent()) {
- GitlabApiClient gitlabApiClient = new GitlabApiClient(serverUrlOptional.get());
- try {
- // If the token request catches the unauthorised error, it means that the provided url
- // belongs to Gitlab.
- gitlabApiClient.getOAuthTokenInfo("");
- } catch (ScmUnauthorizedException e) {
- return true;
- } catch (ScmItemNotFoundException | IllegalArgumentException | ScmCommunicationException e) {
- return false;
- }
- }
- return false;
- }
-
- private Optional getPatternMatcherByUrl(String url) {
- URI uri =
- URI.create(
- url.matches(format(gitlabSSHPatternTemplate, ".*"))
- ? "ssh://" + url.replace(":", "/")
- : url);
- String scheme = uri.getScheme();
- String host = uri.getHost();
- return gitlabUrlPatternTemplates.stream()
- .map(t -> compile(format(t, scheme, host)).matcher(url))
- .filter(Matcher::matches)
- .findAny()
- .or(
- () -> {
- Matcher matcher = compile(format(gitlabSSHPatternTemplate, host)).matcher(url);
- if (matcher.matches()) {
- return Optional.of(matcher);
- }
- return Optional.empty();
- });
- }
-
- private Optional getServerUrl(String repositoryUrl) {
- if (repositoryUrl.startsWith("git@")) {
- String substring = repositoryUrl.substring(4);
- return Optional.of("https://" + substring.substring(0, substring.indexOf(":")));
- }
- Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl);
- if (serverUrlMatcher.find()) {
- return Optional.of(
- repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1));
- }
- return Optional.empty();
- }
-
- /**
- * Parses url-s like https://gitlab.apps.cluster-327a.327a.example.opentlc.com/root/proj1.git into
- * {@link GitlabUrl} objects.
- */
- public GitlabUrl parse(String url) {
- String trimmedUrl = trimEnd(url, '/');
- Optional matcherOptional =
- gitlabUrlPatterns.stream()
- .map(pattern -> pattern.matcher(trimmedUrl))
- .filter(Matcher::matches)
- .findFirst()
- .or(() -> getPatternMatcherByUrl(trimmedUrl));
- if (matcherOptional.isPresent()) {
- return parse(matcherOptional.get()).withUrl(trimmedUrl);
- } else {
- throw new UnsupportedOperationException(
- "The gitlab integration is not configured properly and cannot be used at this moment."
- + "Please refer to docs to check the Gitlab integration instructions");
- }
- }
-
- private GitlabUrl parse(Matcher matcher) {
- String scheme = null;
- try {
- scheme = matcher.group("scheme");
- } catch (IllegalArgumentException e) {
- // ok no such group
- }
- String host = matcher.group("host");
- String subGroups = trimEnd(matcher.group("subgroups"), '/');
- if (subGroups.endsWith(".git")) {
- subGroups = subGroups.substring(0, subGroups.length() - 4);
- }
-
- String branch = null;
- try {
- branch = matcher.group("branch");
- } catch (IllegalArgumentException e) {
- // ok no such group
- }
-
- return new GitlabUrl()
- .withHostName(host)
- .withScheme(scheme)
- .withSubGroups(subGroups)
- .withBranch(branch)
- .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames());
+ super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java
new file mode 100644
index 00000000000..3480215a03d
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012-2024 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 javax.inject.Inject;
+import javax.inject.Named;
+import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager;
+import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider;
+import org.eclipse.che.commons.annotation.Nullable;
+
+/**
+ * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects.
+ *
+ * @author Max Shaposhnyk
+ */
+public class GitlabUrlParserSecond extends AbstractGitlabUrlParser {
+
+ private static final String OAUTH_PROVIDER_NAME = "gitlab_2";
+
+ @Inject
+ public GitlabUrlParserSecond(
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl,
+ DevfileFilenamesProvider devfileFilenamesProvider,
+ PersonalAccessTokenManager personalAccessTokenManager) {
+ super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME);
+ }
+}
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
index e276d2c6987..028a46228c5 100644
--- 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
@@ -11,94 +11,22 @@
*/
package org.eclipse.che.api.factory.server.gitlab;
-import static com.google.common.base.Strings.isNullOrEmpty;
-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.factory.server.scm.*;
-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.lang.StringUtils;
-import org.eclipse.che.inject.ConfigurationException;
/** Gitlab OAuth token retriever. */
-public class GitlabUserDataFetcher extends AbstractGitUserDataFetcher {
- private final String apiEndpoint;
+public class GitlabUserDataFetcher extends AbstractGitlabUserDataFetcher {
/** 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,
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl,
@Named("che.api") String apiEndpoint,
PersonalAccessTokenManager personalAccessTokenManager) {
- super(
- OAUTH_PROVIDER_NAME,
- isNullOrEmpty(gitlabEndpoints) ? "https://gitlab.com" : gitlabEndpoints,
- personalAccessTokenManager);
- 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.");
- }
- }
- }
-
- @Override
- protected GitUserData fetchGitUserDataWithOAuthToken(String token)
- throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException,
- ScmUnauthorizedException {
- for (String gitlabServerEndpoint : this.registeredGitlabEndpoints) {
- GitlabUser user = new GitlabApiClient(gitlabServerEndpoint).getUser(token);
- return new GitUserData(user.getName(), user.getEmail());
- }
- throw new ScmCommunicationException("Failed to retrieve git user data from Gitlab");
- }
-
- @Override
- protected GitUserData fetchGitUserDataWithPersonalAccessToken(
- PersonalAccessToken personalAccessToken)
- throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException,
- ScmUnauthorizedException {
- GitlabUser user =
- new GitlabApiClient(personalAccessToken.getScmProviderUrl())
- .getUser(personalAccessToken.getToken());
- return new GitUserData(user.getName(), user.getEmail());
- }
-
- protected String getLocalAuthenticateUrl() {
- return apiEndpoint
- + "/oauth/authenticate?oauth_provider="
- + OAUTH_PROVIDER_NAME
- + "&scope="
- + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES)
- + "&request_method=POST&signature_method=rsa";
+ super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME);
}
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java
new file mode 100644
index 00000000000..a7696cfd91f
--- /dev/null
+++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012-2024 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 javax.inject.Inject;
+import javax.inject.Named;
+import org.eclipse.che.api.factory.server.scm.*;
+import org.eclipse.che.commons.annotation.Nullable;
+
+/** Gitlab OAuth token retriever. */
+public class GitlabUserDataFetcherSecond extends AbstractGitlabUserDataFetcher {
+
+ /** Name of this OAuth provider as found in OAuthAPI. */
+ private static final String OAUTH_PROVIDER_NAME = "gitlab_2";
+
+ @Inject
+ public GitlabUserDataFetcherSecond(
+ @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl,
+ @Named("che.api") String apiEndpoint,
+ PersonalAccessTokenManager personalAccessTokenManager) {
+ super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME);
+ }
+}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java
index 45aab1561b0..926408df4ca 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java
@@ -38,7 +38,6 @@
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.commons.subject.SubjectImpl;
-import org.eclipse.che.inject.ConfigurationException;
import org.eclipse.che.security.oauth.OAuthAPI;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
@@ -64,8 +63,7 @@ void start() {
WireMock.configureFor("localhost", wireMockServer.port());
wireMock = new WireMock("localhost", wireMockServer.port());
oAuthTokenFetcher =
- new GitlabOAuthTokenFetcher(
- wireMockServer.url("/"), wireMockServer.url("/"), "http://che.api", oAuthAPI);
+ new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", oAuthAPI);
}
@AfterMethod
@@ -139,15 +137,6 @@ public void shouldReturnToken() throws Exception {
assertNotNull(token);
}
- @Test(
- expectedExceptions = ConfigurationException.class,
- expectedExceptionsMessageRegExp =
- "GitLab OAuth integration endpoint must be present in registered GitLab endpoints list.")
- public void shouldThrowConfigurationExceptionIfOauthEndpointNotInTheList() throws Exception {
- new GitlabOAuthTokenFetcher(
- wireMockServer.url("/"), "http://foo.bar", "http://che.api", oAuthAPI);
- }
-
@Test(
expectedExceptions = ScmCommunicationException.class,
expectedExceptionsMessageRegExp =
@@ -156,7 +145,7 @@ public void shouldThrowConfigurationExceptionIfOauthEndpointNotInTheList() throw
public void shouldThrowScmCommunicationExceptionWhenNoOauthIsConfigured() throws Exception {
Subject subject = new SubjectImpl("Username", "id1", "token", false);
GitlabOAuthTokenFetcher localFetcher =
- new GitlabOAuthTokenFetcher(wireMockServer.url("/"), null, "http://che.api", oAuthAPI);
+ new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", null);
localFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/"));
}
diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java
index e8022fd043c..28e7159a05f 100644
--- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java
+++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2023 Red Hat, Inc.
+ * Copyright (c) 2012-2024 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/
@@ -58,7 +58,7 @@ public void prepare() {
public void setUp() {
gitlabUrlParser =
new GitlabUrlParser(
- "https://gitlab1.com,https://gitlab.foo.xxx",
+ "https://gitlab1.com",
devfileFilenamesProvider,
mock(PersonalAccessTokenManager.class));
}
@@ -126,14 +126,14 @@ public Object[][] urls() {
return new Object[][] {
{"https://gitlab1.com/user/project/test1.git"},
{"https://gitlab1.com/user/project1.git"},
- {"https://gitlab.foo.xxx/scm/project/test1.git"},
+ {"https://gitlab1.com/scm/project/test1.git"},
{"https://gitlab1.com/user/project/"},
{"https://gitlab1.com/user/project/repo/"},
{"https://gitlab1.com/user/project/-/tree/master/"},
{"https://gitlab1.com/user/project/repo/-/tree/master/subfolder"},
{"git@gitlab1.com:user/project/test1.git"},
{"git@gitlab1.com:user/project1.git"},
- {"git@gitlab.foo.xxx:scm/project/test1.git"},
+ {"git@gitlab1.com:scm/project/test1.git"},
{"git@gitlab1.com:user/project/"},
{"git@gitlab1.com:user/project/repo/"},
};
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
index 5e16f9d0608..96a57b591ad 100644
--- 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
@@ -60,10 +60,7 @@ void start() {
wireMock = new WireMock("localhost", wireMockServer.port());
gitlabUserDataFetcher =
new GitlabUserDataFetcher(
- wireMockServer.url("/"),
- wireMockServer.url("/"),
- "http://che.api",
- personalAccessTokenManager);
+ wireMockServer.url("/"), "http://che.api", personalAccessTokenManager);
stubFor(
get(urlEqualTo("/api/v4/user"))
diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml
index c98aabfd89d..1533c6efa68 100644
--- a/wsmaster/pom.xml
+++ b/wsmaster/pom.xml
@@ -31,6 +31,7 @@
che-core-api-auth-github
che-core-api-auth-github-common
che-core-api-auth-gitlab
+ che-core-api-auth-gitlab-common
che-core-api-auth-openshift
che-core-api-workspace-shared
che-core-api-workspace
@@ -47,6 +48,7 @@
che-core-api-factory-github
che-core-api-factory-github-common
che-core-api-factory-gitlab
+ che-core-api-factory-gitlab-common
che-core-api-factory-bitbucket
che-core-api-factory-bitbucket-server
che-core-api-ssh