From 6121661b7054f7a8aed313e1120aea952aa1799e Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Fri, 13 Sep 2024 16:58:02 +0300 Subject: [PATCH] Respect the ScmComunicationException on token validation step (#715) Throw the ScmCommunicationException on token validation step instead of returning invalid result. Propagate the ScmCommunicationException to the dashboard as an ApiException. Throw ScmUnauthorizedException on each scm api request with invalid token. Log error instead of throwing RuntimeException on workspace provision steps. --- .../KubernetesPersonalAccessTokenManager.java | 6 +-- .../CredentialsSecretConfigurator.java | 6 ++- .../OAuthTokenSecretsConfigurator.java | 9 +++- .../che/security/oauth/EmbeddedOAuthAPI.java | 2 +- .../azure/devops/AzureDevOpsApiClient.java | 6 ++- ...AzureDevOpsPersonalAccessTokenFetcher.java | 5 +- ...ucketServerPersonalAccessTokenFetcher.java | 5 +- .../bitbucket/BitbucketServerURLParser.java | 2 +- .../HttpBitbucketServerApiClient.java | 4 +- .../server/bitbucket/BitbucketApiClient.java | 27 +++++++---- .../BitbucketPersonalAccessTokenFetcher.java | 10 ++-- ...tbucketPersonalAccessTokenFetcherTest.java | 6 +-- ...tractGithubPersonalAccessTokenFetcher.java | 10 ++-- .../github/AbstractGithubURLParser.java | 23 ++++++---- .../github/AbstractGithubUserDataFetcher.java | 7 ++- .../server/github/GithubApiClient.java | 23 +++++++--- .../GithubPersonalAccessTokenFetcherTest.java | 6 +-- .../server/gitlab/GitlabApiClient.java | 22 ++++++--- .../gitlab/GitlabOAuthTokenFetcher.java | 12 +++-- .../server/gitlab/GitlabUrlParser.java | 10 ++-- .../server/gitlab/GitlabUserDataFetcher.java | 7 ++- .../factory/server/ApiExceptionMapper.java | 46 +++++++++++++++---- .../api/factory/server/FactoryService.java | 9 ++-- .../scm/AbstractGitUserDataFetcher.java | 6 ++- .../scm/PersonalAccessTokenFetcher.java | 4 +- .../scm/PersonalAccessTokenManager.java | 6 ++- .../scm/ScmPersonalAccessTokenFetcher.java | 2 +- .../exception/ScmCommunicationException.java | 22 ++++++++- 28 files changed, 211 insertions(+), 92 deletions(-) diff --git a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java index 18c825cce7a..12e1c8d0997 100644 --- a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java +++ b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java @@ -152,7 +152,7 @@ public PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) @Override public Optional get(Subject cheUser, String scmServerUrl) - throws ScmConfigurationPersistenceException { + throws ScmConfigurationPersistenceException, ScmCommunicationException { return doGetPersonalAccessTokens(cheUser, null, scmServerUrl).stream().findFirst(); } @@ -174,13 +174,13 @@ public PersonalAccessToken get(String scmServerUrl) @Override public Optional get( Subject cheUser, String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException { + throws ScmConfigurationPersistenceException, ScmCommunicationException { return doGetPersonalAccessTokens(cheUser, oAuthProviderName, scmServerUrl).stream().findFirst(); } private List doGetPersonalAccessTokens( Subject cheUser, @Nullable String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException { + throws ScmConfigurationPersistenceException, ScmCommunicationException { List result = new ArrayList<>(); try { LOG.debug( diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java index cf52218b624..a33edb2a921 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java @@ -25,6 +25,8 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This {@link NamespaceConfigurator} ensures that Secret {@link @@ -45,6 +47,8 @@ public class CredentialsSecretConfigurator implements NamespaceConfigurator { private static final String MERGED_GIT_CREDENTIALS_SECRET_NAME = "devworkspace-merged-git-credentials"; + private static final Logger LOG = LoggerFactory.getLogger(CredentialsSecretConfigurator.class); + @Inject public CredentialsSecretConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, @@ -79,7 +83,7 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str | ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException | ScmUnauthorizedException e) { - throw new RuntimeException(e); + LOG.error(e.getMessage(), e); } }); } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java index 0ab31f71223..5739b26c38e 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java @@ -17,12 +17,15 @@ import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; 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.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Ensures that OAuth token that are represented by Kubernetes Secrets are valid. @@ -44,6 +47,8 @@ public class OAuthTokenSecretsConfigurator implements NamespaceConfigurator { "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); + private static final Logger LOG = LoggerFactory.getLogger(OAuthTokenSecretsConfigurator.class); + @Inject public OAuthTokenSecretsConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, @@ -74,8 +79,8 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); personalAccessTokenManager.get( cheSubject, s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL)); - } catch (ScmConfigurationPersistenceException e) { - throw new RuntimeException(e); + } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { + LOG.error(e.getMessage(), e); } }); } diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java index c040f630c0f..fbdac336036 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java @@ -252,7 +252,7 @@ public OAuthToken getOrRefreshToken(String oauthProvider) if (tokenOptional.isPresent()) { return newDto(OAuthToken.class).withToken(tokenOptional.get().getToken()); } - } catch (ScmConfigurationPersistenceException e) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { throw new RuntimeException(e); } } diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java index a18ae884d21..87616776a7c 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 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/ @@ -147,7 +147,9 @@ private T executeRequest( throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "azure-devops"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java index 5721dbf3af6..2c671c33ac7 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java @@ -169,7 +169,8 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!isValidScmServerUrl(params.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); @@ -183,7 +184,7 @@ public Optional> isValid(PersonalAccessTokenParams params) user = azureDevOpsApiClient.getUserWithPAT(params.getToken(), params.getOrganization()); } return Optional.of(Pair.of(Boolean.TRUE, user.getEmailAddress())); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java index 11484956988..5229fd58774 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java @@ -158,7 +158,8 @@ public Optional isValid(PersonalAccessToken accessToken) } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!bitbucketServerApiClient.isConnected(params.getScmProviderUrl())) { // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = @@ -180,7 +181,7 @@ public Optional> isValid(PersonalAccessTokenParams params) try { BitbucketUser user = bitbucketServerApiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getName())); - } catch (ScmItemNotFoundException | ScmUnauthorizedException | ScmCommunicationException e) { + } catch (ScmItemNotFoundException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java index f551cdce423..93e84f6c794 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java @@ -96,7 +96,7 @@ private boolean isUserTokenPresent(String repositoryUrl) { Optional token = personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl); return token.isPresent() && token.get().getScmTokenName().equals(OAUTH_PROVIDER_NAME); - } catch (ScmConfigurationPersistenceException exception) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java index a92c2b8b614..6cf5383c715 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java @@ -411,7 +411,9 @@ private T executeRequest( throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java index 1b0d72c2a0f..6f0ef8a23f3 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 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/ @@ -15,6 +15,7 @@ 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.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,6 +38,7 @@ 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.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; @@ -100,7 +102,8 @@ public BitbucketApiClient() { * @throws ScmBadRequestException */ public BitbucketUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -120,7 +123,8 @@ public BitbucketUser getUser(String authenticationToken) public String getFileContent( String workspace, String repository, String source, String path, String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve( String.format("repositories/%s/%s/src/%s/%s", workspace, repository, source, path)); @@ -148,7 +152,8 @@ public String getFileContent( * @throws ScmBadRequestException */ public BitbucketUserEmail getEmail(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user/emails"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -174,7 +179,8 @@ public BitbucketUserEmail getEmail(String authenticationToken) * scopes. */ public Pair getTokenScopes(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -212,7 +218,8 @@ private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -228,13 +235,17 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "bitbucket", "v2", ""); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, "bitbucket"); } } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java index 5ad3380f31f..460fc95949b 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java @@ -166,13 +166,17 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { try { String[] scopes = bitbucketApiClient.getTokenScopes(personalAccessToken.getToken()).second; return Optional.of(isValidScope(Sets.newHashSet(scopes))); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!bitbucketApiClient.isConnected(params.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); @@ -184,7 +188,7 @@ public Optional> isValid(PersonalAccessTokenParams params) Pair.of( isValidScope(Sets.newHashSet(pair.second)) ? Boolean.TRUE : Boolean.FALSE, pair.first)); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java index 04ffb68bbf4..6bd77b2d3b9 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java @@ -17,7 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; @@ -211,7 +211,7 @@ public void shouldValidateOauthToken() throws Exception { @Test public void shouldNotValidateExpiredOauthToken() throws Exception { - stubFor(get(urlEqualTo("/user")).willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + stubFor(get(urlEqualTo("/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( @@ -235,7 +235,7 @@ public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + bitbucketOauthToken)) - .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java index df0f70c64ff..36495295170 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java @@ -215,13 +215,17 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { return Optional.of(Boolean.FALSE); } } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { GithubApiClient apiClient; if (githubApiClient.isConnected(params.getScmProviderUrl())) { // The url from the token has the same url as the api client, no need to create a new one. @@ -247,7 +251,7 @@ public Optional> isValid(PersonalAccessTokenParams params) GithubUser user = apiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getLogin())); } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java index b1747d5663d..46e65e0c648 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java @@ -14,7 +14,6 @@ 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.net.HttpURLConnection.HTTP_UNAUTHORIZED; 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; @@ -121,7 +120,7 @@ private boolean isUserTokenPresent(String repositoryUrl) { PersonalAccessToken accessToken = token.get(); return accessToken.getScmTokenName().equals(providerName); } - } catch (ScmConfigurationPersistenceException exception) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } @@ -137,14 +136,16 @@ private boolean isApiRequestRelevant(String repositoryUrl) { // If the user request catches the unauthorised error, it means that the provided url // belongs to GitHub. githubApiClient.getUser(""); - } catch (ScmCommunicationException e) { - return e.getStatusCode() == HTTP_UNAUTHORIZED - // Check the error message as well, because other providers might also return 401 - // for such requests. - && e.getMessage().contains("Requires authentication") + } catch (ScmUnauthorizedException e) { + // Check the error message as well, because other providers might also return 401 + // for such requests. + return e.getMessage().contains("Requires authentication") || // for older GitHub Enterprise versions e.getMessage().contains("Must authenticate to access this API."); - } catch (ScmItemNotFoundException | ScmBadRequestException | IllegalArgumentException e) { + } catch (ScmItemNotFoundException + | ScmBadRequestException + | IllegalArgumentException + | ScmCommunicationException e) { return false; } } @@ -289,7 +290,8 @@ private GithubPullRequest getPullRequest( return apiClient.getPullRequest(pullRequestId, repoUser, repoName, null); } catch (ScmItemNotFoundException | ScmCommunicationException - | ScmBadRequestException exception) { + | ScmBadRequestException + | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } @@ -340,7 +342,8 @@ private GithubCommit getLatestCommit( } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException - | URISyntaxException exception) { + | URISyntaxException + | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } } catch (ScmCommunicationException diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java index 1ce559486dd..54c8ddb45c6 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java @@ -23,6 +23,7 @@ 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; /** GitHub user data retriever. */ public abstract class AbstractGithubUserDataFetcher extends AbstractGitUserDataFetcher { @@ -53,7 +54,8 @@ public AbstractGithubUserDataFetcher( @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { GithubUser user = githubApiClient.getUser(token); if (isNullOrEmpty(user.getName()) || isNullOrEmpty(user.getEmail())) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); @@ -65,7 +67,8 @@ protected GitUserData fetchGitUserDataWithOAuthToken(String token) @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { GithubApiClient apiClient = githubApiClient.isConnected(personalAccessToken.getScmProviderUrl()) ? githubApiClient diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java index 57907c1275b..8aa0fbf0696 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java @@ -16,6 +16,7 @@ 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.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; @@ -40,6 +41,7 @@ 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.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; @@ -103,7 +105,8 @@ public GithubApiClient(@Nullable String serverUrl) { * @throws ScmBadRequestException */ public GithubUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -133,7 +136,8 @@ public GithubUser getUser(String authenticationToken) */ public GithubPullRequest getPullRequest( String id, String username, String repoName, String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/pulls/%s", username, repoName, id)); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); @@ -167,7 +171,7 @@ public GithubPullRequest getPullRequest( public GithubCommit getLatestCommit( String user, String repository, String branch, @Nullable String authenticationToken) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, - URISyntaxException { + URISyntaxException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/commits", user, repository)); @@ -208,7 +212,8 @@ public GithubCommit getLatestCommit( * @throws ScmBadRequestException */ public Pair getTokenScopes(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -270,7 +275,9 @@ private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { + String provider = GITHUB_SAAS_ENDPOINT.equals(getServerUrl()) ? "github" : "github-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -290,13 +297,15 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "github", "v2", ""); default: throw new ScmCommunicationException( - "Unexpected status code " + statusCode + " " + body, statusCode); + "Unexpected status code " + statusCode + " " + body, statusCode, provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, provider); } } diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java index 2193a349ec9..2bf561c630a 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java @@ -17,7 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.github.GithubPersonalAccessTokenFetcher.DEFAULT_TOKEN_SCOPES; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; @@ -176,7 +176,7 @@ public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) - .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @@ -254,7 +254,7 @@ public void shouldValidateOauthToken() throws Exception { @Test public void shouldNotValidateExpiredOauthToken() throws Exception { - stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java index 6f2a902e9bf..71a67fc3aec 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 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/ @@ -13,6 +13,7 @@ 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.databind.ObjectMapper; @@ -33,6 +34,7 @@ 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.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +67,8 @@ public GitlabApiClient(String serverUrl) { } public GitlabUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = serverUrl.resolve("/api/v4/user"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -88,7 +91,7 @@ public GitlabUser getUser(String authenticationToken) } public GitlabPersonalAccessTokenInfo getPersonalAccessTokenInfo(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = serverUrl.resolve("/api/v4/personal_access_tokens/self"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -115,7 +118,7 @@ public GitlabPersonalAccessTokenInfo getPersonalAccessTokenInfo(String authentic } public GitlabOauthTokenInfo getOAuthTokenInfo(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = serverUrl.resolve("/oauth/token/info"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -143,7 +146,9 @@ public GitlabOauthTokenInfo getOAuthTokenInfo(String authenticationToken) private T executeRequest( HttpClient httpClient, HttpRequest request, Function bodyConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { + String provider = "http://gitlab.com".equals(serverUrl.toString()) ? "gitlab" : "gitlab-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -159,14 +164,17 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "gitlab", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, - response.statusCode()); + response.statusCode(), + provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, provider); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java index c5e59cd2d95..9444afff638 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java @@ -188,7 +188,7 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken()); return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)); - } catch (ScmItemNotFoundException | ScmCommunicationException e) { + } catch (ScmItemNotFoundException | ScmCommunicationException | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } else { @@ -201,14 +201,18 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { } else { return Optional.of(Boolean.FALSE); } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl()); if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) { if (OAUTH_PROVIDER_NAME.equals(params.getScmTokenName())) { @@ -234,7 +238,7 @@ public Optional> isValid(PersonalAccessTokenParams params) // latest GitLab version, we just perform check by accessing something from API. // TODO: add PAT scope validation return Optional.of(Pair.of(Boolean.TRUE, user.getUsername())); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java index d094b114a70..53faa2663da 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java @@ -12,7 +12,6 @@ package org.eclipse.che.api.factory.server.gitlab; import static java.lang.String.format; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.util.regex.Pattern.compile; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; @@ -31,6 +30,7 @@ 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.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; @@ -90,7 +90,7 @@ private boolean isUserTokenPresent(String repositoryUrl) { PersonalAccessToken accessToken = token.get(); return accessToken.getScmTokenName().equals(OAUTH_PROVIDER_NAME); } - } catch (ScmConfigurationPersistenceException exception) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } @@ -115,9 +115,9 @@ private boolean isApiRequestRelevant(String repositoryUrl) { // If the token request catches the unauthorised error, it means that the provided url // belongs to Gitlab. gitlabApiClient.getOAuthTokenInfo(""); - } catch (ScmCommunicationException e) { - return e.getStatusCode() == HTTP_UNAUTHORIZED; - } catch (ScmItemNotFoundException | IllegalArgumentException e) { + } catch (ScmUnauthorizedException e) { + return true; + } catch (ScmItemNotFoundException | IllegalArgumentException | ScmCommunicationException e) { return false; } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java index 15f2cffc7ad..e276d2c6987 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java @@ -26,6 +26,7 @@ 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.commons.annotation.Nullable; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.inject.ConfigurationException; @@ -72,7 +73,8 @@ public GitlabUserDataFetcher( @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { for (String gitlabServerEndpoint : this.registeredGitlabEndpoints) { GitlabUser user = new GitlabApiClient(gitlabServerEndpoint).getUser(token); return new GitUserData(user.getName(), user.getEmail()); @@ -83,7 +85,8 @@ protected GitUserData fetchGitUserDataWithOAuthToken(String token) @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { GitlabUser user = new GitlabApiClient(personalAccessToken.getScmProviderUrl()) .getUser(personalAccessToken.getToken()); diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java index 122c5b4d34f..f119ea40230 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 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/ @@ -11,11 +11,16 @@ */ package org.eclipse.che.api.factory.server; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +import java.util.Collections; import java.util.Map; 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.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; @@ -48,6 +53,16 @@ public static ApiException toApiException(ScmUnauthorizedException scmUnauthoriz + scmUnauthorizedException.getMessage()); } + public static ApiException toApiException(ScmCommunicationException scmCommunicationException) { + ApiException apiException = getApiException(scmCommunicationException); + return (apiException != null) + ? apiException + : new ServerException( + "Error occurred during SCM communication." + + "Cause: " + + scmCommunicationException.getMessage()); + } + public static ApiException toApiException( DevfileException devfileException, DevfileLocation location) { ApiException cause = getApiException(devfileException.getCause()); @@ -70,15 +85,28 @@ private static ApiException getApiException(Throwable throwable) { "oauth_version", scmCause.getOauthVersion(), "oauth_provider", scmCause.getOauthProvider(), "oauth_authentication_url", scmCause.getAuthenticateUrl())); - } else if (throwable instanceof UnknownScmProviderException) { - return new ServerException( - "Provided location is unknown or misconfigured on the server side. Error message: " - + throwable.getMessage()); - } else if (throwable instanceof ScmCommunicationException) { - return new ServerException( - "There is an error happened when communicate with SCM server. Error message: " - + throwable.getMessage()); + } else { + if (throwable instanceof UnknownScmProviderException) { + return new ServerException( + appendErrorMessage( + "Provided location is unknown or misconfigured on the server side", + throwable.getMessage())); + } else if (throwable instanceof ScmCommunicationException) { + return new ServerException( + newDto(ExtendedError.class) + .withMessage( + appendErrorMessage( + "Error occurred during SCM communication.", throwable.getMessage())) + .withErrorCode(404) + .withAttributes( + Collections.singletonMap( + "provider", ((ScmCommunicationException) throwable).getProvider()))); + } } return null; } + + private static String appendErrorMessage(String message, String errorMessage) { + return message + (isNullOrEmpty(errorMessage) ? "" : " Error message: " + errorMessage); + } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java index 496e2c16264..86aef36214f 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java @@ -159,13 +159,14 @@ public void refreshToken(@Parameter(description = "Factory url") @QueryParam("ur personalAccessTokenManager.getAndStore(scmServerUrl); } } - } catch (ScmCommunicationException - | ScmConfigurationPersistenceException - | UnknownScmProviderException - | UnsatisfiedScmPreconditionException e) { + } catch (ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { throw new ApiException(e); } catch (ScmUnauthorizedException e) { throw toApiException(e); + } catch (ScmCommunicationException e) { + throw toApiException(e); + } catch (UnknownScmProviderException e) { + // ignore the exception as it is not a problem if the provider from the given URL is unknown } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java index 8b1b94d27a9..21d92a937a8 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java @@ -56,11 +56,13 @@ public GitUserData fetchGitUserData() } protected abstract GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException; + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException; protected abstract GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException; + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException; protected abstract String getLocalAuthenticateUrl(); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java index da86a930bbd..0abd3e095cb 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java @@ -75,6 +75,8 @@ Optional isValid(PersonalAccessToken personalAccessToken) * the token has expected scope of permissions, false if the token scopes does not match the * expected ones. Empty optional if {@link PersonalAccessTokenFetcher} is not able to confirm * or deny that token is valid. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ - Optional> isValid(PersonalAccessTokenParams params); + Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java index 9bbe0bd9ad2..dab000515a5 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java @@ -48,9 +48,10 @@ PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) * @return personal access token * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional get(Subject cheUser, String scmServerUrl) - throws ScmConfigurationPersistenceException; + throws ScmConfigurationPersistenceException, ScmCommunicationException; /** * Gets {@link PersonalAccessToken} from permanent storage. @@ -79,10 +80,11 @@ PersonalAccessToken get(String scmServerUrl) * @return personal access token * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional get( Subject cheUser, String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException; + throws ScmConfigurationPersistenceException, ScmCommunicationException; /** * Gets {@link PersonalAccessToken} from permanent storage. If the token is not found try to fetch diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java index f276c055043..cb863226f78 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java @@ -94,7 +94,7 @@ public boolean isValid(PersonalAccessToken personalAccessToken) * fetchers return an scm username, return it. Otherwise, return null. */ public Optional getScmUsername(PersonalAccessTokenParams params) - throws UnknownScmProviderException { + throws UnknownScmProviderException, ScmCommunicationException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { Optional> isValid = fetcher.isValid(params); if (isValid.isPresent() && isValid.get().first) { diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java index 6bfd7fa31f9..cc6427568cd 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 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/ @@ -14,11 +14,18 @@ /** Thrown when problem occurred during communication with scm provider */ public class ScmCommunicationException extends Exception { private int statusCode; + private String provider; public ScmCommunicationException(String message) { super(message); } + public ScmCommunicationException(String message, int statusCode, String provider) { + super(message); + this.statusCode = statusCode; + this.provider = provider; + } + public ScmCommunicationException(String message, int statusCode) { super(message); this.statusCode = statusCode; @@ -28,7 +35,20 @@ public ScmCommunicationException(String message, Throwable cause) { super(message, cause); } + public ScmCommunicationException(String message, Throwable cause, String provider) { + super(message, cause); + this.provider = provider; + } + public int getStatusCode() { return statusCode; } + + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } }