From e99fcd78b9319d350e170f6d8e307cd8c16d91e5 Mon Sep 17 00:00:00 2001 From: Max Shaposhnik Date: Tue, 30 Mar 2021 14:19:23 +0300 Subject: [PATCH] Factories for private Gitlab repository with pre-created personal access token (#19351) --- ...tServerAuthorizingFileContentProvider.java | 86 +------------- .../api/factory/server/github/GithubUrl.java | 9 +- .../GitlabAuthorizingFileContentProvider.java | 29 +++++ .../GitlabFactoryParametersResolver.java | 16 ++- .../gitlab/GitlabFileContentProvider.java | 46 -------- .../api/factory/server/gitlab/GitlabUrl.java | 27 +++-- ...abAuthorizingFileContentProviderTest.java} | 27 ++++- .../factory/server/gitlab/GitlabUrlTest.java | 19 +-- .../scm/AuthorizingFileContentProvider.java | 109 ++++++++++++++++++ .../server/urlfactory/DefaultFactoryUrl.java | 11 ++ .../server/urlfactory/RemoteFactoryUrl.java | 6 + .../urlfactory/URLFactoryBuilderTest.java | 19 ++- 12 files changed, 249 insertions(+), 155 deletions(-) create mode 100644 wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java delete mode 100644 wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProvider.java rename wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/{GitlabFileContentProviderTest.java => GitlabAuthorizingFileContentProviderTest.java} (54%) create mode 100644 wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProvider.java index 5f943085d8..124c44d2bb 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProvider.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFileContentProvider.java @@ -11,101 +11,23 @@ */ package org.eclipse.che.api.factory.server.bitbucket; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Optional; +import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; import org.eclipse.che.api.factory.server.scm.GitCredentialManager; -import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; -import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; -import org.eclipse.che.api.factory.server.scm.exception.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.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; -import org.eclipse.che.commons.env.EnvironmentContext; /** * Bitbucket specific file content provider. Files are retrieved using bitbucket REST API and * personal access token based authentication is performed during requests. */ -public class BitbucketServerAuthorizingFileContentProvider implements FileContentProvider { - - private final URLFetcher urlFetcher; - private final BitbucketUrl bitbucketUrl; - private final GitCredentialManager gitCredentialManager; - private final PersonalAccessTokenManager personalAccessTokenManager; +public class BitbucketServerAuthorizingFileContentProvider + extends AuthorizingFileContentProvider { public BitbucketServerAuthorizingFileContentProvider( BitbucketUrl bitbucketUrl, URLFetcher urlFetcher, GitCredentialManager gitCredentialManager, PersonalAccessTokenManager personalAccessTokenManager) { - this.bitbucketUrl = bitbucketUrl; - this.urlFetcher = urlFetcher; - this.gitCredentialManager = gitCredentialManager; - this.personalAccessTokenManager = personalAccessTokenManager; - } - - @Override - public String fetchContent(String fileURL) throws IOException, DevfileException { - String requestURL; - try { - if (new URI(fileURL).isAbsolute()) { - requestURL = fileURL; - } else { - // since files retrieved via REST, we cannot use path symbols like . ./ so cut them off - requestURL = bitbucketUrl.rawFileLocation(fileURL.replaceAll("^[/.]+", "")); - } - } catch (URISyntaxException e) { - throw new DevfileException(e.getMessage(), e); - } - try { - Optional token = - personalAccessTokenManager.get( - EnvironmentContext.getCurrent().getSubject(), bitbucketUrl.getHostName()); - if (token.isPresent()) { - PersonalAccessToken personalAccessToken = token.get(); - String content = urlFetcher.fetch(requestURL, "Bearer " + personalAccessToken.getToken()); - gitCredentialManager.createOrReplace(personalAccessToken); - return content; - } else { - try { - return urlFetcher.fetch(requestURL); - } catch (IOException exception) { - // unable to determine exact cause, so let's just try to authorize... - try { - PersonalAccessToken personalAccessToken = - personalAccessTokenManager.fetchAndSave( - EnvironmentContext.getCurrent().getSubject(), bitbucketUrl.getHostName()); - String content = - urlFetcher.fetch(requestURL, "Bearer " + personalAccessToken.getToken()); - gitCredentialManager.createOrReplace(personalAccessToken); - return content; - } catch (ScmUnauthorizedException - | ScmCommunicationException - | UnknownScmProviderException e) { - throw new DevfileException(e.getMessage(), e); - } - } - } - - } catch (IOException e) { - throw new IOException( - String.format( - "Failed to fetch a content from URL %s. Make sure the URL" - + " is correct. Additionally, if you're using " - + " relative form, make sure the referenced files are actually stored" - + " relative to the devfile on the same host," - + " or try to specify URL in absolute form. The current attempt to download" - + " the file failed with the following error message: %s", - fileURL, e.getMessage()), - e); - } catch (ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { - throw new DevfileException(e.getMessage(), e); - } + super(bitbucketUrl, urlFetcher, personalAccessTokenManager, gitCredentialManager); } } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java index d5a8f75b65..b62d04eb77 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubUrl.java @@ -31,6 +31,8 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; */ public class GithubUrl implements RemoteFactoryUrl { + private static final String HOSTNAME = "https://github.com/"; + /** Username part of github URL */ private String username; @@ -160,12 +162,17 @@ public class GithubUrl implements RemoteFactoryUrl { .toString(); } + @Override + public String getHostName() { + return HOSTNAME; + } + /** * Provides location to the repository part of the full github URL. * * @return location of the repository. */ protected String repositoryLocation() { - return "https://github.com/" + this.username + "/" + this.repository; + return HOSTNAME + this.username + "/" + this.repository; } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java new file mode 100644 index 0000000000..632abee203 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; +import org.eclipse.che.api.factory.server.scm.GitCredentialManager; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** Gitlab specific authorizing file content provider. */ +class GitlabAuthorizingFileContentProvider extends AuthorizingFileContentProvider { + + GitlabAuthorizingFileContentProvider( + GitlabUrl githubUrl, + URLFetcher urlFetcher, + GitCredentialManager gitCredentialManager, + PersonalAccessTokenManager personalAccessTokenManager) { + super(githubUrl, urlFetcher, personalAccessTokenManager, gitCredentialManager); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java index 3f1d3f3ee0..7f7139308d 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java @@ -22,6 +22,8 @@ import javax.validation.constraints.NotNull; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.DefaultFactoryParameterResolver; +import org.eclipse.che.api.factory.server.scm.GitCredentialManager; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; @@ -40,11 +42,20 @@ public class GitlabFactoryParametersResolver extends DefaultFactoryParameterReso private final GitlabUrlParser gitlabURLParser; + private final GitCredentialManager gitCredentialManager; + private final PersonalAccessTokenManager personalAccessTokenManager; + @Inject public GitlabFactoryParametersResolver( - URLFactoryBuilder urlFactoryBuilder, URLFetcher urlFetcher, GitlabUrlParser gitlabURLParser) { + URLFactoryBuilder urlFactoryBuilder, + URLFetcher urlFetcher, + GitlabUrlParser gitlabURLParser, + GitCredentialManager gitCredentialManager, + PersonalAccessTokenManager personalAccessTokenManager) { super(urlFactoryBuilder, urlFetcher); this.gitlabURLParser = gitlabURLParser; + this.gitCredentialManager = gitCredentialManager; + this.personalAccessTokenManager = personalAccessTokenManager; } /** @@ -76,7 +87,8 @@ public class GitlabFactoryParametersResolver extends DefaultFactoryParameterReso return urlFactoryBuilder .createFactoryFromDevfile( gitlabUrl, - new GitlabFileContentProvider(gitlabUrl, urlFetcher), + new GitlabAuthorizingFileContentProvider( + gitlabUrl, urlFetcher, gitCredentialManager, personalAccessTokenManager), extractOverrideParams(factoryParameters)) .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) .acceptVisitor(new GitlabFactoryVisitor(gitlabUrl)); diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProvider.java deleted file mode 100644 index 7c799112c8..0000000000 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.gitlab; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; -import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; - -/** Gitlab specific file content provider. */ -class GitlabFileContentProvider implements FileContentProvider { - - private final GitlabUrl gitlabUrl; - private final URLFetcher urlFetcher; - - GitlabFileContentProvider(GitlabUrl githubUrl, URLFetcher urlFetcher) { - this.gitlabUrl = githubUrl; - this.urlFetcher = urlFetcher; - } - - @Override - public String fetchContent(String fileURL) throws IOException, DevfileException { - String requestURL; - try { - if (new URI(fileURL).isAbsolute()) { - requestURL = fileURL; - } else { - requestURL = gitlabUrl.rawFileLocation(fileURL); - } - } catch (URISyntaxException e) { - throw new DevfileException(e.getMessage(), e); - } - return urlFetcher.fetch(requestURL); - } -} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java index 42fdde5bdb..8f9caa28d6 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java @@ -11,8 +11,9 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static com.google.common.base.MoreObjects.firstNonNull; +import static java.net.URLEncoder.encode; +import com.google.common.base.Charsets; import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; @@ -185,15 +186,23 @@ public class GitlabUrl implements RemoteFactoryUrl { * @return location of specified file in a repository */ public String rawFileLocation(String fileName) { - StringJoiner joiner = new StringJoiner("/").add(hostName).add(username).add(project); - if (repository != null) { - joiner.add(repository); + String resultUrl = + new StringJoiner("/") + .add(hostName) + .add("api/v4/projects") + // use URL-encoded path to the project as a selector instead of id + .add(encode(username + "/" + project, Charsets.UTF_8)) + .add("repository") + .add("files") + .add(fileName) + .add("raw") + .toString(); + if (branch != null) { + resultUrl = resultUrl + "?ref=" + branch; + } else { + resultUrl = resultUrl + "?ref=master"; } - joiner.add("-").add("raw").add(firstNonNull(branch, "master")); - if (subfolder != null) { - joiner.add(subfolder); - } - return joiner.add(fileName).toString(); + return resultUrl; } /** diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProviderTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java similarity index 54% rename from wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProviderTest.java rename to wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java index 125a3145e7..607f534aad 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFileContentProviderTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java @@ -14,12 +14,21 @@ package org.eclipse.che.api.factory.server.gitlab; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import org.eclipse.che.api.factory.server.scm.GitCredentialManager; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; -public class GitlabFileContentProviderTest { +@Listeners(MockitoTestNGListener.class) +public class GitlabAuthorizingFileContentProviderTest { + + @Mock private GitCredentialManager gitCredentialsManager; + @Mock private PersonalAccessTokenManager personalAccessTokenManager; @Test public void shouldExpandRelativePaths() throws Exception { @@ -29,9 +38,14 @@ public class GitlabFileContentProviderTest { .withHostName("https://gitlab.net") .withUsername("eclipse") .withProject("che"); - FileContentProvider fileContentProvider = new GitlabFileContentProvider(gitlabUrl, urlFetcher); + FileContentProvider fileContentProvider = + new GitlabAuthorizingFileContentProvider( + gitlabUrl, urlFetcher, gitCredentialsManager, personalAccessTokenManager); fileContentProvider.fetchContent("devfile.yaml"); - verify(urlFetcher).fetch(eq("https://gitlab.net/eclipse/che/-/raw/master/devfile.yaml")); + verify(urlFetcher) + .fetch( + eq( + "https://gitlab.net/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=master")); } @Test @@ -39,8 +53,11 @@ public class GitlabFileContentProviderTest { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withUsername("eclipse").withProject("che"); - FileContentProvider fileContentProvider = new GitlabFileContentProvider(gitlabUrl, urlFetcher); - String url = "https://gitlab.net/eclipse/che/-/raw/master/devfile.yaml"; + FileContentProvider fileContentProvider = + new GitlabAuthorizingFileContentProvider( + gitlabUrl, urlFetcher, gitCredentialsManager, personalAccessTokenManager); + String url = + "https://gitlab.net/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=master"; fileContentProvider.fetchContent(url); verify(urlFetcher).fetch(eq(url)); } diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlTest.java index f9700fc94e..791569cda8 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlTest.java @@ -11,6 +11,7 @@ */ package org.eclipse.che.api.factory.server.gitlab; +import static java.lang.String.format; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; @@ -57,30 +58,32 @@ public class GitlabUrlTest { GitlabUrl gitlabUrl = gitlabUrlParser.parse(repoUrl); assertEquals(gitlabUrl.devfileFileLocations().size(), 2); Iterator iterator = gitlabUrl.devfileFileLocations().iterator(); - assertEquals(iterator.next().location(), fileUrl + "devfile.yaml"); - - assertEquals(iterator.next().location(), fileUrl + "foo.bar"); + assertEquals(iterator.next().location(), format(fileUrl, "devfile.yaml")); + assertEquals(iterator.next().location(), format(fileUrl, "foo.bar")); } @DataProvider public static Object[][] urlsProvider() { return new Object[][] { - {"https://gitlab.net/eclipse/che.git", "https://gitlab.net/eclipse/che/-/raw/master/"}, + { + "https://gitlab.net/eclipse/che.git", + "https://gitlab.net/api/v4/projects/eclipse%%2Fche/repository/files/%s/raw?ref=master" + }, { "https://gitlab.net/eclipse/fooproj/che.git", - "https://gitlab.net/eclipse/fooproj/che/-/raw/master/" + "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=master" }, { "https://gitlab.net/eclipse/fooproj/-/tree/master/", - "https://gitlab.net/eclipse/fooproj/-/raw/master/" + "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=master" }, { "https://gitlab.net/eclipse/fooproj/che/-/tree/foobranch/", - "https://gitlab.net/eclipse/fooproj/che/-/raw/foobranch/" + "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=foobranch" }, { "https://gitlab.net/eclipse/fooproj/che/-/tree/foobranch/subfolder", - "https://gitlab.net/eclipse/fooproj/che/-/raw/foobranch/subfolder/" + "https://gitlab.net/api/v4/projects/eclipse%%2Ffooproj/repository/files/%s/raw?ref=foobranch" }, }; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java new file mode 100644 index 0000000000..6e47618a1c --- /dev/null +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AuthorizingFileContentProvider.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.scm; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; +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.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.RemoteFactoryUrl; +import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; +import org.eclipse.che.commons.env.EnvironmentContext; + +/** + * Common implementation of file content provider which is able to access content of private + * repositories using personal access tokens from specially formatted secret in user's namespace. + */ +public class AuthorizingFileContentProvider + implements FileContentProvider { + + private final T remoteFactoryUrl; + private final URLFetcher urlFetcher; + private final PersonalAccessTokenManager personalAccessTokenManager; + private final GitCredentialManager gitCredentialManager; + + public AuthorizingFileContentProvider( + T remoteFactoryUrl, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager, + GitCredentialManager gitCredentialManager) { + this.remoteFactoryUrl = remoteFactoryUrl; + this.urlFetcher = urlFetcher; + this.personalAccessTokenManager = personalAccessTokenManager; + this.gitCredentialManager = gitCredentialManager; + } + + @Override + public String fetchContent(String fileURL) throws IOException, DevfileException { + String requestURL; + try { + if (new URI(fileURL).isAbsolute()) { + requestURL = fileURL; + } else { + // since files retrieved via REST, we cannot use path symbols like . ./ so cut them off + requestURL = remoteFactoryUrl.rawFileLocation(fileURL.replaceAll("^[/.]+", "")); + } + } catch (URISyntaxException e) { + throw new DevfileException(e.getMessage(), e); + } + try { + Optional token = + personalAccessTokenManager.get( + EnvironmentContext.getCurrent().getSubject(), remoteFactoryUrl.getHostName()); + if (token.isPresent()) { + PersonalAccessToken personalAccessToken = token.get(); + String content = urlFetcher.fetch(requestURL, "Bearer " + personalAccessToken.getToken()); + gitCredentialManager.createOrReplace(personalAccessToken); + return content; + } else { + try { + return urlFetcher.fetch(requestURL); + } catch (IOException exception) { + // unable to determine exact cause, so let's just try to authorize... + try { + PersonalAccessToken personalAccessToken = + personalAccessTokenManager.fetchAndSave( + EnvironmentContext.getCurrent().getSubject(), remoteFactoryUrl.getHostName()); + String content = + urlFetcher.fetch(requestURL, "Bearer " + personalAccessToken.getToken()); + gitCredentialManager.createOrReplace(personalAccessToken); + return content; + } catch (ScmUnauthorizedException + | ScmCommunicationException + | UnknownScmProviderException e) { + throw new DevfileException(e.getMessage(), e); + } + } + } + } catch (IOException e) { + throw new IOException( + String.format( + "Failed to fetch a content from URL %s. Make sure the URL" + + " is correct. Additionally, if you're using " + + " relative form, make sure the referenced files are actually stored" + + " relative to the devfile on the same host," + + " or try to specify URL in absolute form. The current attempt to download" + + " the file failed with the following error message: %s", + fileURL, e.getMessage()), + e); + } catch (ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { + throw new DevfileException(e.getMessage(), e); + } + } +} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrl.java index 71d8ee1e88..5b0791e218 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/DefaultFactoryUrl.java @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.server.urlfactory; import static java.util.Collections.singletonList; +import java.net.URI; import java.util.List; import java.util.Optional; @@ -40,6 +41,16 @@ public class DefaultFactoryUrl implements RemoteFactoryUrl { }); } + @Override + public String rawFileLocation(String filename) { + return URI.create(devfileFileLocation).resolve(filename).toString(); + } + + @Override + public String getHostName() { + return URI.create(devfileFileLocation).getHost(); + } + public DefaultFactoryUrl withDevfileFileLocation(String devfileFileLocation) { this.devfileFileLocation = devfileFileLocation; return this; diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/RemoteFactoryUrl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/RemoteFactoryUrl.java index 44ebfc4de9..503b62ecbf 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/RemoteFactoryUrl.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/RemoteFactoryUrl.java @@ -27,6 +27,12 @@ public interface RemoteFactoryUrl { */ List devfileFileLocations(); + /** Address of raw file content in remote repository */ + String rawFileLocation(String filename); + + /** Remote hostname */ + String getHostName(); + /** Describes devfile location, including filename if any. */ interface DevfileLocation { Optional filename(); diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java index 895afb3833..0575167baa 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; @@ -173,8 +174,10 @@ public class URLFactoryBuilderTest { when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2); RemoteFactoryUrl githubLikeRemoteUrl = - () -> - Collections.singletonList( + new RemoteFactoryUrl() { + @Override + public List devfileFileLocations() { + return Collections.singletonList( new DevfileLocation() { @Override public Optional filename() { @@ -186,6 +189,18 @@ public class URLFactoryBuilderTest { return myLocation; } }); + } + + @Override + public String rawFileLocation(String filename) { + return null; + } + + @Override + public String getHostName() { + return null; + } + }; FactoryMetaDto factory = urlFactoryBuilder