From d382f47b5ec821fc1de261cfb7f7eb84ef540b9c Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Thu, 19 Oct 2023 11:12:08 +0200 Subject: [PATCH] feat: Support enabling Github enterprise and SaaS simultaneously on Dev Spaces Signed-off-by: Anatolii Bazko --- .../che/api/deploy/WsMasterModule.java | 6 + .../webapp/WEB-INF/classes/che/che.properties | 6 + .../pom.xml | 2 +- .../OpenShiftGitHubOAuthAuthenticator.java | 2 +- pom.xml | 10 + .../che-core-api-auth-github-common/pom.xml | 70 +++++ ...tractGitHubOAuthAuthenticatorProvider.java | 115 ++++++++ .../oauth/GitHubOAuthAuthenticator.java | 7 +- .../che/security/oauth/GitHubUser.java | 2 +- wsmaster/che-core-api-auth-github/pom.xml | 18 +- .../GitHubOAuthAuthenticatorProvider.java | 82 +----- ...itHubOAuthAuthenticatorProviderSecond.java | 48 ++++ .../che/security/oauth/GithubModule.java | 3 +- .../pom.xml | 94 +++++++ ...stractGithubFactoryParametersResolver.java | 189 +++++++++++++ ...tractGithubPersonalAccessTokenFetcher.java | 261 ++++++++++++++++++ .../github/AbstractGithubScmFileResolver.java | 78 ++++++ .../github/AbstractGithubURLParser.java | 256 +++++++++++++++++ .../github/AbstractGithubUserDataFetcher.java | 75 +++++ .../server/github/GithubApiClient.java | 5 +- .../GithubAuthorizingFileContentProvider.java | 0 .../factory/server/github/GithubCommit.java | 0 .../server/github/GithubPullRequest.java | 0 .../github/GithubSourceStorageBuilder.java | 0 .../api/factory/server/github/GithubUrl.java | 10 +- .../api/factory/server/github/GithubUser.java | 0 wsmaster/che-core-api-factory-github/pom.xml | 20 +- .../GithubFactoryParametersResolver.java | 164 +---------- ...GithubFactoryParametersResolverSecond.java | 51 ++++ .../factory/server/github/GithubModule.java | 3 + .../GithubPersonalAccessTokenFetcher.java | 251 +---------------- ...ithubPersonalAccessTokenFetcherSecond.java | 33 +++ .../server/github/GithubScmFileResolver.java | 55 +--- .../github/GithubScmFileResolverSecond.java | 28 ++ .../server/github/GithubURLParser.java | 242 +--------------- .../server/github/GithubURLParserSecond.java | 46 +++ .../server/github/GithubUserDataFetcher.java | 61 +--- .../github/GithubUserDataFetcherSecond.java | 38 +++ ...hubAuthorizingFileContentProviderTest.java | 10 +- .../GithubFactoryParametersResolverTest.java | 36 +-- .../factory/server/github/GithubUrlTest.java | 6 +- wsmaster/pom.xml | 2 + 42 files changed, 1511 insertions(+), 874 deletions(-) create mode 100644 wsmaster/che-core-api-auth-github-common/pom.xml create mode 100644 wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitHubOAuthAuthenticatorProvider.java rename wsmaster/{che-core-api-auth-github => che-core-api-auth-github-common}/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java (95%) rename wsmaster/{che-core-api-auth-github => che-core-api-auth-github-common}/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java (97%) create mode 100644 wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProviderSecond.java create mode 100644 wsmaster/che-core-api-factory-github-common/pom.xml create mode 100644 wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java create mode 100644 wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java create mode 100644 wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubScmFileResolver.java create mode 100644 wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java create mode 100644 wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java (98%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java (100%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java (100%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java (100%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java (100%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java (97%) rename wsmaster/{che-core-api-factory-github => che-core-api-factory-github-common}/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java (100%) create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherSecond.java create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverSecond.java create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParserSecond.java create mode 100644 wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcherSecond.java 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 08acaa2eaf..9218765b6f 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 @@ -48,7 +48,9 @@ import org.eclipse.che.api.factory.server.bitbucket.BitbucketServerScmFileResolv import org.eclipse.che.api.factory.server.git.ssh.GitSshFactoryParametersResolver; import org.eclipse.che.api.factory.server.git.ssh.GitSshScmFileResolver; import org.eclipse.che.api.factory.server.github.GithubFactoryParametersResolver; +import org.eclipse.che.api.factory.server.github.GithubFactoryParametersResolverSecond; 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.GitlabScmFileResolver; import org.eclipse.che.api.metrics.WsMasterMetricsModule; @@ -169,6 +171,9 @@ public class WsMasterModule extends AbstractModule { Multibinder factoryParametersResolverMultibinder = Multibinder.newSetBinder(binder(), FactoryParametersResolver.class); factoryParametersResolverMultibinder.addBinding().to(GithubFactoryParametersResolver.class); + factoryParametersResolverMultibinder + .addBinding() + .to(GithubFactoryParametersResolverSecond.class); factoryParametersResolverMultibinder .addBinding() .to(BitbucketServerAuthorizingFactoryParametersResolver.class); @@ -185,6 +190,7 @@ public class WsMasterModule extends AbstractModule { Multibinder scmFileResolverResolverMultibinder = Multibinder.newSetBinder(binder(), ScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(GithubScmFileResolver.class); + scmFileResolverResolverMultibinder.addBinding().to(GithubScmFileResolverSecond.class); scmFileResolverResolverMultibinder.addBinding().to(BitbucketScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(BitbucketServerScmFileResolver.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 5bea92e00f..544682f01f 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 @@ -144,22 +144,28 @@ che.oauth.azure.devops.redirecturis=https://${CHE_HOST}/api/oauth/callback # Configuration of the GitHub OAuth2 client. Used to obtain personal access tokens. # Location of the file with GitHub client id. che.oauth2.github.clientid_filepath=NULL +che.oauth2.github.clientid_filepath_2=NULL # Location of the file with GitHub client secret. che.oauth2.github.clientsecret_filepath=NULL +che.oauth2.github.clientsecret_filepath_2=NULL # GitHub OAuth authorization URI. che.oauth.github.authuri= https://github.com/login/oauth/authorize +che.oauth.github.authuri_2= https://github.com/login/oauth/authorize # GitHub OAuth token URI. che.oauth.github.tokenuri= https://github.com/login/oauth/access_token +che.oauth.github.tokenuri_2= https://github.com/login/oauth/access_token # GitHub server address. # Prerequisite: OAuth 2 integration is configured on the GitHub server. che.integration.github.oauth_endpoint=NULL +che.integration.github.oauth_endpoint_2=NULL # GitHub server disable subdomain isolation flag. che.integration.github.disable_subdomain_isolation=false +che.integration.github.disable_subdomain_isolation_2=false # GitHub OAuth redirect URIs. # Separate multiple values with comma, for example: URI,URI,URI. diff --git a/multiuser/keycloak/che-multiuser-keycloak-token-provider/pom.xml b/multiuser/keycloak/che-multiuser-keycloak-token-provider/pom.xml index a8aa3b15f3..3efba8b5f4 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-token-provider/pom.xml +++ b/multiuser/keycloak/che-multiuser-keycloak-token-provider/pom.xml @@ -60,7 +60,7 @@ org.eclipse.che.core - che-core-api-auth-github + che-core-api-auth-github-common org.eclipse.che.core diff --git a/multiuser/keycloak/che-multiuser-keycloak-token-provider/src/main/java/org/eclipse/che/multiuser/keycloak/token/provider/oauth/OpenShiftGitHubOAuthAuthenticator.java b/multiuser/keycloak/che-multiuser-keycloak-token-provider/src/main/java/org/eclipse/che/multiuser/keycloak/token/provider/oauth/OpenShiftGitHubOAuthAuthenticator.java index ed6862f6c4..04114df298 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-token-provider/src/main/java/org/eclipse/che/multiuser/keycloak/token/provider/oauth/OpenShiftGitHubOAuthAuthenticator.java +++ b/multiuser/keycloak/che-multiuser-keycloak-token-provider/src/main/java/org/eclipse/che/multiuser/keycloak/token/provider/oauth/OpenShiftGitHubOAuthAuthenticator.java @@ -33,7 +33,7 @@ public class OpenShiftGitHubOAuthAuthenticator extends GitHubOAuthAuthenticator @Nullable @Named("che.oauth.github.tokenuri") String tokenUri) throws IOException { - super("NULL", "NULL", redirectUris, null, authUri, tokenUri); + super("NULL", "NULL", redirectUris, null, authUri, tokenUri, "github"); if (!isNullOrEmpty(authUri) && !isNullOrEmpty(tokenUri) diff --git a/pom.xml b/pom.xml index abddbb3346..a223e07608 100644 --- a/pom.xml +++ b/pom.xml @@ -687,6 +687,11 @@ che-core-api-auth-github ${che.version} + + org.eclipse.che.core + che-core-api-auth-github-common + ${che.version} + org.eclipse.che.core che-core-api-auth-gitlab @@ -769,6 +774,11 @@ che-core-api-factory-github ${che.version} + + org.eclipse.che.core + che-core-api-factory-github-common + ${che.version} + org.eclipse.che.core che-core-api-factory-gitlab diff --git a/wsmaster/che-core-api-auth-github-common/pom.xml b/wsmaster/che-core-api-auth-github-common/pom.xml new file mode 100644 index 0000000000..3acb7fda12 --- /dev/null +++ b/wsmaster/che-core-api-auth-github-common/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.76.0-SNAPSHOT + + che-core-api-auth-github-common + jar + Che Core :: API :: Authentication Github 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-lang + + + org.slf4j + slf4j-api + + + com.github.tomakehurst + wiremock-jre8-standalone + test + + + org.testng + testng + test + + + org.wiremock + wiremock-standalone + test + + + diff --git a/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitHubOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitHubOAuthAuthenticatorProvider.java new file mode 100644 index 0000000000..08d7ed5ab7 --- /dev/null +++ b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitHubOAuthAuthenticatorProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012-2023 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 static org.eclipse.che.commons.lang.StringUtils.trimEnd; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Provider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration. + * + * @author Pavol Baran + */ +public abstract class AbstractGitHubOAuthAuthenticatorProvider + implements Provider { + private static final Logger LOG = + LoggerFactory.getLogger(AbstractGitHubOAuthAuthenticatorProvider.class); + private final String providerName; + private final OAuthAuthenticator authenticator; + + public AbstractGitHubOAuthAuthenticatorProvider( + String gitHubClientIdPath, + String gitHubClientSecretPath, + String[] redirectUris, + String oauthEndpoint, + String authUri, + String tokenUri, + String providerName) + throws IOException { + this.providerName = providerName; + authenticator = + getOAuthAuthenticator( + gitHubClientIdPath, + gitHubClientSecretPath, + redirectUris, + oauthEndpoint, + authUri, + tokenUri); + LOG.debug("{} GitHub OAuth Authenticator is used.", authenticator); + } + + @Override + public OAuthAuthenticator get() { + return authenticator; + } + + private OAuthAuthenticator getOAuthAuthenticator( + String clientIdPath, + String clientSecretPath, + String[] redirectUris, + String oauthEndpoint, + String authUri, + String tokenUri) + throws IOException { + + String trimmedOauthEndpoint = isNullOrEmpty(oauthEndpoint) ? null : trimEnd(oauthEndpoint, '/'); + authUri = + isNullOrEmpty(trimmedOauthEndpoint) + ? authUri + : trimmedOauthEndpoint + "/login/oauth/authorize"; + tokenUri = + isNullOrEmpty(trimmedOauthEndpoint) + ? tokenUri + : trimmedOauthEndpoint + "/login/oauth/access_token"; + if (!isNullOrEmpty(clientIdPath) + && !isNullOrEmpty(clientSecretPath) + && !isNullOrEmpty(authUri) + && !isNullOrEmpty(tokenUri) + && Objects.nonNull(redirectUris) + && redirectUris.length != 0) { + final String clientId = Files.readString(Path.of(clientIdPath)).trim(); + final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim(); + if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { + return new GitHubOAuthAuthenticator( + clientId, + clientSecret, + redirectUris, + trimmedOauthEndpoint, + authUri, + tokenUri, + 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-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java similarity index 95% rename from wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java rename to wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java index 7cacae7c7a..301e830a2f 100644 --- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java @@ -30,6 +30,7 @@ public class GitHubOAuthAuthenticator extends OAuthAuthenticator { private final String clientSecret; private final String githubApiUrl; private final String providerUrl; + private final String providerName; public GitHubOAuthAuthenticator( String clientId, @@ -37,10 +38,12 @@ public class GitHubOAuthAuthenticator extends OAuthAuthenticator { String[] redirectUris, String authEndpoint, String authUri, - String tokenUri) + String tokenUri, + String providerName) throws IOException { this.clientId = clientId; this.clientSecret = clientSecret; + this.providerName = providerName; providerUrl = isNullOrEmpty(authEndpoint) ? "https://github.com" : trimEnd(authEndpoint, '/'); githubApiUrl = providerUrl.equals("https://github.com") @@ -52,7 +55,7 @@ public class GitHubOAuthAuthenticator extends OAuthAuthenticator { @Override public final String getOAuthProvider() { - return "github"; + return providerName; } @Override diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java similarity index 97% rename from wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java rename to wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java index fbdba5f5f1..ce48134b75 100644 --- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java +++ b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubUser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2023 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-github/pom.xml b/wsmaster/che-core-api-auth-github/pom.xml index 59611099c5..33a87d6bfd 100644 --- a/wsmaster/che-core-api-auth-github/pom.xml +++ b/wsmaster/che-core-api-auth-github/pom.xml @@ -27,10 +27,6 @@ com.google.guava guava - - com.google.http-client - google-http-client - com.google.inject guice @@ -45,7 +41,11 @@ org.eclipse.che.core - che-core-api-auth-shared + che-core-api-auth-github-common + + + org.eclipse.che.core + che-core-api-auth-github-common org.eclipse.che.core @@ -55,14 +55,6 @@ org.eclipse.che.core che-core-commons-inject - - org.eclipse.che.core - che-core-commons-lang - - - org.slf4j - slf4j-api - com.github.tomakehurst wiremock-jre8-standalone diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java index d14c446e6f..85d286d64e 100644 --- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java +++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java @@ -11,20 +11,11 @@ */ package org.eclipse.che.security.oauth; -import static com.google.common.base.Strings.isNullOrEmpty; -import static org.eclipse.che.commons.lang.StringUtils.trimEnd; - import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; 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 GitHub {@link OAuthAuthenticator} based on available configuration. @@ -32,9 +23,8 @@ import org.slf4j.LoggerFactory; * @author Pavol Baran */ @Singleton -public class GitHubOAuthAuthenticatorProvider implements Provider { - private static final Logger LOG = LoggerFactory.getLogger(GitHubOAuthAuthenticatorProvider.class); - private final OAuthAuthenticator authenticator; +public class GitHubOAuthAuthenticatorProvider extends AbstractGitHubOAuthAuthenticatorProvider { + private static final String PROVIDER_NAME = "github"; @Inject public GitHubOAuthAuthenticatorProvider( @@ -45,65 +35,13 @@ public class GitHubOAuthAuthenticatorProvider implements Provider oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProvider.class); + oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProviderSecond.class); } } diff --git a/wsmaster/che-core-api-factory-github-common/pom.xml b/wsmaster/che-core-api-factory-github-common/pom.xml new file mode 100644 index 0000000000..7f4a81da83 --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.76.0-SNAPSHOT + + che-core-api-factory-github-common + jar + Che Core :: API :: Factory Resolver Github Common + + true + + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.guava + guava + + + jakarta.inject + jakarta.inject-api + + + 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-api-workspace-shared + + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-lang + + + org.slf4j + slf4j-api + + + diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java new file mode 100644 index 0000000000..2a4652ec55 --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import static java.util.Collections.emptyMap; +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.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; +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.*; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; +import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; +import org.eclipse.che.security.oauth.AuthorisationRequestManager; + +/** + * Provides Factory Parameters resolver for github repositories. + * + * @author Florent Benoit + */ +public abstract class AbstractGithubFactoryParametersResolver extends BaseFactoryParameterResolver + implements FactoryParametersResolver { + + /** Parser which will allow to check validity of URLs and create objects. */ + private final AbstractGithubURLParser githubUrlParser; + + private final URLFetcher urlFetcher; + + /** Builder allowing to build objects from github URL. */ + private final GithubSourceStorageBuilder githubSourceStorageBuilder; + + private final URLFactoryBuilder urlFactoryBuilder; + + /** ProjectDtoMerger */ + private final ProjectConfigDtoMerger projectConfigDtoMerger; + + private final PersonalAccessTokenManager personalAccessTokenManager; + + private final String providerName; + + public AbstractGithubFactoryParametersResolver( + AbstractGithubURLParser githubUrlParser, + URLFetcher urlFetcher, + GithubSourceStorageBuilder githubSourceStorageBuilder, + AuthorisationRequestManager authorisationRequestManager, + URLFactoryBuilder urlFactoryBuilder, + ProjectConfigDtoMerger projectConfigDtoMerger, + PersonalAccessTokenManager personalAccessTokenManager, + String providerName) { + super(authorisationRequestManager, urlFactoryBuilder, providerName); + this.providerName = providerName; + this.githubUrlParser = githubUrlParser; + this.urlFetcher = urlFetcher; + this.githubSourceStorageBuilder = githubSourceStorageBuilder; + this.urlFactoryBuilder = urlFactoryBuilder; + this.projectConfigDtoMerger = projectConfigDtoMerger; + 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) { + // Check if url parameter is a github URL + return factoryParameters.containsKey(URL_PARAMETER_NAME) + && githubUrlParser.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 GithubUrl githubUrl; + if (getSkipAuthorisation(factoryParameters)) { + githubUrl = + githubUrlParser.parseWithoutAuthentication(factoryParameters.get(URL_PARAMETER_NAME)); + } else { + githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); + } + + return createFactory( + factoryParameters, + githubUrl, + new GithubFactoryVisitor(githubUrl), + new GithubAuthorizingFileContentProvider( + githubUrl, urlFetcher, personalAccessTokenManager)); + } + + /** + * Visitor that puts the default devfile or updates devfile projects into the Github Factory, if + * needed. + */ + private class GithubFactoryVisitor implements FactoryVisitor { + + private final GithubUrl githubUrl; + + private GithubFactoryVisitor(GithubUrl githubUrl) { + this.githubUrl = githubUrl; + } + + @Override + public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { + ScmInfoDto scmInfo = + newDto(ScmInfoDto.class) + .withScmProviderName(githubUrl.getProviderName()) + .withRepositoryUrl(githubUrl.repositoryLocation()); + if (githubUrl.getBranch() != null) { + scmInfo.withBranch(githubUrl.getBranch()); + } + return factoryDto.withScmInfo(scmInfo); + } + + @Override + public FactoryDto visit(FactoryDto factory) { + if (factory.getWorkspace() != null) { + return projectConfigDtoMerger.merge( + factory, + () -> { + // Compute project configuration + return newDto(ProjectConfigDto.class) + .withSource(githubSourceStorageBuilder.buildWorkspaceConfigSource(githubUrl)) + .withName(githubUrl.getRepository()) + .withPath("/".concat(githubUrl.getRepository())); + }); + } else if (factory.getDevfile() == null) { + // initialize default devfile + factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(githubUrl.getRepository())); + } + + updateProjects( + factory.getDevfile(), + () -> + newDto(ProjectDto.class) + .withSource(githubSourceStorageBuilder.buildDevfileSource(githubUrl)) + .withName(githubUrl.getRepository()), + project -> { + final String location = project.getSource().getLocation(); + if (location.equals(githubUrl.repositoryLocation())) { + project.getSource().setBranch(githubUrl.getBranch()); + } + }); + + return factory; + } + } + + @Override + public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { + if (getSkipAuthorisation(emptyMap())) { + return githubUrlParser.parseWithoutAuthentication(factoryUrl); + } else { + return githubUrlParser.parse(factoryUrl); + } + } +} diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java new file mode 100644 index 0000000000..1086a20191 --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.*; +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.*; +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; + +/** GitHub OAuth token retriever. */ +public abstract class AbstractGithubPersonalAccessTokenFetcher + implements PersonalAccessTokenFetcher { + + private static final Logger LOG = + LoggerFactory.getLogger(AbstractGithubPersonalAccessTokenFetcher.class); + private final String apiEndpoint; + private final OAuthAPI oAuthAPI; + + /** GitHub API client. */ + private final GithubApiClient githubApiClient; + + /** Name of this OAuth provider as found in OAuthAPI. */ + private final String providerName; + + /** Collection of OAuth scopes required to make integration with GitHub work. */ + public static final Set DEFAULT_TOKEN_SCOPES = + ImmutableSet.of("repo", "user:email", "read:user", "read:org", "workflow"); + + /** + * Map of OAuth GitHub scopes where each key is a scope and its value is the parent scope. The + * parent scope includes all of its children scopes. This map is used when determining if a token + * has the required scopes. See + * https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes + */ + private static final Map SCOPE_MAP = + ImmutableMap.builderWithExpectedSize(35) + .put("repo", "repo") + .put("repo:status", "repo") + .put("repo_deployment", "repo") + .put("public_repo", "repo") + .put("repo:invite", "repo") + .put("security_events", "repo") + // + .put("workflow", "workflow") + // + .put("write:packages", "write:packages") + .put("read:packages", "write:packages") + // + .put("delete:packages", "delete:packages") + // + .put("admin:org", "admin:org") + .put("write:org", "admin:org") + .put("read:org", "admin:org") + // + .put("admin:public_key", "admin:public_key") + .put("write:public_key", "admin:public_key") + .put("read:public_key", "admin:public_key") + // + .put("admin:repo_hook", "admin:repo_hook") + .put("write:repo_hook", "admin:repo_hook") + .put("read:repo_hook", "admin:repo_hook") + // + .put("admin:org_hook", "admin:org_hook") + // + .put("gist", "gist") + // + .put("notifications", "notifications") + // + .put("user", "user") + .put("read:user", "user") + .put("user:email", "user") + .put("user:follow", "user") + // + .put("delete_repo", "delete_repo") + // + .put("write:discussion", "write:discussion") + .put("read:discussion", "write:discussion") + // + .put("admin:enterprise", "admin:enterprise") + .put("manage_billing:enterprise", "admin:enterprise") + .put("read:enterprise", "admin:enterprise") + // + .put("admin:gpg_key", "admin:gpg_key") + .put("write:gpg_key", "admin:gpg_key") + .put("read:gpg_key", "admin:gpg_key") + .build(); + + /** + * Constructor used for testing only. + * + * @param apiEndpoint + * @param oAuthAPI + * @param githubApiClient + */ + AbstractGithubPersonalAccessTokenFetcher( + String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient, String providerName) { + this.apiEndpoint = apiEndpoint; + this.oAuthAPI = oAuthAPI; + this.githubApiClient = githubApiClient; + this.providerName = providerName; + } + + @Override + public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + OAuthToken oAuthToken; + + if (githubApiClient == null || !githubApiClient.isConnected(scmServerUrl)) { + LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); + return null; + } + try { + oAuthToken = oAuthAPI.getToken(providerName); + String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); + String tokenId = NameGenerator.generate("id-", 5); + Optional> valid = + isValid( + new PersonalAccessTokenParams( + scmServerUrl, tokenName, tokenId, oAuthToken.getToken(), null)); + if (valid.isEmpty()) { + throw new ScmCommunicationException( + "Unable to verify if current token is a valid GitHub token. Token's scm-url needs to be '" + + GithubApiClient.GITHUB_SAAS_ENDPOINT + + "' and was '" + + scmServerUrl + + "'"); + } 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.toString()); + } + return new PersonalAccessToken( + scmServerUrl, + cheSubject.getUserId(), + valid.get().second, + tokenName, + tokenId, + oAuthToken.getToken()); + } catch (UnauthorizedException e) { + throw new ScmUnauthorizedException( + cheSubject.getUserName() + " is not authorized in " + providerName + " OAuth provider.", + providerName, + "2.0", + getLocalAuthenticateUrl()); + } catch (NotFoundException nfe) { + throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); + } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { + LOG.error(e.getMessage()); + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + @Deprecated + public Optional isValid(PersonalAccessToken personalAccessToken) { + if (!githubApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { + LOG.debug("not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); + return Optional.empty(); + } + + try { + if (personalAccessToken.getScmTokenName() != null + && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { + String[] scopes = githubApiClient.getTokenScopes(personalAccessToken.getToken()).second; + return Optional.of(containsScopes(scopes, DEFAULT_TOKEN_SCOPES)); + } else { + // No REST API for PAT-s in Github found yet. Just try to do some action. + GithubUser user = githubApiClient.getUser(personalAccessToken.getToken()); + if (personalAccessToken.getScmUserName().equals(user.getLogin())) { + return Optional.of(Boolean.TRUE); + } else { + return Optional.of(Boolean.FALSE); + } + } + } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + return Optional.of(Boolean.FALSE); + } + } + + @Override + public Optional> isValid(PersonalAccessTokenParams params) { + if (!githubApiClient.isConnected(params.getScmProviderUrl())) { + LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); + return Optional.empty(); + } + try { + if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { + Pair pair = githubApiClient.getTokenScopes(params.getToken()); + return Optional.of( + Pair.of( + containsScopes(pair.second, DEFAULT_TOKEN_SCOPES) ? Boolean.TRUE : Boolean.FALSE, + pair.first)); + } else { + // TODO: add PAT scope validation + // No REST API for PAT-s in Github found yet. Just try to do some action. + GithubUser user = githubApiClient.getUser(params.getToken()); + return Optional.of(Pair.of(Boolean.TRUE, user.getLogin())); + } + } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + return Optional.empty(); + } + } + + /** + * Checks if the tokenScopes array contains the requiredScopes. + * + * @param tokenScopes Scopes from token + * @param requiredScopes Mandatory scopes + * @return If all mandatory scopes are contained in the token's scopes + */ + boolean containsScopes(String[] tokenScopes, Set requiredScopes) { + Arrays.sort(tokenScopes); + // We need check that the token has the required minimal scopes. The scopes can be normalized + // by GitHub, so we need to be careful for sub-scopes being included in parent scopes. + for (String requiredScope : requiredScopes) { + String parentScope = SCOPE_MAP.get(requiredScope); + if (parentScope == null) { + // requiredScope is not recognized as a GitHub scope, so just skip it. + continue; + } + if (Arrays.binarySearch(tokenScopes, parentScope) < 0 + && Arrays.binarySearch(tokenScopes, requiredScope) < 0) { + return false; + } + } + return true; + } + + private 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-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubScmFileResolver.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubScmFileResolver.java new file mode 100644 index 0000000000..f19b9e9f7f --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubScmFileResolver.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; + +import jakarta.validation.constraints.NotNull; +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; + +/** Github specific SCM file resolver. */ +public abstract class AbstractGithubScmFileResolver implements ScmFileResolver { + + private final AbstractGithubURLParser githubUrlParser; + private final URLFetcher urlFetcher; + private final PersonalAccessTokenManager personalAccessTokenManager; + + public AbstractGithubScmFileResolver( + AbstractGithubURLParser githubUrlParser, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + this.githubUrlParser = githubUrlParser; + this.urlFetcher = urlFetcher; + this.personalAccessTokenManager = personalAccessTokenManager; + } + + @Override + public boolean accept(@NotNull String repository) { + // Check if repository parameter is a github URL + return githubUrlParser.isValid(repository); + } + + @Override + public String fileContent(@NotNull String repository, @NotNull String filePath) + throws ApiException { + final GithubUrl githubUrl = githubUrlParser.parse(repository); + try { + return fetchContent(githubUrl, 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(githubUrl, filePath, true); + } catch (DevfileException devfileException) { + throw toApiException(devfileException); + } + } + } + + private String fetchContent(GithubUrl githubUrl, String filePath, boolean skipAuthentication) + throws DevfileException, NotFoundException { + try { + GithubAuthorizingFileContentProvider contentProvider = + new GithubAuthorizingFileContentProvider( + githubUrl, 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-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java new file mode 100644 index 0000000000..94e55d4391 --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import static com.google.common.base.MoreObjects.firstNonNull; +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.api.factory.server.ApiExceptionMapper.toApiException; +import static org.eclipse.che.api.factory.server.github.GithubApiClient.GITHUB_SAAS_ENDPOINT; +import static org.eclipse.che.commons.lang.StringUtils.trimEnd; + +import jakarta.validation.constraints.NotNull; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.che.api.core.ApiException; +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.*; +import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parser of String Github URLs and provide {@link GithubUrl} objects. + * + * @author Florent Benoit + */ +public abstract class AbstractGithubURLParser { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractGithubURLParser.class); + private final PersonalAccessTokenManager tokenManager; + private final DevfileFilenamesProvider devfileFilenamesProvider; + private final GithubApiClient apiClient; + private final String oauthEndpoint; + /** + * Regexp to find repository details (repository name, project name and branch and subfolder) + * Examples of valid URLs are in the test class. + */ + private final Pattern githubPattern; + + private final Pattern githubSSHPattern; + + private final boolean disableSubdomainIsolation; + + private final String providerName; + + /** Constructor used for testing only. */ + AbstractGithubURLParser( + PersonalAccessTokenManager tokenManager, + DevfileFilenamesProvider devfileFilenamesProvider, + GithubApiClient githubApiClient, + String oauthEndpoint, + boolean disableSubdomainIsolation, + String providerName) { + this.tokenManager = tokenManager; + this.devfileFilenamesProvider = devfileFilenamesProvider; + this.apiClient = githubApiClient; + this.oauthEndpoint = oauthEndpoint; + this.disableSubdomainIsolation = disableSubdomainIsolation; + this.providerName = providerName; + + String endpoint = + isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); + + this.githubPattern = + compile( + format( + "^%s/(?[^/]+)/(?[^/]++)((/)|(?:/tree/(?.++))|(/pull/(?\\d++)))?$", + endpoint)); + this.githubSSHPattern = + compile(format("^git@%s:(?.*)/(?.*)$", URI.create(endpoint).getHost())); + } + + public boolean isValid(@NotNull String url) { + String trimmedUrl = trimEnd(url, '/'); + return githubPattern.matcher(trimmedUrl).matches() + || githubSSHPattern.matcher(trimmedUrl).matches(); + } + + public GithubUrl parseWithoutAuthentication(String url) throws ApiException { + return parse(trimEnd(url, '/'), false); + } + + public GithubUrl parse(String url) throws ApiException { + return parse(trimEnd(url, '/'), true); + } + + private GithubUrl parse(String url, boolean authenticationRequired) throws ApiException { + boolean isHTTPSUrl = githubPattern.matcher(url).matches(); + Matcher matcher = isHTTPSUrl ? githubPattern.matcher(url) : githubSSHPattern.matcher(url); + if (!matcher.matches()) { + throw new IllegalArgumentException( + format("The given url %s is not a valid github URL. ", url)); + } + + String serverUrl = + isNullOrEmpty(oauthEndpoint) || trimEnd(oauthEndpoint, '/').equals(GITHUB_SAAS_ENDPOINT) + ? null + : trimEnd(oauthEndpoint, '/'); + String repoUser = matcher.group("repoUser"); + String repoName = matcher.group("repoName"); + if (repoName.matches("^[\\w-][\\w.-]*?\\.git$")) { + repoName = repoName.substring(0, repoName.length() - 4); + } + + String branchName = null; + String pullRequestId = null; + if (isHTTPSUrl) { + branchName = matcher.group("branchName"); + pullRequestId = matcher.group("pullRequestId"); + } + + if (pullRequestId != null) { + GithubPullRequest pullRequest = + this.getPullRequest(pullRequestId, repoUser, repoName, authenticationRequired); + if (pullRequest != null) { + String state = pullRequest.getState(); + if (!"open".equalsIgnoreCase(state)) { + throw new IllegalArgumentException( + format( + "The given Pull Request url %s is not Opened, (found %s), thus it can't be opened as branch may have been removed.", + url, state)); + } + + GithubHead pullRequestHead = pullRequest.getHead(); + repoUser = pullRequestHead.getUser().getLogin(); + repoName = pullRequestHead.getRepo().getName(); + branchName = pullRequestHead.getRef(); + } + } + + String latestCommit = null; + GithubCommit commit = + this.getLatestCommit( + repoUser, repoName, firstNonNull(branchName, "HEAD"), authenticationRequired); + if (commit != null) { + latestCommit = commit.getSha(); + } + + return new GithubUrl(providerName) + .withUsername(repoUser) + .withRepository(repoName) + .setIsHTTPSUrl(isHTTPSUrl) + .withServerUrl(serverUrl) + .withDisableSubdomainIsolation(disableSubdomainIsolation) + .withBranch(branchName) + .withLatestCommit(latestCommit) + .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) + .withUrl(url); + } + + private GithubPullRequest getPullRequest( + String pullRequestId, String repoUser, String repoName, boolean authenticationRequired) + throws ApiException { + try { + // prepare token + String githubEndpoint = + isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); + Subject subject = EnvironmentContext.getCurrent().getSubject(); + PersonalAccessToken personalAccessToken = null; + Optional token = tokenManager.get(subject, githubEndpoint); + if (token.isPresent()) { + personalAccessToken = token.get(); + } else if (authenticationRequired) { + personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); + } + + // get pull request + return this.apiClient.getPullRequest( + pullRequestId, + repoUser, + repoName, + personalAccessToken != null ? personalAccessToken.getToken() : null); + } catch (UnknownScmProviderException e) { + + // get pull request without authentication + try { + return this.apiClient.getPullRequest(pullRequestId, repoUser, repoName, null); + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException exception) { + LOG.error("Failed to authenticate to GitHub", e); + } + + } catch (ScmUnauthorizedException e) { + throw toApiException(e); + } catch (ScmCommunicationException + | UnsatisfiedScmPreconditionException + | ScmConfigurationPersistenceException e) { + LOG.error("Failed to authenticate to GitHub", e); + } catch (ScmItemNotFoundException | ScmBadRequestException e) { + LOG.error("Failed retrieve GitHub Pull Request", e); + } + + return null; + } + + private GithubCommit getLatestCommit( + String repoUser, String repoName, String branchName, boolean authenticationRequired) + throws ApiException { + try { + // prepare token + String githubEndpoint = + isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); + Subject subject = EnvironmentContext.getCurrent().getSubject(); + PersonalAccessToken personalAccessToken = null; + Optional token = tokenManager.get(subject, githubEndpoint); + if (token.isPresent()) { + personalAccessToken = token.get(); + } else if (authenticationRequired) { + personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); + } + + // get latest commit + return this.apiClient.getLatestCommit( + repoUser, + repoName, + branchName, + personalAccessToken != null ? personalAccessToken.getToken() : null); + } catch (UnknownScmProviderException | ScmUnauthorizedException e) { + // get latest commit without authentication + try { + return this.apiClient.getLatestCommit(repoUser, repoName, branchName, null); + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | URISyntaxException exception) { + LOG.error("Failed to authenticate to GitHub", e); + } + } catch (ScmCommunicationException + | UnsatisfiedScmPreconditionException + | ScmConfigurationPersistenceException e) { + LOG.error("Failed to authenticate to GitHub", e); + } catch (ScmItemNotFoundException | ScmBadRequestException | URISyntaxException e) { + LOG.error("Failed to retrieve the latest commit", e); + e.printStackTrace(); + } + + return null; + } +} diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java new file mode 100644 index 0000000000..7ab4c7f2cc --- /dev/null +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.factory.server.scm.AbstractGitUserDataFetcher; +import org.eclipse.che.api.factory.server.scm.GitUserData; +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.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.security.oauth.OAuthAPI; + +/** GitHub user data retriever. */ +public abstract class AbstractGithubUserDataFetcher extends AbstractGitUserDataFetcher { + private final String apiEndpoint; + /** GitHub API client. */ + private final GithubApiClient githubApiClient; + + /** Name of this OAuth provider as found in OAuthAPI. */ + private final String providerName; + /** Collection of OAuth scopes required to make integration with GitHub work. */ + public static final Set DEFAULT_TOKEN_SCOPES = + ImmutableSet.of("repo", "user:email", "read:user"); + + /** Constructor used for testing only. */ + public AbstractGithubUserDataFetcher( + String apiEndpoint, + OAuthAPI oAuthTokenFetcher, + PersonalAccessTokenManager personalAccessTokenManager, + GithubApiClient githubApiClient, + String providerName) { + super(providerName, personalAccessTokenManager, oAuthTokenFetcher); + this.providerName = providerName; + this.githubApiClient = githubApiClient; + this.apiEndpoint = apiEndpoint; + } + + @Override + protected GitUserData fetchGitUserDataWithOAuthToken(OAuthToken oAuthToken) + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + GithubUser user = githubApiClient.getUser(oAuthToken.getToken()); + return new GitUserData(user.getName(), user.getEmail()); + } + + @Override + protected GitUserData fetchGitUserDataWithPersonalAccessToken( + PersonalAccessToken personalAccessToken) + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + GithubUser user = githubApiClient.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-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java similarity index 98% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java index 4aebfe1440..df0e67e79a 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java @@ -12,10 +12,7 @@ package org.eclipse.che.api.factory.server.github; import static com.google.common.base.Strings.isNullOrEmpty; -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.*; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java similarity index 100% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProvider.java diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java similarity index 100% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubCommit.java diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java similarity index 100% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubPullRequest.java diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java similarity index 100% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubSourceStorageBuilder.java diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java similarity index 97% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java index 148bae9bf4..c3066cf332 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java @@ -29,8 +29,8 @@ import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; * @author Florent Benoit */ public class GithubUrl extends DefaultFactoryUrl { - - private final String NAME = "github"; + // TODO + private final String providerName; private static final String HOSTNAME = "https://github.com"; @@ -59,11 +59,13 @@ public class GithubUrl extends DefaultFactoryUrl { * Creation of this instance is made by the parser so user may not need to create a new instance * directly */ - protected GithubUrl() {} + protected GithubUrl(String providerName) { + this.providerName = providerName; + } @Override public String getProviderName() { - return NAME; + return providerName; } /** diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java similarity index 100% rename from wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java rename to wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubUser.java diff --git a/wsmaster/che-core-api-factory-github/pom.xml b/wsmaster/che-core-api-factory-github/pom.xml index bddfacee81..8afaf403a4 100644 --- a/wsmaster/che-core-api-factory-github/pom.xml +++ b/wsmaster/che-core-api-factory-github/pom.xml @@ -26,14 +26,6 @@ true - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - com.google.guava guava @@ -46,10 +38,6 @@ jakarta.inject jakarta.inject-api - - jakarta.validation - jakarta.validation-api - org.eclipse.che.core che-core-api-auth @@ -70,6 +58,10 @@ org.eclipse.che.core che-core-api-factory + + org.eclipse.che.core + che-core-api-factory-github-common + org.eclipse.che.core che-core-api-factory-shared @@ -94,10 +86,6 @@ org.eclipse.che.core che-core-commons-lang - - org.slf4j - slf4j-api - ch.qos.logback logback-classic diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java index f769f8fb32..5721690855 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java @@ -11,30 +11,12 @@ */ package org.eclipse.che.api.factory.server.github; -import static java.util.Collections.emptyMap; -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.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; -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.FactoryDto; -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; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; import org.eclipse.che.security.oauth.AuthorisationRequestManager; /** @@ -43,26 +25,10 @@ import org.eclipse.che.security.oauth.AuthorisationRequestManager; * @author Florent Benoit */ @Singleton -public class GithubFactoryParametersResolver extends BaseFactoryParameterResolver - implements FactoryParametersResolver { +public class GithubFactoryParametersResolver extends AbstractGithubFactoryParametersResolver { private static final String PROVIDER_NAME = "github"; - /** Parser which will allow to check validity of URLs and create objects. */ - private final GithubURLParser githubUrlParser; - - private final URLFetcher urlFetcher; - - /** Builder allowing to build objects from github URL. */ - private final GithubSourceStorageBuilder githubSourceStorageBuilder; - - private final URLFactoryBuilder urlFactoryBuilder; - - /** ProjectDtoMerger */ - private final ProjectConfigDtoMerger projectConfigDtoMerger; - - private final PersonalAccessTokenManager personalAccessTokenManager; - @Inject public GithubFactoryParametersResolver( GithubURLParser githubUrlParser, @@ -72,124 +38,14 @@ public class GithubFactoryParametersResolver extends BaseFactoryParameterResolve URLFactoryBuilder urlFactoryBuilder, ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager) { - super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); - this.githubUrlParser = githubUrlParser; - this.urlFetcher = urlFetcher; - this.githubSourceStorageBuilder = githubSourceStorageBuilder; - this.urlFactoryBuilder = urlFactoryBuilder; - this.projectConfigDtoMerger = projectConfigDtoMerger; - 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) { - // Check if url parameter is a github URL - return factoryParameters.containsKey(URL_PARAMETER_NAME) - && githubUrlParser.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 GithubUrl githubUrl; - if (getSkipAuthorisation(factoryParameters)) { - githubUrl = - githubUrlParser.parseWithoutAuthentication(factoryParameters.get(URL_PARAMETER_NAME)); - } else { - githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); - } - - return createFactory( - factoryParameters, - githubUrl, - new GithubFactoryVisitor(githubUrl), - new GithubAuthorizingFileContentProvider( - githubUrl, urlFetcher, personalAccessTokenManager)); - } - - /** - * Visitor that puts the default devfile or updates devfile projects into the Github Factory, if - * needed. - */ - private class GithubFactoryVisitor implements FactoryVisitor { - - private final GithubUrl githubUrl; - - private GithubFactoryVisitor(GithubUrl githubUrl) { - this.githubUrl = githubUrl; - } - - @Override - public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { - ScmInfoDto scmInfo = - newDto(ScmInfoDto.class) - .withScmProviderName(githubUrl.getProviderName()) - .withRepositoryUrl(githubUrl.repositoryLocation()); - if (githubUrl.getBranch() != null) { - scmInfo.withBranch(githubUrl.getBranch()); - } - return factoryDto.withScmInfo(scmInfo); - } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getWorkspace() != null) { - return projectConfigDtoMerger.merge( - factory, - () -> { - // Compute project configuration - return newDto(ProjectConfigDto.class) - .withSource(githubSourceStorageBuilder.buildWorkspaceConfigSource(githubUrl)) - .withName(githubUrl.getRepository()) - .withPath("/".concat(githubUrl.getRepository())); - }); - } else if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(githubUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource(githubSourceStorageBuilder.buildDevfileSource(githubUrl)) - .withName(githubUrl.getRepository()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(githubUrl.repositoryLocation())) { - project.getSource().setBranch(githubUrl.getBranch()); - } - }); - - return factory; - } - } - - @Override - public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { - if (getSkipAuthorisation(emptyMap())) { - return githubUrlParser.parseWithoutAuthentication(factoryUrl); - } else { - return githubUrlParser.parse(factoryUrl); - } + super( + githubUrlParser, + urlFetcher, + githubSourceStorageBuilder, + authorisationRequestManager, + urlFactoryBuilder, + projectConfigDtoMerger, + personalAccessTokenManager, + PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java new file mode 100644 index 0000000000..5103711d48 --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; +import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.security.oauth.AuthorisationRequestManager; + +/** + * Provides Factory Parameters resolver for github repositories. + * + * @author Florent Benoit + */ +@Singleton +public class GithubFactoryParametersResolverSecond extends AbstractGithubFactoryParametersResolver { + + private static final String PROVIDER_NAME = "github_2"; + + @Inject + public GithubFactoryParametersResolverSecond( + GithubURLParserSecond githubUrlParser, + URLFetcher urlFetcher, + GithubSourceStorageBuilder githubSourceStorageBuilder, + AuthorisationRequestManager authorisationRequestManager, + URLFactoryBuilder urlFactoryBuilder, + ProjectConfigDtoMerger projectConfigDtoMerger, + PersonalAccessTokenManager personalAccessTokenManager) { + super( + githubUrlParser, + urlFetcher, + githubSourceStorageBuilder, + authorisationRequestManager, + urlFactoryBuilder, + projectConfigDtoMerger, + personalAccessTokenManager, + PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java index b5254194ac..e753514887 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubModule.java @@ -23,8 +23,11 @@ public class GithubModule extends AbstractModule { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcher.class); + tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcherSecond.class); + Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GithubUserDataFetcher.class); + gitUserDataMultibinder.addBinding().to(GithubUserDataFetcherSecond.class); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java index 2508a0020d..7d28581f57 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcher.java @@ -11,270 +11,27 @@ */ package org.eclipse.che.api.factory.server.github; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import java.util.Arrays; -import java.util.Map; -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.subject.Subject; import org.eclipse.che.security.oauth.OAuthAPI; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** GitHub OAuth token retriever. */ -public class GithubPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { - - private static final Logger LOG = LoggerFactory.getLogger(GithubPersonalAccessTokenFetcher.class); - private final String apiEndpoint; - private final OAuthAPI oAuthAPI; - - /** GitHub API client. */ - private final GithubApiClient githubApiClient; +public class GithubPersonalAccessTokenFetcher extends AbstractGithubPersonalAccessTokenFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github"; - /** Collection of OAuth scopes required to make integration with GitHub work. */ - public static final Set DEFAULT_TOKEN_SCOPES = - ImmutableSet.of("repo", "user:email", "read:user", "read:org", "workflow"); - - /** - * Map of OAuth GitHub scopes where each key is a scope and its value is the parent scope. The - * parent scope includes all of its children scopes. This map is used when determining if a token - * has the required scopes. See - * https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes - */ - private static final Map SCOPE_MAP = - ImmutableMap.builderWithExpectedSize(35) - .put("repo", "repo") - .put("repo:status", "repo") - .put("repo_deployment", "repo") - .put("public_repo", "repo") - .put("repo:invite", "repo") - .put("security_events", "repo") - // - .put("workflow", "workflow") - // - .put("write:packages", "write:packages") - .put("read:packages", "write:packages") - // - .put("delete:packages", "delete:packages") - // - .put("admin:org", "admin:org") - .put("write:org", "admin:org") - .put("read:org", "admin:org") - // - .put("admin:public_key", "admin:public_key") - .put("write:public_key", "admin:public_key") - .put("read:public_key", "admin:public_key") - // - .put("admin:repo_hook", "admin:repo_hook") - .put("write:repo_hook", "admin:repo_hook") - .put("read:repo_hook", "admin:repo_hook") - // - .put("admin:org_hook", "admin:org_hook") - // - .put("gist", "gist") - // - .put("notifications", "notifications") - // - .put("user", "user") - .put("read:user", "user") - .put("user:email", "user") - .put("user:follow", "user") - // - .put("delete_repo", "delete_repo") - // - .put("write:discussion", "write:discussion") - .put("read:discussion", "write:discussion") - // - .put("admin:enterprise", "admin:enterprise") - .put("manage_billing:enterprise", "admin:enterprise") - .put("read:enterprise", "admin:enterprise") - // - .put("admin:gpg_key", "admin:gpg_key") - .put("write:gpg_key", "admin:gpg_key") - .put("read:gpg_key", "admin:gpg_key") - .build(); - @Inject public GithubPersonalAccessTokenFetcher( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, OAuthAPI oAuthAPI) { - this(apiEndpoint, oAuthAPI, new GithubApiClient(oauthEndpoint)); + super(apiEndpoint, oAuthAPI, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); } - /** - * Constructor used for testing only. - * - * @param apiEndpoint - * @param oAuthAPI - * @param githubApiClient - */ GithubPersonalAccessTokenFetcher( - String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient) { - this.apiEndpoint = apiEndpoint; - this.oAuthAPI = oAuthAPI; - this.githubApiClient = githubApiClient; - } - - @Override - public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) - throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { - OAuthToken oAuthToken; - - if (githubApiClient == null || !githubApiClient.isConnected(scmServerUrl)) { - LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); - return null; - } - try { - oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); - String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); - String tokenId = NameGenerator.generate("id-", 5); - Optional> valid = - isValid( - new PersonalAccessTokenParams( - scmServerUrl, 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.toString()); - } - return new PersonalAccessToken( - scmServerUrl, - 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.error(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 - @Deprecated - public Optional isValid(PersonalAccessToken personalAccessToken) { - if (!githubApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { - LOG.debug("not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); - return Optional.empty(); - } - - try { - if (personalAccessToken.getScmTokenName() != null - && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { - String[] scopes = githubApiClient.getTokenScopes(personalAccessToken.getToken()).second; - return Optional.of(containsScopes(scopes, DEFAULT_TOKEN_SCOPES)); - } else { - // No REST API for PAT-s in Github found yet. Just try to do some action. - GithubUser user = githubApiClient.getUser(personalAccessToken.getToken()); - if (personalAccessToken.getScmUserName().equals(user.getLogin())) { - return Optional.of(Boolean.TRUE); - } else { - return Optional.of(Boolean.FALSE); - } - } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { - return Optional.of(Boolean.FALSE); - } - } - - @Override - public Optional> isValid(PersonalAccessTokenParams params) { - if (!githubApiClient.isConnected(params.getScmProviderUrl())) { - LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); - return Optional.empty(); - } - try { - if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { - Pair pair = githubApiClient.getTokenScopes(params.getToken()); - return Optional.of( - Pair.of( - containsScopes(pair.second, DEFAULT_TOKEN_SCOPES) ? Boolean.TRUE : Boolean.FALSE, - pair.first)); - } else { - // TODO: add PAT scope validation - // No REST API for PAT-s in Github found yet. Just try to do some action. - GithubUser user = githubApiClient.getUser(params.getToken()); - return Optional.of(Pair.of(Boolean.TRUE, user.getLogin())); - } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { - return Optional.empty(); - } - } - - /** - * Checks if the tokenScopes array contains the requiredScopes. - * - * @param tokenScopes Scopes from token - * @param requiredScopes Mandatory scopes - * @return If all mandatory scopes are contained in the token's scopes - */ - boolean containsScopes(String[] tokenScopes, Set requiredScopes) { - Arrays.sort(tokenScopes); - // We need check that the token has the required minimal scopes. The scopes can be normalized - // by GitHub, so we need to be careful for sub-scopes being included in parent scopes. - for (String requiredScope : requiredScopes) { - String parentScope = SCOPE_MAP.get(requiredScope); - if (parentScope == null) { - // requiredScope is not recognized as a GitHub scope, so just skip it. - continue; - } - if (Arrays.binarySearch(tokenScopes, parentScope) < 0 - && Arrays.binarySearch(tokenScopes, requiredScope) < 0) { - return false; - } - } - return true; - } - - 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"; + @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI, GithubApiClient githubApiClient) { + super(apiEndpoint, oAuthAPI, githubApiClient, OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherSecond.java new file mode 100644 index 0000000000..f80531964a --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherSecond.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.security.oauth.OAuthAPI; + +/** GitHub OAuth token retriever. */ +public class GithubPersonalAccessTokenFetcherSecond + extends AbstractGithubPersonalAccessTokenFetcher { + + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "github_2"; + + @Inject + public GithubPersonalAccessTokenFetcherSecond( + @Named("che.api") String apiEndpoint, + @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, + OAuthAPI oAuthAPI) { + super(apiEndpoint, oAuthAPI, new GithubApiClient(oauthEndpoint), OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolver.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolver.java index 01b51f7a24..c4c70f3319 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolver.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolver.java @@ -11,69 +11,18 @@ */ package org.eclipse.che.api.factory.server.github; -import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; - -import jakarta.validation.constraints.NotNull; -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; /** Github specific SCM file resolver. */ -public class GithubScmFileResolver implements ScmFileResolver { - - private final GithubURLParser githubUrlParser; - private final URLFetcher urlFetcher; - private final PersonalAccessTokenManager personalAccessTokenManager; +public class GithubScmFileResolver extends AbstractGithubScmFileResolver { @Inject public GithubScmFileResolver( GithubURLParser githubUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { - this.githubUrlParser = githubUrlParser; - this.urlFetcher = urlFetcher; - this.personalAccessTokenManager = personalAccessTokenManager; - } - - @Override - public boolean accept(@NotNull String repository) { - // Check if repository parameter is a github URL - return githubUrlParser.isValid(repository); - } - - @Override - public String fileContent(@NotNull String repository, @NotNull String filePath) - throws ApiException { - final GithubUrl githubUrl = githubUrlParser.parse(repository); - try { - return fetchContent(githubUrl, 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(githubUrl, filePath, true); - } catch (DevfileException devfileException) { - throw toApiException(devfileException); - } - } - } - - private String fetchContent(GithubUrl githubUrl, String filePath, boolean skipAuthentication) - throws DevfileException, NotFoundException { - try { - GithubAuthorizingFileContentProvider contentProvider = - new GithubAuthorizingFileContentProvider( - githubUrl, urlFetcher, personalAccessTokenManager); - return skipAuthentication - ? contentProvider.fetchContentWithoutAuthentication(filePath) - : contentProvider.fetchContent(filePath); - } catch (IOException e) { - throw new NotFoundException(e.getMessage()); - } + super(githubUrlParser, urlFetcher, personalAccessTokenManager); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverSecond.java new file mode 100644 index 0000000000..d3a3dd9fb9 --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubScmFileResolverSecond.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import javax.inject.Inject; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** Github specific SCM file resolver. */ +public class GithubScmFileResolverSecond extends AbstractGithubScmFileResolver { + + @Inject + public GithubScmFileResolverSecond( + GithubURLParserSecond githubUrlParser, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + super(githubUrlParser, urlFetcher, personalAccessTokenManager); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParser.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParser.java index a8ea88b991..0bb94e7315 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParser.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParser.java @@ -11,39 +11,12 @@ */ package org.eclipse.che.api.factory.server.github; -import static com.google.common.base.MoreObjects.firstNonNull; -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.api.factory.server.ApiExceptionMapper.toApiException; -import static org.eclipse.che.api.factory.server.github.GithubApiClient.GITHUB_SAAS_ENDPOINT; -import static org.eclipse.che.commons.lang.StringUtils.trimEnd; - -import jakarta.validation.constraints.NotNull; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.eclipse.che.api.core.ApiException; -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.ScmBadRequestException; -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.scm.exception.UnknownScmProviderException; -import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.subject.Subject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Parser of String Github URLs and provide {@link GithubUrl} objects. @@ -51,22 +24,10 @@ import org.slf4j.LoggerFactory; * @author Florent Benoit */ @Singleton -public class GithubURLParser { +public class GithubURLParser extends AbstractGithubURLParser { - private static final Logger LOG = LoggerFactory.getLogger(GithubURLParser.class); - private final PersonalAccessTokenManager tokenManager; - private final DevfileFilenamesProvider devfileFilenamesProvider; - private final GithubApiClient apiClient; - private final String oauthEndpoint; - /** - * Regexp to find repository details (repository name, project name and branch and subfolder) - * Examples of valid URLs are in the test class. - */ - private final Pattern githubPattern; - - private final Pattern githubSSHPattern; - - private final boolean disableSubdomainIsolation; + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "github"; @Inject public GithubURLParser( @@ -75,204 +36,27 @@ public class GithubURLParser { @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, @Named("che.integration.github.disable_subdomain_isolation") boolean disableSubdomainIsolation) { - this( + super( tokenManager, devfileFilenamesProvider, new GithubApiClient(oauthEndpoint), oauthEndpoint, - disableSubdomainIsolation); + disableSubdomainIsolation, + OAUTH_PROVIDER_NAME); } - /** Constructor used for testing only. */ GithubURLParser( PersonalAccessTokenManager tokenManager, DevfileFilenamesProvider devfileFilenamesProvider, GithubApiClient githubApiClient, String oauthEndpoint, boolean disableSubdomainIsolation) { - this.tokenManager = tokenManager; - this.devfileFilenamesProvider = devfileFilenamesProvider; - this.apiClient = githubApiClient; - this.oauthEndpoint = oauthEndpoint; - this.disableSubdomainIsolation = disableSubdomainIsolation; - - String endpoint = - isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); - - this.githubPattern = - compile( - format( - "^%s/(?[^/]+)/(?[^/]++)((/)|(?:/tree/(?.++))|(/pull/(?\\d++)))?$", - endpoint)); - this.githubSSHPattern = - compile(format("^git@%s:(?.*)/(?.*)$", URI.create(endpoint).getHost())); - } - - public boolean isValid(@NotNull String url) { - String trimmedUrl = trimEnd(url, '/'); - return githubPattern.matcher(trimmedUrl).matches() - || githubSSHPattern.matcher(trimmedUrl).matches(); - } - - public GithubUrl parseWithoutAuthentication(String url) throws ApiException { - return parse(trimEnd(url, '/'), false); - } - - public GithubUrl parse(String url) throws ApiException { - return parse(trimEnd(url, '/'), true); - } - - private GithubUrl parse(String url, boolean authenticationRequired) throws ApiException { - boolean isHTTPSUrl = githubPattern.matcher(url).matches(); - Matcher matcher = isHTTPSUrl ? githubPattern.matcher(url) : githubSSHPattern.matcher(url); - if (!matcher.matches()) { - throw new IllegalArgumentException( - format("The given url %s is not a valid github URL. ", url)); - } - - String serverUrl = - isNullOrEmpty(oauthEndpoint) || trimEnd(oauthEndpoint, '/').equals(GITHUB_SAAS_ENDPOINT) - ? null - : trimEnd(oauthEndpoint, '/'); - String repoUser = matcher.group("repoUser"); - String repoName = matcher.group("repoName"); - if (repoName.matches("^[\\w-][\\w.-]*?\\.git$")) { - repoName = repoName.substring(0, repoName.length() - 4); - } - - String branchName = null; - String pullRequestId = null; - if (isHTTPSUrl) { - branchName = matcher.group("branchName"); - pullRequestId = matcher.group("pullRequestId"); - } - - if (pullRequestId != null) { - GithubPullRequest pullRequest = - this.getPullRequest(pullRequestId, repoUser, repoName, authenticationRequired); - if (pullRequest != null) { - String state = pullRequest.getState(); - if (!"open".equalsIgnoreCase(state)) { - throw new IllegalArgumentException( - format( - "The given Pull Request url %s is not Opened, (found %s), thus it can't be opened as branch may have been removed.", - url, state)); - } - - GithubHead pullRequestHead = pullRequest.getHead(); - repoUser = pullRequestHead.getUser().getLogin(); - repoName = pullRequestHead.getRepo().getName(); - branchName = pullRequestHead.getRef(); - } - } - - String latestCommit = null; - GithubCommit commit = - this.getLatestCommit( - repoUser, repoName, firstNonNull(branchName, "HEAD"), authenticationRequired); - if (commit != null) { - latestCommit = commit.getSha(); - } - - return new GithubUrl() - .withUsername(repoUser) - .withRepository(repoName) - .setIsHTTPSUrl(isHTTPSUrl) - .withServerUrl(serverUrl) - .withDisableSubdomainIsolation(disableSubdomainIsolation) - .withBranch(branchName) - .withLatestCommit(latestCommit) - .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()) - .withUrl(url); - } - - private GithubPullRequest getPullRequest( - String pullRequestId, String repoUser, String repoName, boolean authenticationRequired) - throws ApiException { - try { - // prepare token - String githubEndpoint = - isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); - Subject subject = EnvironmentContext.getCurrent().getSubject(); - PersonalAccessToken personalAccessToken = null; - Optional token = tokenManager.get(subject, githubEndpoint); - if (token.isPresent()) { - personalAccessToken = token.get(); - } else if (authenticationRequired) { - personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); - } - - // get pull request - return this.apiClient.getPullRequest( - pullRequestId, - repoUser, - repoName, - personalAccessToken != null ? personalAccessToken.getToken() : null); - } catch (UnknownScmProviderException e) { - - // get pull request without authentication - try { - return this.apiClient.getPullRequest(pullRequestId, repoUser, repoName, null); - } catch (ScmItemNotFoundException - | ScmCommunicationException - | ScmBadRequestException exception) { - LOG.error("Failed to authenticate to GitHub", e); - } - - } catch (ScmUnauthorizedException e) { - throw toApiException(e); - } catch (ScmCommunicationException - | UnsatisfiedScmPreconditionException - | ScmConfigurationPersistenceException e) { - LOG.error("Failed to authenticate to GitHub", e); - } catch (ScmItemNotFoundException | ScmBadRequestException e) { - LOG.error("Failed retrieve GitHub Pull Request", e); - } - - return null; - } - - private GithubCommit getLatestCommit( - String repoUser, String repoName, String branchName, boolean authenticationRequired) - throws ApiException { - try { - // prepare token - String githubEndpoint = - isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); - Subject subject = EnvironmentContext.getCurrent().getSubject(); - PersonalAccessToken personalAccessToken = null; - Optional token = tokenManager.get(subject, githubEndpoint); - if (token.isPresent()) { - personalAccessToken = token.get(); - } else if (authenticationRequired) { - personalAccessToken = tokenManager.fetchAndSave(subject, githubEndpoint); - } - - // get latest commit - return this.apiClient.getLatestCommit( - repoUser, - repoName, - branchName, - personalAccessToken != null ? personalAccessToken.getToken() : null); - } catch (UnknownScmProviderException | ScmUnauthorizedException e) { - // get latest commit without authentication - try { - return this.apiClient.getLatestCommit(repoUser, repoName, branchName, null); - } catch (ScmItemNotFoundException - | ScmCommunicationException - | ScmBadRequestException - | URISyntaxException exception) { - LOG.error("Failed to authenticate to GitHub", e); - } - } catch (ScmCommunicationException - | UnsatisfiedScmPreconditionException - | ScmConfigurationPersistenceException e) { - LOG.error("Failed to authenticate to GitHub", e); - } catch (ScmItemNotFoundException | ScmBadRequestException | URISyntaxException e) { - LOG.error("Failed to retrieve the latest commit", e); - e.printStackTrace(); - } - - return null; + super( + tokenManager, + devfileFilenamesProvider, + githubApiClient, + oauthEndpoint, + disableSubdomainIsolation, + OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParserSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParserSecond.java new file mode 100644 index 0000000000..04f7d55a5b --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubURLParserSecond.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +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 Github URLs and provide {@link GithubUrl} objects. + * + * @author Florent Benoit + */ +@Singleton +public class GithubURLParserSecond extends AbstractGithubURLParser { + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "github_2"; + + @Inject + public GithubURLParserSecond( + PersonalAccessTokenManager tokenManager, + DevfileFilenamesProvider devfileFilenamesProvider, + @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, + @Named("che.integration.github.disable_subdomain_isolation_2") + boolean disableSubdomainIsolation) { + super( + tokenManager, + devfileFilenamesProvider, + new GithubApiClient(oauthEndpoint), + oauthEndpoint, + disableSubdomainIsolation, + OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java index 3b319cc988..49d91c7cd5 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcher.java @@ -11,80 +11,41 @@ */ package org.eclipse.che.api.factory.server.github; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableSet; -import java.util.Set; import javax.inject.Inject; import javax.inject.Named; -import org.eclipse.che.api.auth.shared.dto.OAuthToken; -import org.eclipse.che.api.factory.server.scm.AbstractGitUserDataFetcher; -import org.eclipse.che.api.factory.server.scm.GitUserData; -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.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.commons.annotation.Nullable; import org.eclipse.che.security.oauth.OAuthAPI; /** GitHub user data retriever. */ -public class GithubUserDataFetcher extends AbstractGitUserDataFetcher { - private final String apiEndpoint; - /** GitHub API client. */ - private final GithubApiClient githubApiClient; - +public class GithubUserDataFetcher extends AbstractGithubUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "github"; - /** Collection of OAuth scopes required to make integration with GitHub work. */ - public static final Set DEFAULT_TOKEN_SCOPES = - ImmutableSet.of("repo", "user:email", "read:user"); - @Inject public GithubUserDataFetcher( @Named("che.api") String apiEndpoint, @Nullable @Named("che.integration.github.oauth_endpoint") String oauthEndpoint, OAuthAPI oAuthTokenFetcher, PersonalAccessTokenManager personalAccessTokenManager) { - this( + super( apiEndpoint, oAuthTokenFetcher, personalAccessTokenManager, - new GithubApiClient(oauthEndpoint)); + new GithubApiClient(oauthEndpoint), + OAUTH_PROVIDER_NAME); } - /** Constructor used for testing only. */ - public GithubUserDataFetcher( + GithubUserDataFetcher( String apiEndpoint, OAuthAPI oAuthTokenFetcher, PersonalAccessTokenManager personalAccessTokenManager, GithubApiClient githubApiClient) { - super(OAUTH_PROVIDER_NAME, personalAccessTokenManager, oAuthTokenFetcher); - this.githubApiClient = githubApiClient; - this.apiEndpoint = apiEndpoint; - } - - @Override - protected GitUserData fetchGitUserDataWithOAuthToken(OAuthToken oAuthToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { - GithubUser user = githubApiClient.getUser(oAuthToken.getToken()); - return new GitUserData(user.getName(), user.getEmail()); - } - - @Override - protected GitUserData fetchGitUserDataWithPersonalAccessToken( - PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { - GithubUser user = githubApiClient.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( + apiEndpoint, + oAuthTokenFetcher, + personalAccessTokenManager, + githubApiClient, + OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcherSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcherSecond.java new file mode 100644 index 0000000000..2b707e8f03 --- /dev/null +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUserDataFetcherSecond.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012-2023 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.github; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.security.oauth.OAuthAPI; + +/** GitHub user data retriever. */ +public class GithubUserDataFetcherSecond extends AbstractGithubUserDataFetcher { + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "github_2"; + + @Inject + public GithubUserDataFetcherSecond( + @Named("che.api") String apiEndpoint, + @Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint, + OAuthAPI oAuthTokenFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + super( + apiEndpoint, + oAuthTokenFetcher, + personalAccessTokenManager, + new GithubApiClient(oauthEndpoint), + OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProviderTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProviderTest.java index 1cd4564839..9fd553183b 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProviderTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubAuthorizingFileContentProviderTest.java @@ -45,7 +45,7 @@ public class GithubAuthorizingFileContentProviderTest { URLFetcher urlFetcher = mock(URLFetcher.class); GithubUrl githubUrl = - new GithubUrl() + new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withBranch("main") @@ -71,7 +71,7 @@ public class GithubAuthorizingFileContentProviderTest { String raw_url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; GithubUrl githubUrl = - new GithubUrl() + new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withBranch("main") @@ -92,7 +92,7 @@ public class GithubAuthorizingFileContentProviderTest { public void shouldThrowNotFoundForPublicRepos() throws Exception { String url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; - GithubUrl githubUrl = new GithubUrl().withUsername("eclipse").withRepository("che"); + GithubUrl githubUrl = new GithubUrl("github").withUsername("eclipse").withRepository("che"); URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); FileContentProvider fileContentProvider = @@ -109,7 +109,7 @@ public class GithubAuthorizingFileContentProviderTest { @Test(expectedExceptions = DevfileException.class) public void shouldThrowDevfileException() throws Exception { String url = "https://raw.githubusercontent.com/foo/bar/branch-name/devfile.yaml"; - GithubUrl githubUrl = new GithubUrl().withUsername("eclipse").withRepository("che"); + GithubUrl githubUrl = new GithubUrl("github").withUsername("eclipse").withRepository("che"); URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); FileContentProvider fileContentProvider = @@ -128,7 +128,7 @@ public class GithubAuthorizingFileContentProviderTest { String raw_url = "https://ghserver.com/foo/bar/branch-name/devfile.yaml"; URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); - GithubUrl githubUrl = new GithubUrl().withUsername("eclipse").withRepository("che"); + GithubUrl githubUrl = new GithubUrl("github").withUsername("eclipse").withRepository("che"); FileContentProvider fileContentProvider = new GithubAuthorizingFileContentProvider(githubUrl, urlFetcher, personalAccessTokenManager); var personalAccessToken = new PersonalAccessToken(raw_url, "che", "my-token"); diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java index 853ed13f61..387acb6cfb 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java @@ -99,7 +99,7 @@ public class GithubFactoryParametersResolverTest { @Captor private ArgumentCaptor factoryUrlArgumentCaptor; /** Instance of resolver that will be tested. */ - private GithubFactoryParametersResolver githubFactoryParametersResolver; + private AbstractGithubFactoryParametersResolver abstractGithubFactoryParametersResolver; @BeforeMethod protected void init() throws Exception { @@ -110,7 +110,7 @@ public class GithubFactoryParametersResolverTest { new GithubURLParser( personalAccessTokenManager, devfileFilenamesProvider, githubApiClient, null, false); - githubFactoryParametersResolver = + abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, @@ -119,14 +119,14 @@ public class GithubFactoryParametersResolverTest { urlFactoryBuilder, projectConfigDtoMerger, personalAccessTokenManager); - assertNotNull(this.githubFactoryParametersResolver); + assertNotNull(this.abstractGithubFactoryParametersResolver); } /** Check missing parameter name can't be accepted by this resolver */ @Test public void checkMissingParameter() { Map parameters = singletonMap("foo", "this is a foo bar"); - boolean accept = githubFactoryParametersResolver.accept(parameters); + boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } @@ -135,7 +135,7 @@ public class GithubFactoryParametersResolverTest { @Test public void checkInvalidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "http://www.eclipse.org/che"); - boolean accept = githubFactoryParametersResolver.accept(parameters); + boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertFalse(accept); } @@ -145,7 +145,7 @@ public class GithubFactoryParametersResolverTest { public void checkValidAcceptUrl() { Map parameters = singletonMap(URL_PARAMETER_NAME, "https://github.com/codenvy/codenvy.git"); - boolean accept = githubFactoryParametersResolver.accept(parameters); + boolean accept = abstractGithubFactoryParametersResolver.accept(parameters); // shouldn't be accepted assertTrue(accept); } @@ -168,7 +168,7 @@ public class GithubFactoryParametersResolverTest { Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when - FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params); + FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder).buildDefaultDevfile(eq("che")); assertEquals(factory, computedFactory); @@ -193,7 +193,7 @@ public class GithubFactoryParametersResolverTest { "https://github.com/eclipse/che", ERROR_QUERY_NAME, "access_denied"); - githubFactoryParametersResolver.createFactory(params); + abstractGithubFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) .createFactoryFromDevfile( @@ -210,7 +210,7 @@ public class GithubFactoryParametersResolverTest { .thenReturn(new GithubCommit().withSha("test-sha")); // when - githubFactoryParametersResolver.createFactory( + abstractGithubFactoryParametersResolver.createFactory( ImmutableMap.of(URL_PARAMETER_NAME, "https://github.com/eclipse/che")); // then verify(urlFactoryBuilder) @@ -237,7 +237,7 @@ public class GithubFactoryParametersResolverTest { Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when - FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params); + FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); assertNull(factory.getWorkspace()); @@ -268,7 +268,7 @@ public class GithubFactoryParametersResolverTest { Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when - FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params); + FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); @@ -299,7 +299,7 @@ public class GithubFactoryParametersResolverTest { Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when - FactoryDto factory = (FactoryDto) githubFactoryParametersResolver.createFactory(params); + FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); @@ -323,7 +323,7 @@ public class GithubFactoryParametersResolverTest { Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when FactoryDevfileV2Dto factory = - (FactoryDevfileV2Dto) githubFactoryParametersResolver.createFactory(params); + (FactoryDevfileV2Dto) abstractGithubFactoryParametersResolver.createFactory(params); // then ScmInfo scmInfo = factory.getScmInfo(); assertNotNull(scmInfo); @@ -343,7 +343,7 @@ public class GithubFactoryParametersResolverTest { .thenReturn(Optional.of(generateDevfileFactory())); // when - githubFactoryParametersResolver.createFactory(params); + abstractGithubFactoryParametersResolver.createFactory(params); // then verify(urlFactoryBuilder) @@ -359,7 +359,7 @@ public class GithubFactoryParametersResolverTest { // given githubUrlParser = mock(GithubURLParser.class); - githubFactoryParametersResolver = + abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, @@ -370,7 +370,7 @@ public class GithubFactoryParametersResolverTest { personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(true); // when - githubFactoryParametersResolver.parseFactoryUrl("url"); + abstractGithubFactoryParametersResolver.parseFactoryUrl("url"); // then verify(githubUrlParser).parseWithoutAuthentication("url"); verify(githubUrlParser, never()).parse("url"); @@ -381,7 +381,7 @@ public class GithubFactoryParametersResolverTest { // given githubUrlParser = mock(GithubURLParser.class); - githubFactoryParametersResolver = + abstractGithubFactoryParametersResolver = new GithubFactoryParametersResolver( githubUrlParser, urlFetcher, @@ -392,7 +392,7 @@ public class GithubFactoryParametersResolverTest { personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(false); // when - githubFactoryParametersResolver.parseFactoryUrl("url"); + abstractGithubFactoryParametersResolver.parseFactoryUrl("url"); // then verify(githubUrlParser).parse("url"); verify(githubUrlParser, never()).parseWithoutAuthentication("url"); diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubUrlTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubUrlTest.java index 8c0ff82e14..c63b5181fe 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubUrlTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubUrlTest.java @@ -139,7 +139,7 @@ public class GithubUrlTest { public void testRawFileLocationWithDefaultBranchName() { String file = ".che/che-theia-plugins.yaml"; - GithubUrl url = new GithubUrl().withUsername("eclipse").withRepository("che"); + GithubUrl url = new GithubUrl("github").withUsername("eclipse").withRepository("che"); assertEquals( url.rawFileLocation(file), @@ -151,7 +151,7 @@ public class GithubUrlTest { String file = ".che/che-theia-plugins.yaml"; GithubUrl url = - new GithubUrl().withUsername("eclipse").withRepository("che").withBranch("main"); + new GithubUrl("github").withUsername("eclipse").withRepository("che").withBranch("main"); assertEquals( url.rawFileLocation(file), @@ -163,7 +163,7 @@ public class GithubUrlTest { String file = ".che/che-theia-plugins.yaml"; GithubUrl url = - new GithubUrl() + new GithubUrl("github") .withUsername("eclipse") .withRepository("che") .withBranch("main") diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index 944d579214..f6b12abeed 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -29,6 +29,7 @@ che-core-api-auth-azure-devops che-core-api-auth-bitbucket che-core-api-auth-github + che-core-api-auth-github-common che-core-api-auth-gitlab che-core-api-auth-openshift che-core-api-workspace-shared @@ -44,6 +45,7 @@ che-core-api-factory-shared che-core-api-factory che-core-api-factory-github + che-core-api-factory-github-common che-core-api-factory-gitlab che-core-api-factory-bitbucket che-core-api-factory-bitbucket-server