Correct response codes and messages related to private factory flow

7.28.x
Max Shaposhnik 2021-02-17 19:46:20 +02:00 committed by GitHub
parent 946162f61a
commit 889187e4e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 199 additions and 83 deletions

View File

@ -12,27 +12,15 @@
package org.eclipse.che.api.core;
/**
* Error codes that are used in exceptions. Defined error codes MUST BE in range <b>15000-32999</b>
* inclusive.
* Defines error codes that are used in exceptions, defined codes MUST NOT conflict, error codes
* must be in range <b>10000-14999</b> inclusive.
*
* @author Igor Vinokur
* @author Yevhenii Voevodin
*/
public class ErrorCodes {
public final class ErrorCodes {
public static final int LIMIT_EXCEEDED = 10000;
private ErrorCodes() {}
public static final int NO_COMMITTER_NAME_OR_EMAIL_DEFINED = 15216;
public static final int UNABLE_GET_PRIVATE_SSH_KEY = 32068;
public static final int UNAUTHORIZED_GIT_OPERATION = 32080;
public static final int UNAUTHORIZED_SVN_OPERATION = 32090;
public static final int MERGE_CONFLICT = 32062;
public static final int FAILED_CHECKOUT = 32063;
public static final int FAILED_CHECKOUT_WITH_START_POINT = 32064;
public static final int INIT_COMMIT_WAS_NOT_PERFORMED = 32082;
public static final int NO_PROJECT_ON_FILE_SYSTEM = 10;
public static final int NO_PROJECT_CONFIGURED_IN_WS = 11;
public static final int PROJECT_TYPE_IS_NOT_REGISTERED = 12;
public static final int ATTRIBUTE_NAME_PROBLEM = 13;
public static final int NOT_UPDATED_PROJECT = 14;
public static final int ITEM_NOT_FOUND = 15;
}

View File

@ -1,25 +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.multiuser.resource.api.workspace;
/**
* Defines error codes, defined codes MUST NOT conflict with existing {@link
* org.eclipse.che.api.core.ErrorCodes}, error codes must be in range <b>10000-14999</b> inclusive.
*
* @author Yevhenii Voevodin
*/
public final class ErrorCodes {
public static final int LIMIT_EXCEEDED = 10000;
private ErrorCodes() {}
}

View File

@ -12,6 +12,7 @@
package org.eclipse.che.multiuser.resource.api.workspace;
import java.util.Map;
import org.eclipse.che.api.core.ErrorCodes;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.shared.dto.ExtendedError;
import org.eclipse.che.dto.server.DtoFactory;

View File

@ -21,6 +21,7 @@ import com.google.inject.Singleton;
@Singleton
public class BitbucketServerOAuthAuthenticator extends OAuthAuthenticator {
public static final String AUTHENTICATOR_NAME = "bitbucket-server";
private final String apiEndpoint;
public BitbucketServerOAuthAuthenticator(
String consumerKey, String privateKey, String bitbucketEndpoint, String apiEndpoint) {
@ -32,10 +33,19 @@ public class BitbucketServerOAuthAuthenticator extends OAuthAuthenticator {
apiEndpoint + "/oauth/1.0/callback",
null,
privateKey);
this.apiEndpoint = apiEndpoint;
}
@Override
public final String getOAuthProvider() {
return AUTHENTICATOR_NAME;
}
@Override
public String getLocalAuthenticateUrl() {
return apiEndpoint
+ "/oauth/1.0/authenticate?oauth_provider="
+ AUTHENTICATOR_NAME
+ "&request_method=POST&signature_method=rsa";
}
}

View File

@ -46,4 +46,10 @@ public class NoopOAuthAuthenticator extends OAuthAuthenticator {
throw new RuntimeException(
"The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.");
}
@Override
public String getLocalAuthenticateUrl() {
throw new RuntimeException(
"The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.");
}
}

View File

@ -229,6 +229,14 @@ public abstract class OAuthAuthenticator {
*/
abstract String getOAuthProvider();
/**
* Returns URL to initiate authentication process using given authenticator. Typically points to
* {@code /api/oauth/} or {@code /api/oauth/1.0} endpoint with necessary request params.
*
* @return URL to initiate authentication process
*/
public abstract String getLocalAuthenticateUrl();
/**
* Compute the Authorization header to sign the OAuth 1 request.
*

View File

@ -19,6 +19,7 @@ import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
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;
@ -81,7 +82,7 @@ public class BitbucketServerAuthorizingFactoryParametersResolver
*/
@Override
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException {
throws ApiException {
// no need to check null value of url parameter as accept() method has performed the check
final BitbucketUrl bitbucketUrl =

View File

@ -32,7 +32,8 @@ public interface BitbucketServerApiClient {
* @throws ScmUnauthorizedException - in case if {@link Subject} is not linked to any {@link
* BitbucketUser}
*/
BitbucketUser getUser(Subject cheUser) throws ScmUnauthorizedException, ScmCommunicationException;
BitbucketUser getUser(Subject cheUser)
throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException;
/**
* @param slug

View File

@ -13,7 +13,6 @@ package org.eclipse.che.api.factory.server.bitbucket.server;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static java.time.Duration.ofSeconds;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -89,7 +88,7 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient {
@Override
public BitbucketUser getUser(Subject cheUser)
throws ScmUnauthorizedException, ScmCommunicationException {
throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException {
try {
// Since Bitbucket server API doesn't provide a way to get an account profile currently
// authenticated user we will try to find it and by iterating over the list available to the
@ -118,11 +117,10 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient {
if (currentUser.isPresent()) {
return currentUser.get();
}
} catch (ScmBadRequestException | ScmItemNotFoundException scmBadRequestException) {
throw new ScmCommunicationException(
scmBadRequestException.getMessage(), scmBadRequestException);
} catch (ScmBadRequestException | ScmItemNotFoundException scmException) {
throw new ScmCommunicationException(scmException.getMessage(), scmException);
}
throw new ScmUnauthorizedException(
throw new ScmItemNotFoundException(
"Current user not found. That is possible only if user are not authorized against "
+ serverUri);
}
@ -349,8 +347,6 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient {
switch (response.statusCode()) {
case HTTP_BAD_REQUEST:
throw new ScmBadRequestException(body);
case HTTP_UNAUTHORIZED:
throw new ScmUnauthorizedException(body);
case HTTP_NOT_FOUND:
throw new ScmItemNotFoundException(body);
default:

View File

@ -45,7 +45,10 @@ public class BitbucketServerOAuth1AuthorizationHeaderSupplier
subject.getUserName()
+ " is not authorized in "
+ authenticator.getOAuthProvider()
+ " OAuth1 provider");
+ " OAuth1 provider",
authenticator.getOAuthProvider(),
"1.0",
authenticator.getLocalAuthenticateUrl());
}
return authorizationHeader;
} catch (OAuthAuthenticationException e) {

View File

@ -107,7 +107,7 @@ public class BitbucketServerPersonalAccessTokenFetcherTest {
dataProvider = "expectedExceptions",
expectedExceptions = {ScmUnauthorizedException.class, ScmCommunicationException.class})
public void shouldRethrowBasicExceptionsOnGetUserStep(Class<? extends Throwable> exception)
throws ScmUnauthorizedException, ScmCommunicationException {
throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException {
// given
when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(true);
doThrow(exception).when(bitbucketServerApiClient).getUser(eq(subject));

View File

@ -19,6 +19,7 @@ import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
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.urlfactory.ProjectConfigDtoMerger;
@ -82,8 +83,7 @@ public class GithubFactoryParametersResolver extends DefaultFactoryParameterReso
*/
@Override
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException {
throws ApiException {
// no need to check null value of url parameter as accept() method has performed the check
final GithubUrl githubUrl = githubUrlParser.parse(factoryParameters.get(URL_PARAMETER_NAME));

View File

@ -28,8 +28,8 @@ import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
@ -72,7 +72,7 @@ public class DefaultFactoryParameterResolver implements FactoryParametersResolve
*/
@Override
public FactoryMetaDto createFactory(@NotNull final Map<String, String> factoryParameters)
throws BadRequestException, ServerException {
throws ApiException {
// This should never be null, because our contract in #accept prohibits that
String devfileLocation = factoryParameters.get(URL_PARAMETER_NAME);

View File

@ -13,8 +13,8 @@ package org.eclipse.che.api.factory.server;
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
/**
@ -39,6 +39,5 @@ public interface FactoryParametersResolver {
* @param factoryParameters map containing factory data parameters provided through URL
* @throws BadRequestException when data are invalid
*/
FactoryMetaDto createFactory(@NotNull Map<String, String> factoryParameters)
throws BadRequestException, ServerException;
FactoryMetaDto createFactory(@NotNull Map<String, String> factoryParameters) throws ApiException;
}

View File

@ -30,7 +30,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto;
import org.eclipse.che.api.user.server.UserManager;
@ -87,7 +86,7 @@ public class FactoryService extends Service {
@DefaultValue("false")
@QueryParam(VALIDATE_QUERY_PARAMETER)
Boolean validate)
throws BadRequestException, ServerException {
throws ApiException {
// check parameter
requiredNotNull(parameters, "Factory build parameters");

View File

@ -14,11 +14,39 @@ package org.eclipse.che.api.factory.server.scm.exception;
/** In case if OAuth1 or Oauth2 token is missing and we cant make any authorised calls */
public class ScmUnauthorizedException extends Exception {
public ScmUnauthorizedException(String message) {
private final String oauthProvider;
private final String oauthVersion;
private final String authenticateUrl;
public ScmUnauthorizedException(
String message, String oauthProvider, String oauthVersion, String authenticateUrl) {
super(message);
this.oauthProvider = oauthProvider;
this.oauthVersion = oauthVersion;
this.authenticateUrl = authenticateUrl;
}
public ScmUnauthorizedException(String message, Throwable cause) {
public ScmUnauthorizedException(
String message,
String oauthProvider,
Throwable cause,
String oauthVersion,
String authenticateUrl) {
super(message, cause);
this.oauthProvider = oauthProvider;
this.oauthVersion = oauthVersion;
this.authenticateUrl = authenticateUrl;
}
public String getOauthProvider() {
return oauthProvider;
}
public String getAuthenticateUrl() {
return authenticateUrl;
}
public String getOauthVersion() {
return oauthVersion;
}
}

View File

@ -12,7 +12,6 @@
package org.eclipse.che.api.factory.server.urlfactory;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION;
import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE;
@ -27,7 +26,13 @@ import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
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.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
@ -94,7 +99,7 @@ public class URLFactoryBuilder {
RemoteFactoryUrl remoteFactoryUrl,
FileContentProvider fileContentProvider,
Map<String, String> overrideProperties)
throws BadRequestException {
throws ApiException {
String devfileYamlContent;
for (DevfileLocation location : remoteFactoryUrl.devfileFileLocations()) {
try {
@ -108,10 +113,7 @@ public class URLFactoryBuilder {
continue;
} catch (DevfileException e) {
LOG.debug("Unexpected devfile exception: {}", e.getMessage());
throw new BadRequestException(
format(
"There is an error resolving defvile. Error: %s. URL is %s",
e.getMessage(), location.location()));
throw toApiException(e, location);
}
if (isNullOrEmpty(devfileYamlContent)) {
return Optional.empty();
@ -121,17 +123,42 @@ public class URLFactoryBuilder {
JsonNode parsedDevfile = devfileParser.parseYamlRaw(devfileYamlContent);
return Optional.of(
createFactory(parsedDevfile, overrideProperties, fileContentProvider, location));
} catch (DevfileException | OverrideParameterException e) {
throw new BadRequestException(
"Error occurred during creation a workspace from devfile located at `"
+ location.location()
+ "`. Cause: "
+ e.getMessage());
} catch (OverrideParameterException e) {
throw new BadRequestException("Error processing override parameter(s): " + e.getMessage());
} catch (DevfileException e) {
throw toApiException(e, location);
}
}
return Optional.empty();
}
private ApiException toApiException(DevfileException devfileException, DevfileLocation location) {
Throwable cause = devfileException.getCause();
if (cause instanceof ScmUnauthorizedException) {
ScmUnauthorizedException scmCause = (ScmUnauthorizedException) cause;
return new UnauthorizedException(
"SCM Authentication required",
401,
Map.of(
"oauth_version", scmCause.getOauthVersion(),
"oauth_provider", scmCause.getOauthProvider(),
"oauth_authentication_url", scmCause.getAuthenticateUrl()));
} else if (cause instanceof UnknownScmProviderException) {
return new ServerException(
"Provided location is unknown or misconfigured on the server side. Error message:"
+ cause.getMessage());
} else if (cause instanceof ScmCommunicationException) {
return new ServerException(
"There is an error happened when communicate with SCM server. Error message:"
+ cause.getMessage());
}
return new BadRequestException(
"Error occurred during creation a workspace from devfile located at `"
+ location.location()
+ "`. Cause: "
+ devfileException.getMessage());
}
/**
* Converts given devfile json into factory based on the devfile version.
*

View File

@ -36,7 +36,13 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.api.core.rest.shared.dto.ExtendedError;
import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
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.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
@ -133,7 +139,7 @@ public class URLFactoryBuilderTest {
}
@Test
public void testDevfileV2() throws BadRequestException, DevfileException {
public void testDevfileV2() throws ApiException, DevfileException {
String myLocation = "http://foo-location/";
Map<String, Object> devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?");
@ -157,7 +163,7 @@ public class URLFactoryBuilderTest {
}
@Test
public void testDevfileV2WithFilename() throws BadRequestException, DevfileException {
public void testDevfileV2WithFilename() throws ApiException, DevfileException {
String myLocation = "http://foo-location/";
Map<String, Object> devfileAsMap = Map.of("hello", "there", "how", "are", "you", "?");
@ -220,7 +226,7 @@ public class URLFactoryBuilderTest {
@Test(dataProvider = "devfiles")
public void checkThatDtoHasCorrectNames(DevfileImpl devfile, String expectedGenerateName)
throws BadRequestException, DevfileException, IOException, OverrideParameterException {
throws ApiException, IOException, OverrideParameterException, DevfileException {
DefaultFactoryUrl defaultFactoryUrl = mock(DefaultFactoryUrl.class);
FileContentProvider fileContentProvider = mock(FileContentProvider.class);
when(defaultFactoryUrl.devfileFileLocations())
@ -251,4 +257,72 @@ public class URLFactoryBuilderTest {
assertNull(factory.getDevfile().getMetadata().getName());
assertEquals(factory.getDevfile().getMetadata().getGenerateName(), expectedGenerateName);
}
@Test(dataProvider = "devfileExceptions")
public void checkCorrectExceptionThrownDependingOnCause(
Throwable cause,
Class expectedClass,
String expectedMessage,
Map<String, String> expectedAttributes)
throws IOException, DevfileException {
DefaultFactoryUrl defaultFactoryUrl = mock(DefaultFactoryUrl.class);
FileContentProvider fileContentProvider = mock(FileContentProvider.class);
when(defaultFactoryUrl.devfileFileLocations())
.thenReturn(
singletonList(
new DevfileLocation() {
@Override
public Optional<String> filename() {
return Optional.empty();
}
@Override
public String location() {
return "http://foo.bar/anything";
}
}));
when(fileContentProvider.fetchContent(anyString())).thenThrow(new DevfileException("", cause));
// when
try {
urlFactoryBuilder.createFactoryFromDevfile(
defaultFactoryUrl, fileContentProvider, emptyMap());
} catch (ApiException e) {
assertTrue(e.getClass().isAssignableFrom(expectedClass));
assertEquals(e.getMessage(), expectedMessage);
if (e.getServiceError() instanceof ExtendedError)
assertEquals(((ExtendedError) e.getServiceError()).getAttributes(), expectedAttributes);
}
}
@DataProvider
public static Object[][] devfileExceptions() {
return new Object[][] {
{
new ScmCommunicationException("foo"),
ServerException.class,
"There is an error happened when communicate with SCM server. Error message:foo",
null
},
{
new UnknownScmProviderException("foo", "bar"),
ServerException.class,
"Provided location is unknown or misconfigured on the server side. Error message:foo",
null
},
{
new ScmUnauthorizedException("foo", "bitbucket", "1.0", "http://foo.bar"),
UnauthorizedException.class,
"SCM Authentication required",
Map.of(
"oauth_version",
"1.0",
"oauth_authentication_url",
"http://foo.bar",
"oauth_provider",
"bitbucket")
}
};
}
}