From cb3565dbf8b3b4f7f720cd1f4f3a99bf6d4e6c2c Mon Sep 17 00:00:00 2001 From: Igor Vinokur Date: Wed, 15 Feb 2023 16:43:27 +0200 Subject: [PATCH] Support Bitbucket-server oAuth2 factory (#440) Apply Bitbucket Server oAuth-2 configuration for the factory flow. --- .../che/api/deploy/WsMasterModule.java | 2 +- .../webapp/WEB-INF/classes/che/che.properties | 4 +- wsmaster/che-core-api-auth-bitbucket/pom.xml | 6 +- .../oauth/BitbucketOAuthAuthenticator.java | 56 ++++++++++------ .../BitbucketOAuthAuthenticatorProvider.java | 34 +++++----- ...ucketServerOAuthAuthenticatorProvider.java | 4 +- wsmaster/che-core-api-auth-github/pom.xml | 4 -- .../oauth/GitHubOAuthAuthenticator.java | 21 ------ .../GitHubOAuthAuthenticatorProvider.java | 8 --- wsmaster/che-core-api-auth-gitlab/pom.xml | 4 -- .../oauth/GitLabOAuthAuthenticator.java | 22 ------- .../GitLabOAuthAuthenticatorProvider.java | 7 -- .../oauth/OpenShiftOAuthAuthenticator.java | 6 -- .../che/security/oauth/EmbeddedOAuthAPI.java | 12 +++- .../security/oauth/OAuthAuthenticator.java | 10 --- .../pom.xml | 9 +++ .../BitbucketServerApiProvider.java | 56 +++++++++------- .../bitbucket/BitbucketServerModule.java | 1 - ...ucketServerPersonalAccessTokenFetcher.java | 13 +++- .../bitbucket/BitbucketServerURLParser.java | 10 ++- .../HttpBitbucketServerApiClient.java | 66 +++++++++++++++---- .../BitbucketServerApiClientProviderTest.java | 49 +++++++++----- ...horizingFactoryParametersResolverTest.java | 3 + ...tServerPersonalAccessTokenFetcherTest.java | 19 +++++- .../BitbucketServerScmFileResolverTest.java | 5 +- .../BitbucketServerURLParserTest.java | 5 +- .../HttpBitbucketServerApiClientTest.java | 62 +++++++++++++++-- 27 files changed, 303 insertions(+), 195 deletions(-) rename wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/{security/oauth1 => api/factory/server/bitbucket}/BitbucketServerApiProvider.java (60%) rename wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/{server => }/HttpBitbucketServerApiClient.java (87%) rename wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/{security/oauth1 => api/factory/server/bitbucket}/BitbucketServerApiClientProviderTest.java (78%) diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 5dff6000c8..4d8cd91297 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -395,7 +395,7 @@ public class WsMasterModule extends AbstractModule { } bind(TokenValidator.class).to(NotImplementedTokenValidator.class); bind(ProfileDao.class).to(JpaProfileDao.class); - bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class); + bind(OAuthAPI.class).to(EmbeddedOAuthAPI.class).asEagerSingleton(); } bind(AdminPermissionInitializer.class).asEagerSingleton(); diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 3eeb979568..ca27f889da 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -155,7 +155,7 @@ che.oauth1.bitbucket.consumerkeypath=NULL che.oauth1.bitbucket.privatekeypath=NULL # Bitbucket Server URL. To work correctly with factories, the same URL # has to be part of `che.integration.bitbucket.server_endpoints` too. -che.oauth1.bitbucket.endpoint=NULL +che.oauth.bitbucket.endpoint=https://bitbucket.org # Configuration of Bitbucket OAuth2 client. Used to obtain Personal access tokens. # Location of the file with Bitbucket client id. @@ -172,7 +172,7 @@ che.oauth.bitbucket.tokenuri= https://bitbucket.org/site/oauth2/access_token # Bitbucket OAuth redirect URIs. # Separate multiple values with comma, for example: URI,URI,URI -che.oauth.bitbucket.redirecturis= http://localhost:${CHE_PORT}/api/oauth/callback +che.oauth.bitbucket.redirecturis= https://${CHE_HOST}/api/oauth/callback ### Internal diff --git a/wsmaster/che-core-api-auth-bitbucket/pom.xml b/wsmaster/che-core-api-auth-bitbucket/pom.xml index 2970a2e7ec..95fcbc421d 100644 --- a/wsmaster/che-core-api-auth-bitbucket/pom.xml +++ b/wsmaster/che-core-api-auth-bitbucket/pom.xml @@ -35,6 +35,10 @@ com.google.inject guice + + com.google.oauth-client + google-oauth-client + jakarta.inject jakarta.inject-api @@ -53,7 +57,7 @@ org.eclipse.che.core - che-core-commons-json + che-core-commons-lang org.slf4j 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 ce78d8d82c..4840186c14 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 @@ -11,33 +11,42 @@ */ package org.eclipse.che.security.oauth; +import static com.google.common.base.Strings.isNullOrEmpty; + +import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.util.store.MemoryDataStoreFactory; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.List; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; -import org.eclipse.che.commons.json.JsonHelper; -import org.eclipse.che.commons.json.JsonParseException; -import org.eclipse.che.security.oauth.shared.User; /** OAuth authentication for BitBucket SAAS account. */ @Singleton public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { + private final String bitbucketEndpoint; + public BitbucketOAuthAuthenticator( - String clientId, String clientSecret, String[] redirectUris, String authUri, String tokenUri) + String bitbucketEndpoint, + String clientId, + String clientSecret, + String[] redirectUris, + String authUri, + String tokenUri) throws IOException { + this.bitbucketEndpoint = bitbucketEndpoint; configure( clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } - private static final String USER_URL = "https://api.bitbucket.org/2.0/user"; - @Override - public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException { - BitbucketUser user = getJson(USER_URL, accessToken.getToken(), BitbucketUser.class); - return user; + public String getAuthenticateUrl(URL requestUrl, List scopes) { + AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes); + url.setState(prepareState(requestUrl)); + url.set("redirect_uri", findRedirectUrl(requestUrl)); + return url.build(); } @Override @@ -50,35 +59,42 @@ public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { final OAuthToken token = super.getToken(userId); // Need to check if token is valid for requests, if valid - return it to caller. try { - if (token == null - || token.getToken() == null - || token.getToken().isEmpty() - || getJson(USER_URL, token.getToken(), BitbucketUser.class) == null) { + if (token == null || isNullOrEmpty(token.getToken())) { return null; } + testRequest(getTestRequestUrl(), token.getToken()); } catch (OAuthAuthenticationException e) { return null; } return token; } - @Override - public String getEndpointUrl() { - return "https://bitbucket.org"; + /** + * Generate an API request URL, to use for a token validation. + * + * @return Bitbucket Cloud or Server API request URL + */ + private String getTestRequestUrl() { + return "https://bitbucket.org".equals(bitbucketEndpoint) + ? "https://api.bitbucket.org/2.0/user" + : bitbucketEndpoint + "/rest/api/1.0/application-properties"; } @Override - protected O getJson(String getUserUrl, String accessToken, Class userClass) + public String getEndpointUrl() { + return bitbucketEndpoint; + } + + private void testRequest(String requestUrl, String accessToken) throws OAuthAuthenticationException { HttpURLConnection urlConnection = null; InputStream urlInputStream = null; try { - urlConnection = (HttpURLConnection) new URL(getUserUrl).openConnection(); + urlConnection = (HttpURLConnection) new URL(requestUrl).openConnection(); urlConnection.setRequestProperty("Authorization", "Bearer " + accessToken); urlInputStream = urlConnection.getInputStream(); - return JsonHelper.fromJson(urlInputStream, userClass, null); - } catch (JsonParseException | IOException e) { + } catch (IOException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } finally { if (urlInputStream != null) { diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticatorProvider.java index 8561045669..5f4a97f4a5 100644 --- a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticatorProvider.java +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticatorProvider.java @@ -12,6 +12,7 @@ package org.eclipse.che.security.oauth; import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import java.io.IOException; import java.nio.file.Files; @@ -21,9 +22,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; -import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.security.oauth.shared.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +37,7 @@ public class BitbucketOAuthAuthenticatorProvider implements Providercom.google.inject guice - - com.sun.mail - jakarta.mail - jakarta.inject jakarta.inject-api diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java index 615779a480..7cacae7c7a 100644 --- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java @@ -15,8 +15,6 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.api.client.util.store.MemoryDataStoreFactory; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -24,7 +22,6 @@ import java.net.URL; import java.util.Base64; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; -import org.eclipse.che.security.oauth.shared.User; /** OAuth authentication for github account. */ @Singleton @@ -53,24 +50,6 @@ public class GitHubOAuthAuthenticator extends OAuthAuthenticator { clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } - @Override - public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException { - GitHubUser user = getJson(githubApiUrl + "/user", accessToken.getToken(), GitHubUser.class); - final String email = user.getEmail(); - - if (isNullOrEmpty(email)) { - throw new OAuthAuthenticationException( - "Sorry, we failed to find any verified emails associated with your GitHub account." - + " Please, verify at least one email in your GitHub account and try to connect with GitHub again."); - } - try { - new InternetAddress(email).validate(); - } catch (AddressException e) { - throw new OAuthAuthenticationException(e.getMessage()); - } - return user; - } - @Override public final String getOAuthProvider() { return "github"; diff --git a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java index cf5a0b29ba..d14c446e6f 100644 --- a/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java +++ b/wsmaster/che-core-api-auth-github/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticatorProvider.java @@ -22,9 +22,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; -import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.security.oauth.shared.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,12 +96,6 @@ public class GitHubOAuthAuthenticatorProvider implements Providercom.google.inject guice - - com.sun.mail - jakarta.mail - jakarta.inject jakarta.inject-api diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java index 08e35cc2a1..fd68a7f926 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java @@ -11,12 +11,9 @@ */ package org.eclipse.che.security.oauth; -import static com.google.common.base.Strings.isNullOrEmpty; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.api.client.util.store.MemoryDataStoreFactory; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -28,7 +25,6 @@ import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; -import org.eclipse.che.security.oauth.shared.User; /** * OAuth2 authenticator for GitLab account. @@ -57,24 +53,6 @@ public class GitLabOAuthAuthenticator extends OAuthAuthenticator { new MemoryDataStoreFactory()); } - @Override - public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException { - GitLabUser user = getJson(gitlabUserEndpoint, accessToken.getToken(), GitLabUser.class); - final String email = user.getEmail(); - - if (isNullOrEmpty(email)) { - throw new OAuthAuthenticationException( - "Sorry, we failed to find any verified email associated with your GitLab account." - + " Please, verify at least one email in your account and try to connect with GitLab again."); - } - try { - new InternetAddress(email).validate(); - } catch (AddressException e) { - throw new OAuthAuthenticationException(e.getMessage()); - } - return user; - } - @Override public String getOAuthProvider() { return "gitlab"; diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java index e06f81bbf9..a8a11015ec 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java +++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java @@ -20,9 +20,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; -import org.eclipse.che.api.auth.shared.dto.OAuthToken; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.security.oauth.shared.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,11 +67,6 @@ public class GitLabOAuthAuthenticatorProvider implements Provider errorValues) throws NotFoundException { URL requestUrl = getRequestUrl(uriInfo); Map> params = getQueryParametersFromState(getState(requestUrl)); - if (errorValues != null && errorValues.contains("access_denied")) { + errorValues = errorValues == null ? uriInfo.getQueryParameters().get("error") : errorValues; + if (!isNullOrEmpty(redirectAfterLogin) + && errorValues != null + && errorValues.contains("access_denied")) { return Response.temporaryRedirect( - uriInfo.getRequestUriBuilder().replacePath(errorPage).replaceQuery(null).build()) + URI.create(redirectAfterLogin + "&error_code=access_denied")) .build(); } final String providerName = getParameter(params, "oauth_provider"); diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java index fd314ac0bb..c0f109eff2 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java @@ -42,7 +42,6 @@ import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; import org.eclipse.che.security.oauth.shared.OAuthTokenProvider; -import org.eclipse.che.security.oauth.shared.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -223,15 +222,6 @@ public abstract class OAuthAuthenticator { } } - /** - * Get user info. - * - * @param accessToken oauth access token - * @return user info - * @throws OAuthAuthenticationException if fail to get user info - */ - public abstract User getUser(OAuthToken accessToken) throws OAuthAuthenticationException; - /** * Get the name of OAuth provider supported by current implementation. * diff --git a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml index a6bc774406..c42dc9bbfb 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml +++ b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml @@ -66,6 +66,10 @@ org.eclipse.che.core che-core-api-auth-bitbucket + + org.eclipse.che.core + che-core-api-auth-shared + org.eclipse.che.core che-core-api-core @@ -120,6 +124,11 @@ wiremock-jre8-standalone test + + jakarta.servlet + jakarta.servlet-api + test + org.eclipse.che.core che-core-commons-json diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerApiProvider.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiProvider.java similarity index 60% rename from wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerApiProvider.java rename to wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiProvider.java index 68bcd56b21..93c28d793d 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerApiProvider.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2023 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -9,7 +9,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.che.security.oauth1; +package org.eclipse.che.api.factory.server.bitbucket; import static com.google.common.base.Strings.isNullOrEmpty; @@ -23,11 +23,13 @@ import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; -import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.StringUtils; import org.eclipse.che.inject.ConfigurationException; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; +import org.eclipse.che.security.oauth1.OAuthAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,13 +39,19 @@ public class BitbucketServerApiProvider implements Provider authenticators) { - bitbucketServerApiClient = doGet(bitbucketEndpoints, bitbucketOauth1Endpoint, authenticators); + this.apiEndpoint = apiEndpoint; + this.oAuthAPI = oAuthAPI; + bitbucketServerApiClient = doGet(bitbucketEndpoints, bitbucketOauthEndpoint, authenticators); LOG.debug("Bitbucket server api is used {}", bitbucketServerApiClient); } @@ -52,40 +60,38 @@ public class BitbucketServerApiProvider implements Provider authenticators) { - if (isNullOrEmpty(bitbucketOauth1Endpoint) && isNullOrEmpty(rawBitbucketEndpoints)) { + boolean isBitbucketCloud = bitbucketOauthEndpoint.equals("https://bitbucket.org"); + if (isBitbucketCloud && isNullOrEmpty(rawBitbucketEndpoints)) { return new NoopBitbucketServerApiClient(); - } else if (!isNullOrEmpty(bitbucketOauth1Endpoint) && isNullOrEmpty(rawBitbucketEndpoints)) { + } else if (!isBitbucketCloud && isNullOrEmpty(rawBitbucketEndpoints)) { throw new ConfigurationException( "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing." - + " It should contain values from 'che.oauth1.bitbucket.endpoint'"); - } else if (isNullOrEmpty(bitbucketOauth1Endpoint) && !isNullOrEmpty(rawBitbucketEndpoints)) { + + " It should contain values from 'che.oauth.bitbucket.endpoint'"); + } else if (isBitbucketCloud && !isNullOrEmpty(rawBitbucketEndpoints)) { return new HttpBitbucketServerApiClient( - sanitizedEndpoints(rawBitbucketEndpoints).get(0), new NoopOAuthAuthenticator()); + sanitizedEndpoints(rawBitbucketEndpoints).get(0), + new NoopOAuthAuthenticator(), + oAuthAPI, + apiEndpoint); } else { - bitbucketOauth1Endpoint = StringUtils.trimEnd(bitbucketOauth1Endpoint, '/'); - if (!sanitizedEndpoints(rawBitbucketEndpoints).contains(bitbucketOauth1Endpoint)) { + bitbucketOauthEndpoint = StringUtils.trimEnd(bitbucketOauthEndpoint, '/'); + if (!sanitizedEndpoints(rawBitbucketEndpoints).contains(bitbucketOauthEndpoint)) { throw new ConfigurationException( "`che.integration.bitbucket.server_endpoints` must contain `" - + bitbucketOauth1Endpoint + + bitbucketOauthEndpoint + "` value"); } else { - Optional authenticator = - authenticators.stream() - .filter( - a -> - a.getOAuthProvider() - .equals(BitbucketServerOAuthAuthenticator.AUTHENTICATOR_NAME)) - .filter(a -> BitbucketServerOAuthAuthenticator.class.isAssignableFrom(a.getClass())) - .findFirst(); + Optional authenticator = authenticators.stream().findFirst(); if (authenticator.isEmpty()) { throw new ConfigurationException( - "'che.oauth1.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly"); + "'che.oauth.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly"); } - return new HttpBitbucketServerApiClient(bitbucketOauth1Endpoint, authenticator.get()); + return new HttpBitbucketServerApiClient( + bitbucketOauthEndpoint, authenticator.get(), oAuthAPI, apiEndpoint); } } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java index 0f39fda0fe..3434b59539 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerModule.java @@ -16,7 +16,6 @@ import com.google.inject.multibindings.Multibinder; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.scm.GitUserDataFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; -import org.eclipse.che.security.oauth1.BitbucketServerApiProvider; public class BitbucketServerModule extends AbstractModule { @Override 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 c31ce0158d..157387e30e 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 @@ -25,7 +25,6 @@ import javax.inject.Named; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; -import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; @@ -34,6 +33,7 @@ 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.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,12 +53,16 @@ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccess ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"); private final BitbucketServerApiClient bitbucketServerApiClient; private final URL apiEndpoint; + private final OAuthAPI oAuthAPI; @Inject public BitbucketServerPersonalAccessTokenFetcher( - BitbucketServerApiClient bitbucketServerApiClient, @Named("che.api") URL apiEndpoint) { + BitbucketServerApiClient bitbucketServerApiClient, + @Named("che.api") URL apiEndpoint, + OAuthAPI oAuthAPI) { this.bitbucketServerApiClient = bitbucketServerApiClient; this.apiEndpoint = apiEndpoint; + this.oAuthAPI = oAuthAPI; } @Override @@ -109,7 +113,10 @@ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccess // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = new HttpBitbucketServerApiClient( - personalAccessToken.getScmProviderUrl(), new NoopOAuthAuthenticator()); + personalAccessToken.getScmProviderUrl(), + new NoopOAuthAuthenticator(), + oAuthAPI, + apiEndpoint.toString()); try { apiClient.getUser(personalAccessToken.getScmUserName(), personalAccessToken.getToken()); return Optional.of(Boolean.TRUE); 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 885a51f744..782e91776e 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 @@ -24,7 +24,6 @@ import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; @@ -35,6 +34,7 @@ import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.StringUtils; +import org.eclipse.che.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; /** @@ -46,6 +46,7 @@ import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; public class BitbucketServerURLParser { private final DevfileFilenamesProvider devfileFilenamesProvider; + private final OAuthAPI oAuthAPI; private final PersonalAccessTokenManager personalAccessTokenManager; private static final List bitbucketUrlPatternTemplates = List.of( @@ -60,8 +61,10 @@ public class BitbucketServerURLParser { public BitbucketServerURLParser( @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, DevfileFilenamesProvider devfileFilenamesProvider, + OAuthAPI oAuthAPI, PersonalAccessTokenManager personalAccessTokenManager) { this.devfileFilenamesProvider = devfileFilenamesProvider; + this.oAuthAPI = oAuthAPI; this.personalAccessTokenManager = personalAccessTokenManager; if (bitbucketEndpoints != null) { for (String bitbucketEndpoint : Splitter.on(",").split(bitbucketEndpoints)) { @@ -106,7 +109,10 @@ public class BitbucketServerURLParser { try { HttpBitbucketServerApiClient bitbucketServerApiClient = new HttpBitbucketServerApiClient( - getServerUrl(repositoryUrl), new BitbucketServerOAuthAuthenticator("", "", "", "")); + getServerUrl(repositoryUrl), + new BitbucketServerOAuthAuthenticator("", "", "", ""), + oAuthAPI, + ""); // If the token request catches the unauthorised error, it means that the provided url // belongs to Bitbucket. bitbucketServerApiClient.getPersonalAccessToken("", 0L); diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/HttpBitbucketServerApiClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java similarity index 87% rename from wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/HttpBitbucketServerApiClient.java rename to wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java index e4071e96b9..fd4b64e34f 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/HttpBitbucketServerApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java @@ -9,8 +9,9 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.che.api.factory.server.bitbucket.server; +package org.eclipse.che.api.factory.server.bitbucket; +import static com.google.common.base.Strings.isNullOrEmpty; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; @@ -21,7 +22,6 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.base.Charsets; -import com.google.common.base.Strings; import com.google.common.io.CharStreams; import com.google.common.net.HttpHeaders; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -42,6 +42,17 @@ import java.util.Set; import java.util.concurrent.Executors; import java.util.function.Function; import java.util.stream.Collectors; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; +import org.eclipse.che.api.factory.server.bitbucket.server.Page; 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; @@ -50,6 +61,8 @@ import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticationException; import org.eclipse.che.security.oauth1.OAuthAuthenticator; import org.slf4j.Logger; @@ -67,11 +80,16 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); private final URI serverUri; private final OAuthAuthenticator authenticator; + private final OAuthAPI oAuthAPI; + private final String apiEndpoint; private final HttpClient httpClient; - public HttpBitbucketServerApiClient(String serverUrl, OAuthAuthenticator authenticator) { + public HttpBitbucketServerApiClient( + String serverUrl, OAuthAuthenticator authenticator, OAuthAPI oAuthAPI, String apiEndpoint) { this.serverUri = URI.create(serverUrl.endsWith("/") ? serverUrl : serverUrl + "/"); this.authenticator = authenticator; + this.oAuthAPI = oAuthAPI; + this.apiEndpoint = apiEndpoint; this.httpClient = HttpClient.newBuilder() .executor( @@ -219,6 +237,8 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { public BitbucketPersonalAccessToken createPersonalAccessTokens( String userSlug, String tokenName, Set permissions) throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + BitbucketPersonalAccessToken token = + new BitbucketPersonalAccessToken(tokenName, permissions, 90); URI uri = serverUri.resolve("./rest/access-tokens/1.0/users/" + userSlug); try { @@ -228,7 +248,7 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { HttpRequest.BodyPublishers.ofString( OM.writeValueAsString( // set maximum allowed expiryDays to 90 - new BitbucketPersonalAccessToken(tokenName, permissions, 90)))) + token))) .headers( HttpHeaders.AUTHORIZATION, computeAuthorizationHeader("PUT", uri.toString()), @@ -343,7 +363,7 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { throws ScmUnauthorizedException, ScmBadRequestException, ScmCommunicationException, ScmItemNotFoundException { String suffix = api + "?start=" + start + "&limit=" + limit; - if (!Strings.isNullOrEmpty(filter)) { + if (!isNullOrEmpty(filter)) { suffix += "&filter=" + filter; } @@ -399,8 +419,30 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { } } + private @Nullable String getToken() throws ScmUnauthorizedException { + try { + OAuthToken token = oAuthAPI.getToken("bitbucket"); + return token.getToken(); + } catch (NotFoundException + | ServerException + | ForbiddenException + | BadRequestException + | ConflictException e) { + LOG.error(e.getMessage()); + return null; + } catch (UnauthorizedException e) { + throw buildScmUnauthorizedException(); + } + } + private String computeAuthorizationHeader(String requestMethod, String requestUrl) - throws ScmCommunicationException { + throws ScmUnauthorizedException, ScmCommunicationException { + if (authenticator instanceof NoopOAuthAuthenticator) { + String token = getToken(); + if (!isNullOrEmpty(token)) { + return "Bearer " + token; + } + } try { Subject subject = EnvironmentContext.getCurrent().getSubject(); return authenticator.computeAuthorizationHeader( @@ -413,11 +455,11 @@ public class HttpBitbucketServerApiClient implements BitbucketServerApiClient { private ScmUnauthorizedException buildScmUnauthorizedException() { return new ScmUnauthorizedException( EnvironmentContext.getCurrent().getSubject().getUserName() - + " is not authorized in " - + authenticator.getOAuthProvider() - + " OAuth1 provider", - authenticator.getOAuthProvider(), - "1.0", - authenticator.getLocalAuthenticateUrl()); + + " is not authorized in bitbucket OAuth provider", + "bitbucket", + authenticator instanceof NoopOAuthAuthenticator ? "2.0" : "1.0", + authenticator instanceof NoopOAuthAuthenticator + ? apiEndpoint + "/oauth/authenticate?oauth_provider=bitbucket&scope=ADMIN_WRITE" + : authenticator.getLocalAuthenticateUrl()); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerApiClientProviderTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiClientProviderTest.java similarity index 78% rename from wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerApiClientProviderTest.java rename to wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiClientProviderTest.java index 09760cb3b5..2f67a25d99 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerApiClientProviderTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiClientProviderTest.java @@ -9,7 +9,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.che.security.oauth1; +package org.eclipse.che.api.factory.server.bitbucket; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -19,16 +19,20 @@ import java.io.IOException; import java.util.Collections; import java.util.Set; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; -import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.inject.ConfigurationException; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; +import org.eclipse.che.security.oauth1.OAuthAuthenticator; +import org.mockito.Mock; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class BitbucketServerApiClientProviderTest { BitbucketServerOAuthAuthenticator oAuthAuthenticator; + @Mock OAuthAPI oAuthAPI; @BeforeClass public void setUp() { @@ -44,6 +48,8 @@ public class BitbucketServerApiClientProviderTest { new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", + "https://bitbucket.server.com", + oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); @@ -59,6 +65,8 @@ public class BitbucketServerApiClientProviderTest { new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", + "https://bitbucket.server.com", + oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); @@ -70,13 +78,12 @@ public class BitbucketServerApiClientProviderTest { @Test(dataProvider = "noopConfig") public void shouldProvideNoopOAuthAuthenticatorIfSomeConfigurationIsNotSet( - @Nullable String bitbucketEndpoints, - @Nullable String bitbucketOauth1Endpoint, - Set authenticators) + @Nullable String bitbucketEndpoints, Set authenticators) throws IOException { // given BitbucketServerApiProvider bitbucketServerApiProvider = - new BitbucketServerApiProvider(bitbucketEndpoints, bitbucketOauth1Endpoint, authenticators); + new BitbucketServerApiProvider( + bitbucketEndpoints, "https://bitbucket.org", "", oAuthAPI, authenticators); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then @@ -86,13 +93,12 @@ public class BitbucketServerApiClientProviderTest { @Test(dataProvider = "httpOnlyConfig") public void shouldProvideHttpAuthenticatorIfOauthConfigurationIsNotSet( - @Nullable String bitbucketEndpoints, - @Nullable String bitbucketOauth1Endpoint, - Set authenticators) + @Nullable String bitbucketEndpoints, Set authenticators) throws IOException { // given BitbucketServerApiProvider bitbucketServerApiProvider = - new BitbucketServerApiProvider(bitbucketEndpoints, bitbucketOauth1Endpoint, authenticators); + new BitbucketServerApiProvider( + bitbucketEndpoints, "https://bitbucket.org", "", oAuthAPI, authenticators); // when BitbucketServerApiClient actual = bitbucketServerApiProvider.get(); // then @@ -103,19 +109,23 @@ public class BitbucketServerApiClientProviderTest { @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = - "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing. It should contain values from 'che.oauth1.bitbucket.endpoint'") + "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing. It should contain values from 'che.oauth.bitbucket.endpoint'") public void shouldFailToBuildIfEndpointsAreMisconfigured() { // given // when BitbucketServerApiProvider bitbucketServerApiProvider = new BitbucketServerApiProvider( - "", "https://bitbucket.server.com", ImmutableSet.of(oAuthAuthenticator)); + "", + "https://bitbucket.server.com", + "https://bitbucket.server.com", + oAuthAPI, + ImmutableSet.of(oAuthAuthenticator)); } @Test( expectedExceptions = ConfigurationException.class, expectedExceptionsMessageRegExp = - "'che.oauth1.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly") + "'che.oauth.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly") public void shouldFailToBuildIfEndpointsAreMisconfigured2() { // given // when @@ -123,6 +133,8 @@ public class BitbucketServerApiClientProviderTest { new BitbucketServerApiProvider( "https://bitbucket.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", + "https://bitbucket.server.com", + oAuthAPI, Collections.emptySet()); } @@ -137,25 +149,26 @@ public class BitbucketServerApiClientProviderTest { new BitbucketServerApiProvider( "https://bitbucket3.server.com, https://bitbucket2.server.com", "https://bitbucket.server.com", + "https://bitbucket.server.com", + oAuthAPI, ImmutableSet.of(oAuthAuthenticator)); } @DataProvider(name = "noopConfig") public Object[][] noopConfig() { return new Object[][] { - {null, null, null}, - {"", "", null} + {null, null}, + {"", null} }; } @DataProvider(name = "httpOnlyConfig") public Object[][] httpOnlyConfig() { return new Object[][] { - {"https://bitbucket.server.com", null, null}, - {"https://bitbucket.server.com, https://bitbucket2.server.com", null, null}, + {"https://bitbucket.server.com", null}, + {"https://bitbucket.server.com, https://bitbucket2.server.com", null}, { "https://bitbucket.server.com, https://bitbucket2.server.com", - null, ImmutableSet.of(oAuthAuthenticator) } }; diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java index 8c61f87410..bd1160dc10 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java @@ -45,6 +45,7 @@ import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; +import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -54,6 +55,7 @@ import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class BitbucketServerAuthorizingFactoryParametersResolverTest { + @Mock private OAuthAPI oAuthAPI; @Mock private URLFactoryBuilder urlFactoryBuilder; @Mock private URLFetcher urlFetcher; @@ -72,6 +74,7 @@ public class BitbucketServerAuthorizingFactoryParametersResolverTest { new BitbucketServerURLParser( "http://bitbucket.2mcl.com", devfileFilenamesProvider, + oAuthAPI, mock(PersonalAccessTokenManager.class)); assertNotNull(this.bitbucketURLParser); bitbucketServerFactoryParametersResolver = 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 ca4a328cee..058f40be47 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 @@ -13,6 +13,7 @@ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; @@ -26,6 +27,13 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Optional; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; @@ -37,6 +45,7 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; +import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -51,6 +60,7 @@ public class BitbucketServerPersonalAccessTokenFetcherTest { Subject subject; @Mock BitbucketServerApiClient bitbucketServerApiClient; @Mock PersonalAccessToken personalAccessToken; + @Mock OAuthAPI oAuthAPI; BitbucketUser bitbucketUser; BitbucketServerPersonalAccessTokenFetcher fetcher; BitbucketPersonalAccessToken bitbucketPersonalAccessToken; @@ -93,7 +103,9 @@ public class BitbucketServerPersonalAccessTokenFetcherTest { "3456\\<0>945//i0923i4jasoidfj934ui50", bitbucketUser, ImmutableSet.of("PROJECT_READ", "REPO_READ")); - fetcher = new BitbucketServerPersonalAccessTokenFetcher(bitbucketServerApiClient, apiEndpoint); + fetcher = + new BitbucketServerPersonalAccessTokenFetcher( + bitbucketServerApiClient, apiEndpoint, oAuthAPI); EnvironmentContext context = new EnvironmentContext(); context.setSubject(subject); EnvironmentContext.setCurrent(context); @@ -190,8 +202,11 @@ public class BitbucketServerPersonalAccessTokenFetcherTest { @Test public void shouldSkipToValidateTokensWithUnknownUrls() - throws ScmUnauthorizedException, ScmCommunicationException { + throws ScmUnauthorizedException, ScmCommunicationException, ForbiddenException, + ServerException, ConflictException, UnauthorizedException, NotFoundException, + BadRequestException { // given + when(oAuthAPI.getToken(eq("bitbucket"))).thenReturn(mock(OAuthToken.class)); when(personalAccessToken.getScmProviderUrl()).thenReturn(someNotBitbucketURL); when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(false); // when diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolverTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolverTest.java index 312317b88c..593c89f79e 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolverTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerScmFileResolverTest.java @@ -13,7 +13,6 @@ package org.eclipse.che.api.factory.server.bitbucket; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.*; @@ -23,6 +22,7 @@ import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -35,6 +35,7 @@ public class BitbucketServerScmFileResolverTest { public static final String SCM_URL = "https://foo.bar"; BitbucketServerURLParser bitbucketURLParser; + @Mock private OAuthAPI oAuthAPI; @Mock private URLFetcher urlFetcher; @Mock private DevfileFilenamesProvider devfileFilenamesProvider; @@ -46,7 +47,7 @@ public class BitbucketServerScmFileResolverTest { protected void init() { bitbucketURLParser = new BitbucketServerURLParser( - SCM_URL, devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); + SCM_URL, devfileFilenamesProvider, oAuthAPI, personalAccessTokenManager); assertNotNull(this.bitbucketURLParser); serverScmFileResolver = new BitbucketServerScmFileResolver( diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParserTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParserTest.java index 5a70069e31..1bfdb8f1b2 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParserTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParserTest.java @@ -26,6 +26,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; +import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeClass; @@ -38,6 +39,7 @@ import org.testng.annotations.Test; public class BitbucketServerURLParserTest { @Mock private DevfileFilenamesProvider devfileFilenamesProvider; + @Mock private OAuthAPI oAuthAPI; /** Instance of component that will be tested. */ private BitbucketServerURLParser bitbucketURLParser; @@ -59,6 +61,7 @@ public class BitbucketServerURLParserTest { new BitbucketServerURLParser( "https://bitbucket.2mcl.com,https://bbkt.com", devfileFilenamesProvider, + oAuthAPI, mock(PersonalAccessTokenManager.class)); } @@ -91,7 +94,7 @@ public class BitbucketServerURLParserTest { // given bitbucketURLParser = new BitbucketServerURLParser( - null, devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); + null, devfileFilenamesProvider, oAuthAPI, mock(PersonalAccessTokenManager.class)); String url = wireMockServer.url("/users/user/repos/repo"); stubFor( get(urlEqualTo("/rest/access-tokens/1.0/users/0")).willReturn(aResponse().withStatus(401))); 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 39238a8ade..cc19888e62 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 @@ -22,6 +22,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.unauthorized; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; @@ -34,21 +38,32 @@ import com.google.common.net.HttpHeaders; import jakarta.ws.rs.core.MediaType; import java.util.List; import java.util.stream.Collectors; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient; import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; -import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient; 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.security.oauth.OAuthAPI; import org.eclipse.che.security.oauth1.BitbucketServerOAuthAuthenticator; import org.eclipse.che.security.oauth1.NoopOAuthAuthenticator; import org.eclipse.che.security.oauth1.OAuthAuthenticationException; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; import org.testng.annotations.Test; +@Listeners(MockitoTestNGListener.class) public class HttpBitbucketServerApiClientTest { private final String AUTHORIZATION_TOKEN = "OAuth oauth_consumer_key=\"key123321\", oauth_nonce=\"6c0eace252f8dcda\"," @@ -59,8 +74,13 @@ public class HttpBitbucketServerApiClientTest { WireMock wireMock; BitbucketServerApiClient bitbucketServer; + @Mock OAuthAPI oAuthAPI; + String apiEndpoint; + @BeforeMethod void start() { + oAuthAPI = mock(OAuthAPI.class); + apiEndpoint = "apiEndpoint"; wireMockServer = new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); wireMockServer.start(); @@ -76,7 +96,9 @@ public class HttpBitbucketServerApiClientTest { throws OAuthAuthenticationException { return AUTHORIZATION_TOKEN; } - }); + }, + oAuthAPI, + apiEndpoint); } @AfterMethod @@ -276,12 +298,44 @@ public class HttpBitbucketServerApiClientTest { expectedExceptionsMessageRegExp = "The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.") public void shouldThrowScmCommunicationExceptionInNoOauthAuthenticator() - throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { + throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException, + ForbiddenException, ServerException, ConflictException, UnauthorizedException, + NotFoundException, BadRequestException { + // given + when(oAuthAPI.getToken(eq("bitbucket"))).thenReturn(mock(OAuthToken.class)); HttpBitbucketServerApiClient localServer = - new HttpBitbucketServerApiClient(wireMockServer.url("/"), new NoopOAuthAuthenticator()); + new HttpBitbucketServerApiClient( + wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); // when localServer.getPersonalAccessToken("ksmster", 5L); } + + @Test + public void shouldGetOauth2Token() + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException, + ForbiddenException, ServerException, ConflictException, UnauthorizedException, + NotFoundException, BadRequestException { + // given + OAuthToken token = mock(OAuthToken.class); + when(token.getToken()).thenReturn("token"); + when(oAuthAPI.getToken(eq("bitbucket"))).thenReturn(token); + bitbucketServer = + new HttpBitbucketServerApiClient( + wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); + stubFor( + get(urlEqualTo("/rest/api/1.0/users/user")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/ksmster/response.json"))); + + // when + bitbucketServer.getUser("user", null); + + // then + verify(oAuthAPI).getToken(eq("bitbucket")); + } }