diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java index 4840186c14..657c5c0746 100644 --- a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.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/ @@ -12,6 +12,7 @@ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.util.store.MemoryDataStoreFactory; @@ -77,7 +78,7 @@ public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { private String getTestRequestUrl() { return "https://bitbucket.org".equals(bitbucketEndpoint) ? "https://api.bitbucket.org/2.0/user" - : bitbucketEndpoint + "/rest/api/1.0/application-properties"; + : bitbucketEndpoint + "/plugins/servlet/applinks/whoami"; } @Override @@ -89,11 +90,12 @@ public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { throws OAuthAuthenticationException { HttpURLConnection urlConnection = null; InputStream urlInputStream = null; - + String result; try { urlConnection = (HttpURLConnection) new URL(requestUrl).openConnection(); urlConnection.setRequestProperty("Authorization", "Bearer " + accessToken); urlInputStream = urlConnection.getInputStream(); + result = new String(urlInputStream.readAllBytes(), UTF_8); } catch (IOException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } finally { @@ -108,5 +110,8 @@ public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { urlConnection.disconnect(); } } + if (isNullOrEmpty(result)) { + throw new OAuthAuthenticationException("Empty response from Bitbucket Server API"); + } } } 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 dc65dbb624..382f6c3d66 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 @@ -100,7 +100,7 @@ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccess scmServerUrl, OAUTH_PROVIDER_NAME, EnvironmentContext.getCurrent().getSubject().getUserId(), - user.getName(), + null, user.getSlug(), token.getName(), valueOf(token.getId()), @@ -111,30 +111,34 @@ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccess } @Override - public Optional isValid(PersonalAccessToken personalAccessToken) + public Optional isValid(PersonalAccessToken accessToken) throws ScmCommunicationException, ScmUnauthorizedException { - if (!bitbucketServerApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { + if (!bitbucketServerApiClient.isConnected(accessToken.getScmProviderUrl())) { // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = new HttpBitbucketServerApiClient( - personalAccessToken.getScmProviderUrl(), + accessToken.getScmProviderUrl(), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint.toString()); try { - apiClient.getUser(personalAccessToken.getToken()); + apiClient.getUser(accessToken.getToken()); return Optional.of(Boolean.TRUE); } catch (ScmItemNotFoundException | ScmUnauthorizedException | ScmCommunicationException exception) { - LOG.debug( - "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); + LOG.debug("not a valid url {} for current fetcher ", accessToken.getScmProviderUrl()); return Optional.empty(); } } try { BitbucketPersonalAccessToken bitbucketPersonalAccessToken = - bitbucketServerApiClient.getPersonalAccessToken(personalAccessToken.getScmTokenId()); + bitbucketServerApiClient.getPersonalAccessToken( + accessToken.getScmTokenId(), + // Pass oauth token to fetch personal access token + // TODO: rename the PersonalAccessToken interface to more generic name, so both OAuth + // and personal access token implementations would be suitable. + accessToken.getToken()); return Optional.of(DEFAULT_TOKEN_SCOPE.equals(bitbucketPersonalAccessToken.getPermissions())); } catch (ScmItemNotFoundException e) { return Optional.of(Boolean.FALSE); @@ -169,7 +173,8 @@ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccess } // Token is added by OAuth. Token id is available. BitbucketPersonalAccessToken bitbucketPersonalAccessToken = - bitbucketServerApiClient.getPersonalAccessToken(params.getScmTokenId()); + bitbucketServerApiClient.getPersonalAccessToken( + params.getScmTokenId(), params.getToken()); return Optional.of( Pair.of( DEFAULT_TOKEN_SCOPE.equals(bitbucketPersonalAccessToken.getPermissions()) 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 e7fa8ddc9c..a579b27901 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 @@ -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/ @@ -145,7 +145,9 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { @Override public void deletePersonalAccessTokens(String tokenId) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { - URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + getUserSlug() + "/" + tokenId); + URI uri = + serverUri.resolve( + "./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty()) + "/" + tokenId); HttpRequest request = HttpRequest.newBuilder(uri) .DELETE() @@ -185,7 +187,7 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { ScmItemNotFoundException { BitbucketPersonalAccessToken token = new BitbucketPersonalAccessToken(tokenName, permissions, 90); - URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + getUserSlug()); + URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty())); try { HttpRequest request = @@ -229,7 +231,7 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { return doGetItems( Optional.empty(), BitbucketPersonalAccessToken.class, - "./rest/access-tokens/1.0/users/" + getUserSlug(), + "./rest/access-tokens/1.0/users/" + getUserSlug(Optional.empty()), null); } catch (ScmBadRequestException e) { throw new ScmCommunicationException(e.getMessage(), e); @@ -237,14 +239,19 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { } @Override - public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId) + public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { - URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + getUserSlug() + "/" + tokenId); + URI uri = + serverUri.resolve( + "./rest/access-tokens/1.0/users/" + + getUserSlug(Optional.of(oauthToken)) + + "/" + + tokenId); HttpRequest request = HttpRequest.newBuilder(uri) .headers( "Authorization", - computeAuthorizationHeader("GET", uri.toString()), + "Bearer " + oauthToken, HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) .timeout(DEFAULT_HTTP_TIMEOUT) @@ -269,9 +276,9 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { } } - private String getUserSlug() + private String getUserSlug(Optional token) throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { - return getUser(Optional.empty()).getSlug(); + return getUser(token).getSlug(); } private BitbucketUser getUser(Optional token) diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiClient.java index e399d3f603..9f298dd381 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiClient.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/ @@ -100,9 +100,10 @@ public interface BitbucketServerApiClient { /** * @param tokenId - bitbucket personal access token id. + * @param oauthToken - bitbucket oauth token. * @return - Bitbucket personal access token. * @throws ScmCommunicationException */ - BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId) + BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NoopBitbucketServerApiClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NoopBitbucketServerApiClient.java index d6cfd3efa3..d0d83829c0 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NoopBitbucketServerApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NoopBitbucketServerApiClient.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/ @@ -77,7 +77,7 @@ public class NoopBitbucketServerApiClient implements BitbucketServerApiClient { } @Override - public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId) + public BitbucketPersonalAccessToken getPersonalAccessToken(String tokenId, String oauthToken) throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { throw new RuntimeException("Invalid usage of BitbucketServerApi"); } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java index c917555e11..596ed6282d 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.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/ @@ -156,7 +156,7 @@ public class BitbucketServerPersonalAccessTokenFetcherTest { assertNotNull(result); assertEquals(result.getScmProviderUrl(), someBitbucketURL); assertEquals(result.getCheUserId(), subject.getUserId()); - assertEquals(result.getScmOrganization(), bitbucketUser.getName()); + assertNull(result.getScmOrganization(), bitbucketUser.getName()); assertEquals(result.getScmTokenId(), valueOf(bitbucketPersonalAccessToken.getId())); assertEquals(result.getToken(), bitbucketPersonalAccessToken.getToken()); } @@ -221,10 +221,12 @@ public class BitbucketServerPersonalAccessTokenFetcherTest { throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException { // given when(personalAccessTokenParams.getScmProviderUrl()).thenReturn(someBitbucketURL); + when(personalAccessTokenParams.getToken()).thenReturn(bitbucketPersonalAccessToken.getToken()); when(personalAccessTokenParams.getScmTokenId()) .thenReturn(bitbucketPersonalAccessToken.getId()); when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); - when(bitbucketServerApiClient.getPersonalAccessToken(eq(bitbucketPersonalAccessToken.getId()))) + when(bitbucketServerApiClient.getPersonalAccessToken( + eq(bitbucketPersonalAccessToken.getId()), eq(bitbucketPersonalAccessToken.getToken()))) .thenReturn(bitbucketPersonalAccessToken); // when Optional> result = fetcher.isValid(personalAccessTokenParams); diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java index 9d019932e0..538ab2b384 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.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/ @@ -312,14 +312,14 @@ public class HttpBitbucketServerApiClientTest { // given stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) - .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn( ok().withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json"))); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) - .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( @@ -328,7 +328,7 @@ public class HttpBitbucketServerApiClientTest { .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when - BitbucketPersonalAccessToken result = bitbucketServer.getPersonalAccessToken("5"); + BitbucketPersonalAccessToken result = bitbucketServer.getPersonalAccessToken("5", "token"); // then assertNotNull(result); assertEquals(result.getToken(), "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs"); @@ -346,7 +346,7 @@ public class HttpBitbucketServerApiClientTest { .willReturn(notFound())); // when - bitbucketServer.getPersonalAccessToken("5"); + bitbucketServer.getPersonalAccessToken("5", "token"); } @Test(expectedExceptions = ScmUnauthorizedException.class) @@ -361,18 +361,18 @@ public class HttpBitbucketServerApiClientTest { } @Test(expectedExceptions = ScmUnauthorizedException.class) - public void shouldBeAbleToThrowScmUnauthorizedExceptionOnGePAT() + public void shouldBeAbleToThrowScmUnauthorizedExceptionOnGetPAT() throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { // given stubFor( get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster/5")) - .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) .willReturn(unauthorized())); stubFor( get(urlPathEqualTo("/rest/api/1.0/users")) - .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .withQueryParam("start", equalTo("0")) .withQueryParam("limit", equalTo("25")) .willReturn( @@ -381,7 +381,7 @@ public class HttpBitbucketServerApiClientTest { .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); // when - bitbucketServer.getPersonalAccessToken("5"); + bitbucketServer.getPersonalAccessToken("5", "token"); } @Test( @@ -400,7 +400,7 @@ public class HttpBitbucketServerApiClientTest { wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); // when - localServer.getPersonalAccessToken("5"); + localServer.getUser(); } @Test