Support Bitbucket-server oAuth2 factory (#440)

Apply Bitbucket Server oAuth-2 configuration for the factory flow.
pull/443/head
Igor Vinokur 2023-02-15 16:43:27 +02:00 committed by GitHub
parent 7bad48cfed
commit cb3565dbf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 303 additions and 195 deletions

View File

@ -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();

View File

@ -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

View File

@ -35,6 +35,10 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
@ -53,7 +57,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-json</artifactId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -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<String> 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> O getJson(String getUserUrl, String accessToken, Class<O> 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) {

View File

@ -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 Provider<OAuthAuthen
@Inject
public BitbucketOAuthAuthenticatorProvider(
@Named("che.oauth.bitbucket.endpoint") String oauthEndpoint,
@Nullable @Named("che.oauth2.bitbucket.clientid_filepath") String bitbucketClientIdPath,
@Nullable @Named("che.oauth2.bitbucket.clientsecret_filepath")
String bitbucketClientSecretPath,
@ -47,7 +47,12 @@ public class BitbucketOAuthAuthenticatorProvider implements Provider<OAuthAuthen
throws IOException {
authenticator =
getOAuthAuthenticator(
bitbucketClientIdPath, bitbucketClientSecretPath, redirectUris, authUri, tokenUri);
oauthEndpoint,
bitbucketClientIdPath,
bitbucketClientSecretPath,
redirectUris,
authUri,
tokenUri);
LOG.debug("{} Bitbucket OAuth Authenticator is used.", authenticator);
}
@ -57,11 +62,12 @@ public class BitbucketOAuthAuthenticatorProvider implements Provider<OAuthAuthen
}
private OAuthAuthenticator getOAuthAuthenticator(
String clientIdPath,
String clientSecretPath,
String[] redirectUris,
String authUri,
String tokenUri)
String oauthEndpoint,
@Nullable String clientIdPath,
@Nullable String clientSecretPath,
@Nullable String[] redirectUris,
@Nullable String authUri,
@Nullable String tokenUri)
throws IOException {
if (!isNullOrEmpty(clientIdPath)
@ -70,23 +76,21 @@ public class BitbucketOAuthAuthenticatorProvider implements Provider<OAuthAuthen
&& !isNullOrEmpty(tokenUri)
&& Objects.nonNull(redirectUris)
&& redirectUris.length != 0) {
String trimmedOauthEndpoint = trimEnd(oauthEndpoint, '/');
boolean isBitbucketCloud = trimmedOauthEndpoint.equals("https://bitbucket.org");
authUri = isBitbucketCloud ? authUri : trimmedOauthEndpoint + "/rest/oauth2/1.0/authorize";
tokenUri = isBitbucketCloud ? tokenUri : trimmedOauthEndpoint + "/rest/oauth2/1.0/token";
final String clientId = Files.readString(Path.of(clientIdPath)).trim();
final String clientSecret = Files.readString(Path.of(clientSecretPath)).trim();
if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) {
return new BitbucketOAuthAuthenticator(
clientId, clientSecret, redirectUris, authUri, tokenUri);
trimmedOauthEndpoint, clientId, clientSecret, redirectUris, authUri, tokenUri);
}
}
return new NoopOAuthAuthenticator();
}
static class NoopOAuthAuthenticator extends OAuthAuthenticator {
@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
throw new OAuthAuthenticationException(
"The fallback noop authenticator cannot be used for Bitbucket authentication. Make sure OAuth is properly configured.");
}
@Override
public String getOAuthProvider() {
return "Noop";

View File

@ -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/
@ -35,7 +35,7 @@ public class BitbucketServerOAuthAuthenticatorProvider implements Provider<OAuth
public BitbucketServerOAuthAuthenticatorProvider(
@Nullable @Named("che.oauth1.bitbucket.consumerkeypath") String consumerKeyPath,
@Nullable @Named("che.oauth1.bitbucket.privatekeypath") String privateKeyPath,
@Nullable @Named("che.oauth1.bitbucket.endpoint") String bitbucketEndpoint,
@Nullable @Named("che.oauth.bitbucket.endpoint") String bitbucketEndpoint,
@Named("che.api") String apiEndpoint)
throws IOException {
authenticator =

View File

@ -35,10 +35,6 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>

View File

@ -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";

View File

@ -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 Provider<OAuthAuthentic
}
static class NoopOAuthAuthenticator extends OAuthAuthenticator {
@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
throw new OAuthAuthenticationException(
"The fallback noop authenticator cannot be used for GitHub authentication. Make sure OAuth is properly configured.");
}
@Override
public String getOAuthProvider() {
return "Noop";

View File

@ -35,10 +35,6 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>

View File

@ -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";

View File

@ -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<OAuthAuthentic
}
static class NoopOAuthAuthenticator extends OAuthAuthenticator {
@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
throw new OAuthAuthenticationException(
"The fallback noop authenticator cannot be used for GitLab authentication. Make sure OAuth is properly configured.");
}
@Override
public String getOAuthProvider() {

View File

@ -23,7 +23,6 @@ import javax.inject.Named;
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;
/**
* OAuth authentication for OpenShift.
@ -58,11 +57,6 @@ public class OpenShiftOAuthAuthenticator extends OAuthAuthenticator {
}
}
@Override
public User getUser(OAuthToken accessToken) throws OAuthAuthenticationException {
throw new OAuthAuthenticationException("not supported");
}
@Override
public final String getOAuthProvider() {
return "openshift";

View File

@ -11,6 +11,7 @@
*/
package org.eclipse.che.security.oauth;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Collections.emptyList;
import static org.eclipse.che.commons.lang.UrlUtils.*;
import static org.eclipse.che.commons.lang.UrlUtils.getParameter;
@ -28,6 +29,7 @@ import java.net.URL;
import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
@ -52,6 +54,7 @@ import org.slf4j.LoggerFactory;
*
* @author Mykhailo Kuznietsov
*/
@Singleton
public class EmbeddedOAuthAPI implements OAuthAPI {
private static final Logger LOG = LoggerFactory.getLogger(EmbeddedOAuthAPI.class);
@ -61,6 +64,7 @@ public class EmbeddedOAuthAPI implements OAuthAPI {
@Inject protected OAuthAuthenticatorProvider providers;
@Inject protected PersonalAccessTokenManager personalAccessTokenManager;
private String redirectAfterLogin;
@Override
public Response authenticate(
@ -70,6 +74,7 @@ public class EmbeddedOAuthAPI implements OAuthAPI {
String redirectAfterLogin,
HttpServletRequest request)
throws NotFoundException, OAuthAuthenticationException {
this.redirectAfterLogin = redirectAfterLogin;
OAuthAuthenticator oauth = getAuthenticator(oauthProvider);
final String authUrl =
oauth.getAuthenticateUrl(getRequestUrl(uriInfo), scopes == null ? emptyList() : scopes);
@ -80,9 +85,12 @@ public class EmbeddedOAuthAPI implements OAuthAPI {
public Response callback(UriInfo uriInfo, List<String> errorValues) throws NotFoundException {
URL requestUrl = getRequestUrl(uriInfo);
Map<String, List<String>> 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");

View File

@ -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.
*

View File

@ -66,6 +66,10 @@
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-bitbucket</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-auth-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
@ -120,6 +124,11 @@
<artifactId>wiremock-jre8-standalone</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-json</artifactId>

View File

@ -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<BitbucketServerApiCl
private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerApiProvider.class);
private final BitbucketServerApiClient bitbucketServerApiClient;
private final String apiEndpoint;
private final OAuthAPI oAuthAPI;
@Inject
public BitbucketServerApiProvider(
@Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints,
@Nullable @Named("che.oauth1.bitbucket.endpoint") String bitbucketOauth1Endpoint,
@Named("che.oauth.bitbucket.endpoint") String bitbucketOauthEndpoint,
@Named("che.api") String apiEndpoint,
OAuthAPI oAuthAPI,
Set<OAuthAuthenticator> 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<BitbucketServerApiCl
return bitbucketServerApiClient;
}
private static BitbucketServerApiClient doGet(
private BitbucketServerApiClient doGet(
String rawBitbucketEndpoints,
String bitbucketOauth1Endpoint,
String bitbucketOauthEndpoint,
Set<OAuthAuthenticator> 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<OAuthAuthenticator> authenticator =
authenticators.stream()
.filter(
a ->
a.getOAuthProvider()
.equals(BitbucketServerOAuthAuthenticator.AUTHENTICATOR_NAME))
.filter(a -> BitbucketServerOAuthAuthenticator.class.isAssignableFrom(a.getClass()))
.findFirst();
Optional<OAuthAuthenticator> 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);
}
}
}

View File

@ -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

View File

@ -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);

View File

@ -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<String> 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);

View File

@ -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<String> 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());
}
}

View File

@ -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<OAuthAuthenticator> authenticators)
@Nullable String bitbucketEndpoints, Set<OAuthAuthenticator> 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<OAuthAuthenticator> authenticators)
@Nullable String bitbucketEndpoints, Set<OAuthAuthenticator> 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)
}
};

View File

@ -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 =

View File

@ -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

View File

@ -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(

View File

@ -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)));

View File

@ -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"));
}
}