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 87616776a7..ec7633216d 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 @@ -12,9 +12,12 @@ package org.eclipse.che.api.factory.server.azure.devops; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; 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.api.factory.server.azure.devops.AzureDevOps.formatAuthorizationHeader; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; @@ -40,6 +43,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; @@ -80,7 +84,8 @@ public AzureDevOpsApiClient( *

https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/users/get?view=azure-devops-rest-7.0&tabs=HTTP */ public AzureDevOpsUser getUserWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final String url = String.format( "%s/_apis/profile/profiles/me?api-version=%s", @@ -94,7 +99,8 @@ public AzureDevOpsUser getUserWithOAuthToken(String token) * organization. */ public AzureDevOpsUser getUserWithPAT(String pat, String organization) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final String url = String.format( "%s/%s/_apis/profile/profiles/me?api-version=%s", @@ -103,7 +109,8 @@ public AzureDevOpsUser getUserWithPAT(String pat, String organization) } private AzureDevOpsUser getUser(String url, String authorizationHeader) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final HttpRequest userDataRequest = HttpRequest.newBuilder(URI.create(url)) .headers("Authorization", authorizationHeader) @@ -129,7 +136,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()); @@ -145,6 +153,10 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + case HTTP_FORBIDDEN: + case HTTP_MOVED_TEMP: + throw new ScmUnauthorizedException(body, "azure-devops", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, 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 2c671c33ac..c6b3e274cc 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 @@ -163,7 +163,10 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { personalAccessToken.getToken(), personalAccessToken.getScmOrganization()); } return Optional.of(personalAccessToken.getScmUserName().equals(user.getEmailAddress())); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @@ -184,7 +187,7 @@ public Optional> isValid(PersonalAccessTokenParams params) user = azureDevOpsApiClient.getUserWithPAT(params.getToken(), params.getOrganization()); } return Optional.of(Pair.of(Boolean.TRUE, user.getEmailAddress())); - } catch (ScmItemNotFoundException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUserDataFetcher.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUserDataFetcher.java index b594687eed..482fb8158e 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsUserDataFetcher.java @@ -22,6 +22,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; /** * Azure DevOps user data fetcher. @@ -48,7 +49,8 @@ public AzureDevOpsUserDataFetcher( @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { AzureDevOpsUser user = azureDevOpsApiClient.getUserWithOAuthToken(token); return new GitUserData(user.getDisplayName(), user.getEmailAddress()); } @@ -56,7 +58,8 @@ protected GitUserData fetchGitUserDataWithOAuthToken(String token) @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { AzureDevOpsUser user = azureDevOpsApiClient.getUserWithPAT( personalAccessToken.getToken(), personalAccessToken.getScmOrganization()); diff --git a/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java index ed51fe091f..62ff7aa037 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java @@ -18,6 +18,7 @@ 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_MOVED_TEMP; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -115,9 +116,23 @@ public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) - .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + azureOauthToken)) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + azureOauthToken)) .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); personalAccessTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } + + @Test( + expectedExceptions = ScmUnauthorizedException.class) + public void shouldThrowUnauthorizedExceptionOnRedirectResponse() throws Exception { + Subject subject = new SubjectImpl("Username", "id1", "token", false); + OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(azureOauthToken).withScope(""); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); + stubFor( + get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer " + azureOauthToken)) + .willReturn(aResponse().withStatus(HTTP_MOVED_TEMP))); + + personalAccessTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); + } }