feat: Support enabling Github enterprise and SaaS simultaneously on Dev Spaces

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/598/head
Anatolii Bazko 2023-10-19 11:12:08 +02:00
parent 2c198333ef
commit d382f47b5e
42 changed files with 1511 additions and 874 deletions

View File

@ -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<FactoryParametersResolver> 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<ScmFileResolver> 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);

View File

@ -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.

View File

@ -60,7 +60,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-github</artifactId>
<artifactId>che-core-api-auth-github-common</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>

View File

@ -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)

10
pom.xml
View File

@ -687,6 +687,11 @@
<artifactId>che-core-api-auth-github</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-github-common</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-gitlab</artifactId>
@ -769,6 +774,11 @@
<artifactId>che-core-api-factory-github</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-github-common</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-gitlab</artifactId>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-master-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.76.0-SNAPSHOT</version>
</parent>
<artifactId>che-core-api-auth-github-common</artifactId>
<packaging>jar</packaging>
<name>Che Core :: API :: Authentication Github Common</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<OAuthAuthenticator> {
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";
}
}
}

View File

@ -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

View File

@ -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/

View File

@ -27,10 +27,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
@ -45,7 +41,11 @@
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-shared</artifactId>
<artifactId>che-core-api-auth-github-common</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-github-common</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
@ -55,14 +55,6 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>

View File

@ -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<OAuthAuthenticator> {
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<OAuthAuthentic
@Nullable @Named("che.oauth.github.authuri") String authUri,
@Nullable @Named("che.oauth.github.tokenuri") String tokenUri)
throws IOException {
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);
}
}
return new NoopOAuthAuthenticator();
}
static class NoopOAuthAuthenticator extends OAuthAuthenticator {
@Override
public String getOAuthProvider() {
return "Noop";
}
@Override
public String getEndpointUrl() {
return "Noop";
}
super(
gitHubClientIdPath,
gitHubClientSecretPath,
redirectUris,
oauthEndpoint,
authUri,
tokenUri,
PROVIDER_NAME);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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 java.io.IOException;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.commons.annotation.Nullable;
/**
* Provides implementation of GitHub {@link OAuthAuthenticator} based on available configuration.
*
* @author Pavol Baran
*/
@Singleton
public class GitHubOAuthAuthenticatorProviderSecond
extends AbstractGitHubOAuthAuthenticatorProvider {
private static final String PROVIDER_NAME = "github_2";
@Inject
public GitHubOAuthAuthenticatorProviderSecond(
@Nullable @Named("che.oauth2.github.clientid_filepath_2") String gitHubClientIdPath,
@Nullable @Named("che.oauth2.github.clientsecret_filepath_2") String gitHubClientSecretPath,
@Nullable @Named("che.oauth.github.redirecturis") String[] redirectUris,
@Nullable @Named("che.integration.github.oauth_endpoint_2") String oauthEndpoint,
@Nullable @Named("che.oauth.github.authuri_2") String authUri,
@Nullable @Named("che.oauth.github.tokenuri_2") String tokenUri)
throws IOException {
super(
gitHubClientIdPath,
gitHubClientSecretPath,
redirectUris,
oauthEndpoint,
authUri,
tokenUri,
PROVIDER_NAME);
}
}

View File

@ -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/
@ -27,5 +27,6 @@ public class GithubModule extends AbstractModule {
Multibinder<OAuthAuthenticator> oAuthAuthenticators =
Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProvider.class);
oAuthAuthenticators.addBinding().toProvider(GitHubOAuthAuthenticatorProviderSecond.class);
}
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-master-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>7.76.0-SNAPSHOT</version>
</parent>
<artifactId>che-core-api-factory-github-common</artifactId>
<packaging>jar</packaging>
<name>Che Core :: API :: Factory Resolver Github Common</name>
<properties>
<findbugs.failonerror>true</findbugs.failonerror>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-dto</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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<String, String> 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<String, String> 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);
}
}
}

View File

@ -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<String> 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<String, String> SCOPE_MAP =
ImmutableMap.<String, String>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<Pair<Boolean, String>> 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<Boolean> 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<Pair<Boolean, String>> 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<String, String[]> 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<String> 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";
}
}

View File

@ -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());
}
}
}

View File

@ -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/(?<repoUser>[^/]+)/(?<repoName>[^/]++)((/)|(?:/tree/(?<branchName>.++))|(/pull/(?<pullRequestId>\\d++)))?$",
endpoint));
this.githubSSHPattern =
compile(format("^git@%s:(?<repoUser>.*)/(?<repoName>.*)$", 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<PersonalAccessToken> 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<PersonalAccessToken> 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;
}
}

View File

@ -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<String> 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";
}
}

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -26,14 +26,6 @@
<findbugs.failonerror>true</findbugs.failonerror>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@ -46,10 +38,6 @@
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth</artifactId>
@ -70,6 +58,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-github-common</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory-shared</artifactId>
@ -94,10 +86,6 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>

View File

@ -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<String, String> 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<String, String> 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);
}
}

View File

@ -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);
}
}

View File

@ -23,8 +23,11 @@ public class GithubModule extends AbstractModule {
Multibinder<PersonalAccessTokenFetcher> tokenFetcherMultibinder =
Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class);
tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcher.class);
tokenFetcherMultibinder.addBinding().to(GithubPersonalAccessTokenFetcherSecond.class);
Multibinder<GitUserDataFetcher> gitUserDataMultibinder =
Multibinder.newSetBinder(binder(), GitUserDataFetcher.class);
gitUserDataMultibinder.addBinding().to(GithubUserDataFetcher.class);
gitUserDataMultibinder.addBinding().to(GithubUserDataFetcherSecond.class);
}
}

View File

@ -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<String> 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<String, String> SCOPE_MAP =
ImmutableMap.<String, String>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<Pair<Boolean, String>> 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<Boolean> 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<Pair<Boolean, String>> 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<String, String[]> 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<String> 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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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/(?<repoUser>[^/]+)/(?<repoName>[^/]++)((/)|(?:/tree/(?<branchName>.++))|(/pull/(?<pullRequestId>\\d++)))?$",
endpoint));
this.githubSSHPattern =
compile(format("^git@%s:(?<repoUser>.*)/(?<repoName>.*)$", 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<PersonalAccessToken> 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<PersonalAccessToken> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<String> 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);
}
}

View File

@ -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);
}
}

View File

@ -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");

View File

@ -99,7 +99,7 @@ public class GithubFactoryParametersResolverTest {
@Captor private ArgumentCaptor<RemoteFactoryUrl> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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");

View File

@ -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")

View File

@ -29,6 +29,7 @@
<module>che-core-api-auth-azure-devops</module>
<module>che-core-api-auth-bitbucket</module>
<module>che-core-api-auth-github</module>
<module>che-core-api-auth-github-common</module>
<module>che-core-api-auth-gitlab</module>
<module>che-core-api-auth-openshift</module>
<module>che-core-api-workspace-shared</module>
@ -44,6 +45,7 @@
<module>che-core-api-factory-shared</module>
<module>che-core-api-factory</module>
<module>che-core-api-factory-github</module>
<module>che-core-api-factory-github-common</module>
<module>che-core-api-factory-gitlab</module>
<module>che-core-api-factory-bitbucket</module>
<module>che-core-api-factory-bitbucket-server</module>