Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Basic authentication for devfile factory URL #451

Merged
merged 5 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public BitbucketServerUrl parse(String url) {
format(
"The given url %s is not a valid Bitbucket server URL. Check either URL or server configuration.",
url)));
return parse(matcher);
return parse(matcher).withUrl(url);
}

private BitbucketServerUrl parse(Matcher matcher) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;

/** Representation of a bitbucket Server URL, allowing to get details from it. */
public class BitbucketServerUrl implements RemoteFactoryUrl {
public class BitbucketServerUrl extends DefaultFactoryUrl {

private final String NAME = "bitbucket";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public BitbucketUrl parse(String url) {
.withRepository(repoName)
.withBranch(branchName)
.withWorkspaceId(workspaceId)
.withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames());
.withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames())
.withUrl(url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;

/**
* Representation of a bitbucket URL, allowing to get details from it.
*
* <p>like https://<your_username>@bitbucket.org/<workspace_ID>/<repo_name>.git
*/
public class BitbucketUrl implements RemoteFactoryUrl {
public class BitbucketUrl extends DefaultFactoryUrl {

private final String NAME = "bitbucket";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ private GithubUrl parse(String url, boolean authenticationRequired) throws ApiEx
.withBranch(branchName)
.withLatestCommit(latestCommit)
.withSubfolder(matcher.group("subFolder"))
.withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames());
.withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames())
.withUrl(url);
}

private GithubPullRequest getPullRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;

/**
* Representation of a github URL, allowing to get details from it.
Expand All @@ -28,7 +28,7 @@
*
* @author Florent Benoit
*/
public class GithubUrl implements RemoteFactoryUrl {
public class GithubUrl extends DefaultFactoryUrl {

private final String NAME = "github";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.Optional;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;

/**
* Representation of a gitlab URL, allowing to get details from it.
Expand All @@ -30,7 +30,7 @@
*
* @author Max Shaposhnyk
*/
public class GitlabUrl implements RemoteFactoryUrl {
public class GitlabUrl extends DefaultFactoryUrl {

private final String NAME = "gitlab";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public GitlabUrl parse(String url) {
.findFirst()
.or(() -> getPatternMatcherByUrl(url));
if (matcherOptional.isPresent()) {
return parse(matcherOptional.get());
return parse(matcherOptional.get()).withUrl(url);
} else {
throw new UnsupportedOperationException(
"The gitlab integration is not configured properly and cannot be used at this moment."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryPa
}
return urlFactoryBuilder
.createFactoryFromDevfile(
new DefaultFactoryUrl().withDevfileFileLocation(devfileLocation),
new DefaultFactoryUrl()
.withDevfileFileLocation(devfileLocation)
.withUrl(devfileLocation),
new URLFileContentProvider(devfileURI, urlFetcher),
extractOverrideParams(factoryParameters),
false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
*/
package org.eclipse.che.api.factory.server.scm;

import static com.google.common.base.Strings.isNullOrEmpty;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Base64;
import javax.net.ssl.SSLException;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException;
Expand All @@ -25,6 +28,7 @@
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.annotation.Nullable;

/**
* Common implementation of file content provider which is able to access content of private
Expand All @@ -48,26 +52,41 @@ public AuthorizingFileContentProvider(

@Override
public String fetchContent(String fileURL) throws IOException, DevfileException {
return fetchContent(fileURL, false);
return fetchContent(fileURL, false, null);
}

@Override
public String fetchContent(String fileURL, String credentials)
throws IOException, DevfileException {
return fetchContent(fileURL, false, credentials);
}
Comment on lines +59 to 62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use Optional here also?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use Optional or Nullable here. This is an overloaded method which is supposed to receive non null parameters.


@Override
public String fetchContentWithoutAuthentication(String fileURL)
throws IOException, DevfileException {
return fetchContent(fileURL, true);
return fetchContent(fileURL, true, null);
}

protected String fetchContent(String fileURL, boolean skipAuthentication)
private String fetchContent(
String fileURL, boolean skipAuthentication, @Nullable String credentials)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to use Optional here also

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that passing Optional as a parameter is a good idea: https://stackoverflow.com/questions/31922866/why-should-java-8s-optional-not-be-used-in-arguments
This is a private method so I don't think it will affect something.

throws IOException, DevfileException {
final String requestURL = formatUrl(fileURL);
try {
if (skipAuthentication) {
return urlFetcher.fetch(requestURL);
} else {
// try to authenticate for the given URL
PersonalAccessToken token =
personalAccessTokenManager.getAndStore(remoteFactoryUrl.getHostName());
return urlFetcher.fetch(requestURL, formatAuthorization(token.getToken()));
String authorization;
if (isNullOrEmpty(credentials)) {
authorization =
formatAuthorization(
personalAccessTokenManager
.getAndStore(remoteFactoryUrl.getHostName())
.getToken());
} else {
authorization = getCredentialsAuthorization(credentials);
}
return urlFetcher.fetch(requestURL, authorization);
}
} catch (UnknownScmProviderException e) {
return fetchContentWithoutToken(requestURL, e);
Expand Down Expand Up @@ -143,4 +162,8 @@ protected String formatUrl(String fileURL) throws DevfileException {
protected String formatAuthorization(String token) {
return "Bearer " + token;
}

private String getCredentialsAuthorization(String credentials) {
return "Basic " + new String(Base64.getEncoder().encode(credentials.getBytes()));
}
}
Original file line number Diff line number Diff line change
@@ -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/
Expand All @@ -11,11 +11,16 @@
*/
package org.eclipse.che.api.factory.server.urlfactory;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.util.Collections.singletonList;
import static java.util.regex.Pattern.compile;

import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import org.eclipse.che.commons.annotation.Nullable;

/**
* Default implementation of {@link RemoteFactoryUrl} which used with all factory URL's until there
Expand All @@ -24,6 +29,7 @@
public class DefaultFactoryUrl implements RemoteFactoryUrl {

private String devfileFileLocation;
private String url;

@Override
public String getProviderName() {
Expand Down Expand Up @@ -61,6 +67,38 @@ public String getBranch() {
return null;
}

@Override
public @Nullable String getCredentials() {
if (isNullOrEmpty(url)) {
return null;
}
Matcher matcher =
compile("https?://((?<username>[^:|@]+)?:?(?<password>[^@]+)?@)?.*").matcher(url);
String password = null;
String username = null;
try {
username = matcher.matches() ? matcher.group("username") : null;
} catch (IllegalArgumentException e) {
// no such group
}
try {
password = matcher.matches() ? matcher.group("password") : null;
} catch (IllegalArgumentException e) {
// no such group
}
if (!isNullOrEmpty(username) || !isNullOrEmpty(password)) {
return format(
"%s:%s",
isNullOrEmpty(username) ? "" : username, isNullOrEmpty(password) ? "" : password);
}
return null;
}
vinokurig marked this conversation as resolved.
Show resolved Hide resolved

public <T extends DefaultFactoryUrl> T withUrl(String url) {
this.url = url;
return (T) this;
}

vinokurig marked this conversation as resolved.
Show resolved Hide resolved
public DefaultFactoryUrl withDevfileFileLocation(String devfileFileLocation) {
this.devfileFileLocation = devfileFileLocation;
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -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/
Expand Down Expand Up @@ -42,6 +42,9 @@ public interface RemoteFactoryUrl {
/** Remote branch */
String getBranch();

/** Credentials in format <username>:<password> */
String getCredentials();
vinokurig marked this conversation as resolved.
Show resolved Hide resolved

/** Describes devfile location, including filename if any. */
interface DevfileLocation {
Optional<String> filename();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ public Optional<FactoryMetaDto> createFactoryFromDevfile(
devfileYamlContent =
skipAuthentication
? fileContentProvider.fetchContentWithoutAuthentication(location.location())
: fileContentProvider.fetchContent(location.location());
: fileContentProvider.fetchContent(
location.location(), remoteFactoryUrl.getCredentials());
} catch (IOException ex) {
// try next location
LOG.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ public void shouldResolveRelativeFiles() throws Exception {
// set up our factory with the location of our devfile that is referencing our localfile
Map<String, String> factoryParameters = new HashMap<>();
factoryParameters.put(URL_PARAMETER_NAME, "http://myloc.com/aa/bb/devfile");
doReturn(DEVFILE).when(urlFetcher).fetch(eq("http://myloc.com/aa/bb/devfile"));
doReturn("localfile").when(urlFetcher).fetch("http://myloc.com/aa/localfile");
doReturn(DEVFILE).when(urlFetcher).fetch(eq("http://myloc.com/aa/bb/devfile"), eq(null));
doReturn("localfile").when(urlFetcher).fetch("http://myloc.com/aa/localfile", null);

// when
res.createFactory(factoryParameters);

// then
verify(urlFetcher).fetch(eq("http://myloc.com/aa/localfile"));
verify(urlFetcher).fetch(eq("http://myloc.com/aa/localfile"), eq(null));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.urlfactory;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

/** Testing {@link DefaultFactoryUrl} */
@Listeners(MockitoTestNGListener.class)
public class DefaultFactoryUrlTest {
@Test
public void shouldCredentials() {
// given
DefaultFactoryUrl factoryUrl =
new DefaultFactoryUrl().withUrl("https://username:password@hostname/path");
vinokurig marked this conversation as resolved.
Show resolved Hide resolved
// when
String credentials = factoryUrl.getCredentials();
// then
assertEquals(credentials, "username:password");
}

@Test
public void shouldCredentialsWithoutPassword() {
// given
DefaultFactoryUrl factoryUrl =
new DefaultFactoryUrl().withUrl("https://username@hostname/path");
// when
String credentials = factoryUrl.getCredentials();
// then
assertEquals(credentials, "username:");
}

@Test
public void shouldGetNullCredentials() {
// given
DefaultFactoryUrl factoryUrl = new DefaultFactoryUrl().withUrl("https://hostname/path");
// when
String credentials = factoryUrl.getCredentials();
// then
assertNull(credentials);
}
}
Loading