diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index 19c359d98c9..0abc7e4d82d 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -111,6 +111,10 @@ org.eclipse.che.core che-core-api-auth + + org.eclipse.che.core + che-core-api-auth-bitbucket + org.eclipse.che.core che-core-api-auth-openshift 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 d18a0e2026a..5358c04aad1 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 @@ -165,6 +165,7 @@ protected void configure() { bind(org.eclipse.che.api.user.server.ProfileService.class); bind(org.eclipse.che.api.user.server.PreferencesService.class); bind(org.eclipse.che.security.oauth.OAuthAuthenticationService.class); + bind(org.eclipse.che.security.oauth1.OAuthAuthenticationService.class); install(new DevfileModule()); @@ -256,6 +257,7 @@ protected void configure() { install(new FactoryModuleBuilder().build(JwtProxyConfigBuilderFactory.class)); install(new FactoryModuleBuilder().build(PassThroughProxyProvisionerFactory.class)); installDefaultSecureServerExposer(infrastructure); + install(new org.eclipse.che.security.oauth1.BitbucketModule()); if (Boolean.valueOf(System.getenv("CHE_MULTIUSER"))) { configureMultiUserMode(persistenceProperties, infrastructure); 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 e3e1153a513..dd453eeae8d 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 @@ -184,6 +184,11 @@ che.oauth.openshift.clientsecret=NULL che.oauth.openshift.oauth_endpoint= NULL che.oauth.openshift.verify_token_url= NULL +che.oauth1.bitbucket.consumerkeypath=NULL +che.oauth1.bitbucket.privatekeypath=NULL +che.oauth1.bitbucket.endpoint=NULL + + ### Internal # Che extensions can be scheduled executions on a time basis. diff --git a/infrastructures/infrastructure-factory/pom.xml b/infrastructures/infrastructure-factory/pom.xml index 7d0d5c353ab..e122683f5af 100644 --- a/infrastructures/infrastructure-factory/pom.xml +++ b/infrastructures/infrastructure-factory/pom.xml @@ -64,13 +64,12 @@ infrastructure-kubernetes - ch.qos.logback - logback-classic - test + org.slf4j + slf4j-api - com.github.tomakehurst - wiremock-jre8-standalone + ch.qos.logback + logback-classic test diff --git a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java index 95ae072ebe0..1816eca7ea0 100644 --- a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java +++ b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java @@ -15,10 +15,12 @@ import com.google.common.collect.ImmutableMap; import io.fabric8.kubernetes.api.model.LabelSelector; import io.fabric8.kubernetes.api.model.LabelSelectorBuilder; +import io.fabric8.kubernetes.api.model.Namespace; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -41,10 +43,16 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesClientFactory; import org.eclipse.che.workspace.infrastructure.kubernetes.api.shared.KubernetesNamespaceMeta; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.KubernetesNamespaceFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Manages personal access token secrets used for private repositories authentication. */ @Singleton public class KubernetesPersonalAccessTokenManager implements PersonalAccessTokenManager { + + private static final Logger LOG = + LoggerFactory.getLogger(KubernetesPersonalAccessTokenManager.class); + public static final Map SECRET_LABELS = ImmutableMap.of( "app.kubernetes.io/part-of", "che.eclipse.org", @@ -83,6 +91,7 @@ void save(PersonalAccessToken personalAccessToken) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { try { String namespace = getFirstNamespace(); + LOG.info(" save in namespace {}", namespace); ObjectMeta meta = new ObjectMetaBuilder() .withName(NameGenerator.generate(NAME_PATTERN, 5)) @@ -134,6 +143,9 @@ public Optional get(Subject cheUser, String scmServerUrl) throws ScmConfigurationPersistenceException { try { + String namespace = getFirstNamespace(); + LOG.info(" save in namespace {}", namespace); + for (KubernetesNamespaceMeta namespaceMeta : namespaceFactory.list()) { List secrets = namespaceFactory @@ -158,6 +170,8 @@ public Optional get(Subject cheUser, String scmServerUrl) } } catch (InfrastructureException e) { throw new ScmConfigurationPersistenceException(e.getMessage(), e); + } catch (UnsatisfiedScmPreconditionException e) { + return Optional.empty(); } return Optional.empty(); } @@ -178,4 +192,26 @@ private String getFirstNamespace() throw new ScmConfigurationPersistenceException(e.getMessage(), e); } } + + private boolean canCreateDeleteSecrets(Namespace namespace) { + LOG.info("testing namespace {}", namespace); + ObjectMeta meta = + new ObjectMetaBuilder().withName(NameGenerator.generate(NAME_PATTERN, "tmp", 5)).build(); + Secret secret = new SecretBuilder().withMetadata(meta).build(); + try { + KubernetesClient kubernetesClient = clientFactory.create(); + Secret result = + kubernetesClient + .secrets() + .inNamespace(namespace.getMetadata().getName()) + .createOrReplace(secret); + kubernetesClient.secrets().delete(result); + LOG.info(" namespace {} is ok", namespace); + return true; + } catch (InfrastructureException e) { + LOG.error(e.getMessage(), e); + } + LOG.info(" namespace {} is not ok", namespace); + return false; + } } diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java index 93ef4cdb647..0606f21d265 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java @@ -25,7 +25,7 @@ public class KeycloakServletModule extends ServletModule { // not contains /docs/ (for swagger) + "(?!.*(/docs/))" // not ends with '/oauth/callback/' or '/keycloak/settings/' or '/system/state' - + "(?!.*(/keycloak/settings/?|/oauth/callback/?|/system/state/?)$)" + + "(?!.*(/keycloak/settings/?|/oauth/callback/?|/oauth/1.0/callback/?|/system/state/?)$)" // all other + ".*"; diff --git a/pom.xml b/pom.xml index a9d155af9c7..d7192c684ee 100644 --- a/pom.xml +++ b/pom.xml @@ -700,6 +700,11 @@ che-core-api-auth ${che.version} + + org.eclipse.che.core + che-core-api-auth-bitbucket + ${che.version} + org.eclipse.che.core che-core-api-auth-github diff --git a/wsmaster/che-core-api-auth-bitbucket/pom.xml b/wsmaster/che-core-api-auth-bitbucket/pom.xml new file mode 100644 index 00000000000..8d1a6ccb1d8 --- /dev/null +++ b/wsmaster/che-core-api-auth-bitbucket/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.25.0-SNAPSHOT + + che-core-api-auth-bitbucket + jar + Che Core :: API :: Authentication Bitbucket + + + com.google.guava + guava + + + com.google.inject + guice + + + javax.inject + javax.inject + + + org.eclipse.che.core + che-core-api-auth + + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-inject + + + org.slf4j + slf4j-api + + + diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketModule.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketModule.java new file mode 100644 index 00000000000..e702863582c --- /dev/null +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketModule.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.inject.DynaModule; + +/** + * Setup BitbucketServerOAuthAuthenticator in guice container. + * + * @author Sergii Kabashniuk + */ +@DynaModule +public class BitbucketModule extends AbstractModule { + @Override + protected void configure() { + Multibinder oAuthAuthenticators = + Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); + oAuthAuthenticators.addBinding().toProvider(BitbucketServerOAuthAuthenticatorProvider.class); + } +} diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticator.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticator.java new file mode 100644 index 00000000000..1860f005f8e --- /dev/null +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticator.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; + +/** + * OAuth1 authentication for Bitbucket Server account. + * + * @author Igor Vinokur + */ +@Singleton +public class BitbucketServerOAuthAuthenticator extends OAuthAuthenticator { + + @Inject + public BitbucketServerOAuthAuthenticator( + @Named("che.oauth1.bitbucket.consumerkey") String consumerKey, + @Named("che.oauth1.bitbucket.privatekey") String privateKey, + @Named("che.oauth1.bitbucket.endpoint") String bitbucketEndpoint, + @Named("che.api") String apiEndpoint) { + super( + consumerKey, + bitbucketEndpoint + "/plugins/servlet/oauth/request-token", + bitbucketEndpoint + "/plugins/servlet/oauth/access-token", + bitbucketEndpoint + "/plugins/servlet/oauth/authorize", + apiEndpoint + "/oauth/1.0/callback", + null, + privateKey); + } + + @Override + public final String getOAuthProvider() { + return "bitbucket-server"; + } +} diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticatorProvider.java new file mode 100644 index 00000000000..3d384108fbc --- /dev/null +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuthAuthenticatorProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import com.google.inject.name.Named; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; +import org.eclipse.che.commons.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class BitbucketServerOAuthAuthenticatorProvider implements Provider { + private final OAuthAuthenticator authenticator; + private static final Logger LOG = + LoggerFactory.getLogger(BitbucketServerOAuthAuthenticatorProvider.class); + + @Inject + 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, + @Named("che.api") String apiEndpoint) + throws IOException { + + authenticator = + getOAuthAuthenticator(consumerKeyPath, privateKeyPath, bitbucketEndpoint, apiEndpoint); + LOG.info("authenticator={}", authenticator); + } + + @Override + public OAuthAuthenticator get() { + return authenticator; + } + + private static OAuthAuthenticator getOAuthAuthenticator( + String consumerKeyPath, String privateKeyPath, String bitbucketEndpoint, String apiEndpoint) + throws IOException { + LOG.info("1"); + if (!isNullOrEmpty(bitbucketEndpoint) && consumerKeyPath != null && privateKeyPath != null) { + String consumerKey = Files.readString(Path.of(consumerKeyPath)); + String privateKey = Files.readString(Path.of(privateKeyPath)); + LOG.info("consumerKey={} privateKey={}", consumerKey, privateKey); + if (!isNullOrEmpty(consumerKey) && !isNullOrEmpty(privateKey)) { + return new BitbucketServerOAuthAuthenticator( + consumerKey, privateKey, bitbucketEndpoint, apiEndpoint); + } + } + + return new NoopOAuthAuthenticator(); + } +} diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/NoopOAuthAuthenticator.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/NoopOAuthAuthenticator.java new file mode 100644 index 00000000000..6c7e20eb6db --- /dev/null +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth1/NoopOAuthAuthenticator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import java.net.URL; + +public class NoopOAuthAuthenticator extends OAuthAuthenticator { + protected NoopOAuthAuthenticator() { + super(null, null, null, null, null, null, null); + } + + @Override + String getOAuthProvider() { + return "Noop"; + } + + @Override + String getAuthenticateUrl(URL requestUrl, String requestMethod, String signatureMethod) + throws OAuthAuthenticationException { + throw new RuntimeException("Invalid usage of NoopOAuthAuthenticator"); + } + + @Override + String callback(URL requestUrl) throws OAuthAuthenticationException { + throw new RuntimeException("Invalid usage of NoopOAuthAuthenticator"); + } + + @Override + String computeAuthorizationHeader(String userId, String requestMethod, String requestUrl) + throws OAuthAuthenticationException { + throw new RuntimeException("Invalid usage of NoopOAuthAuthenticator"); + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml index 7b52e1b3014..3ba0872bc7c 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml +++ b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml @@ -26,6 +26,18 @@ false + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + com.google.guava guava @@ -42,6 +54,18 @@ javax.validation validation-api + + javax.ws.rs + javax.ws.rs-api + + + org.eclipse.che.core + che-core-api-auth + + + org.eclipse.che.core + che-core-api-auth-bitbucket + org.eclipse.che.core che-core-api-core @@ -74,11 +98,20 @@ org.eclipse.che.core che-core-commons-lang + + org.slf4j + slf4j-api + ch.qos.logback logback-classic test + + com.github.tomakehurst + wiremock-jre8-standalone + 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/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 72a5740d1c5..794e4dac36f 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 @@ -13,7 +13,9 @@ import com.google.inject.AbstractModule; import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApi; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; +import org.eclipse.che.security.oauth1.BitbucketServerApiProvider; public class BitbucketServerModule extends AbstractModule { @Override @@ -21,5 +23,6 @@ protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(BitbucketServerPersonalAccessTokenFetcher.class); + bind(BitbucketServerApi.class).toProvider(BitbucketServerApiProvider.class); } } 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 b0759493e2c..112147a2389 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 @@ -11,9 +11,28 @@ */ package org.eclipse.che.api.factory.server.bitbucket; +import static java.lang.String.format; +import static java.lang.String.valueOf; + +import com.google.common.collect.ImmutableSet; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApi; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; 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; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Bitbucket implementation for {@link PersonalAccessTokenFetcher}. Right now returns {@code null} @@ -21,8 +40,59 @@ * class. */ public class BitbucketServerPersonalAccessTokenFetcher implements PersonalAccessTokenFetcher { + + private static final Logger LOG = + LoggerFactory.getLogger(BitbucketServerPersonalAccessTokenFetcher.class); + + private static final String TOKEN_NAME_TEMPLATE = "che-token-<%s>-<%s>"; + private final BitbucketServerApi bitbucketServerApi; + private final URL apiEndpoint; + + @Inject + public BitbucketServerPersonalAccessTokenFetcher( + BitbucketServerApi bitbucketServerApi, @Named("che.api") URL apiEndpoint) { + this.bitbucketServerApi = bitbucketServerApi; + this.apiEndpoint = apiEndpoint; + } + @Override - public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) { - return null; + public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException { + if (!bitbucketServerApi.isConnected(scmServerUrl)) { + LOG.info("not valid url {} for current fetcher ", scmServerUrl); + return null; + } + + String tokenName = format(TOKEN_NAME_TEMPLATE, cheUser.getUserId(), apiEndpoint.getHost()); + try { + BitbucketUser user = bitbucketServerApi.getUser(EnvironmentContext.getCurrent().getSubject()); + LOG.info("Current bitbucket user {} ", user); + // cleanup existed + List existedTokens = + bitbucketServerApi + .getPersonalAccessTokens(user.getSlug()) + .stream() + .filter(p -> p.getName().equals(tokenName)) + .collect(Collectors.toList()); + for (BitbucketPersonalAccessToken existedToken : existedTokens) { + LOG.info("Deleting existed che token {} {}", existedToken.getId(), existedToken.getName()); + bitbucketServerApi.deletePersonalAccessTokens(user.getSlug(), existedToken.getId()); + } + + BitbucketPersonalAccessToken token = + bitbucketServerApi.createPersonalAccessTokens( + user.getSlug(), tokenName, ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); + LOG.info("Token = {} for {}", token.getId(), token.getUser()); + return new PersonalAccessToken( + scmServerUrl, + EnvironmentContext.getCurrent().getSubject().getUserId(), + user.getName(), + valueOf(user.getId()), + token.getName(), + valueOf(token.getId()), + token.getToken()); + } catch (ScmBadRequestException | ScmItemNotFoundException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParser.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParser.java index 460bffb7666..faabaa4d1ae 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParser.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketURLParser.java @@ -25,6 +25,8 @@ import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Parser of String Bitbucket URLs and provide {@link BitbucketUrl} objects. @@ -33,7 +35,7 @@ */ @Singleton public class BitbucketURLParser { - + private static final Logger LOG = LoggerFactory.getLogger(BitbucketURLParser.class); private final DevfileFilenamesProvider devfileFilenamesProvider; private static final String bitbucketUrlPatternTemplate = "^(?%s)/scm/(?[^/]++)/(?[^.]++).git(\\?at=)?(?[\\w\\d-_]*)"; @@ -43,6 +45,7 @@ public class BitbucketURLParser { public BitbucketURLParser( @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, DevfileFilenamesProvider devfileFilenamesProvider) { + LOG.info("bitbucketEndpoints={}", bitbucketEndpoints); this.devfileFilenamesProvider = devfileFilenamesProvider; if (bitbucketEndpoints != null) { for (String bitbucketEndpoint : Splitter.on(",").split(bitbucketEndpoints)) { @@ -54,6 +57,7 @@ public BitbucketURLParser( } public boolean isValid(@NotNull String url) { + LOG.info("isValid={}", url); return !bitbucketUrlPatterns.isEmpty() && bitbucketUrlPatterns.stream().anyMatch(pattern -> pattern.matcher(url).matches()); } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/AuthorizationHeaderProvider.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/AuthorizationHeaderProvider.java new file mode 100644 index 00000000000..2b461f93b0a --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/AuthorizationHeaderProvider.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; + +/** Compute the Authorization header to sign the OAuth 1 request. */ +public interface AuthorizationHeaderProvider { + String computeAuthorizationHeader(final String requestMethod, final String requestUrl) + throws ScmUnauthorizedException; +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketPersonalAccessToken.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketPersonalAccessToken.java new file mode 100644 index 00000000000..43f4fe40bd6 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketPersonalAccessToken.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.Set; + +@JsonInclude(JsonInclude.Include.NON_DEFAULT) +public class BitbucketPersonalAccessToken { + private long id; + private long createdDate; + private long lastAuthenticated; + private String name; + private String token; + private BitbucketUser user; + private Set permissions; + + public BitbucketPersonalAccessToken(String name, Set permissions) { + this.name = name; + this.permissions = permissions; + } + + public BitbucketPersonalAccessToken() {} + + public BitbucketPersonalAccessToken( + long id, + long createdDate, + long lastAuthenticated, + String name, + String token, + BitbucketUser user, + Set permissions) { + this.id = id; + this.createdDate = createdDate; + this.lastAuthenticated = lastAuthenticated; + this.name = name; + this.token = token; + this.user = user; + this.permissions = permissions; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(long createdDate) { + this.createdDate = createdDate; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BitbucketUser getUser() { + return user; + } + + public void setUser(BitbucketUser user) { + this.user = user; + } + + public Set getPermissions() { + return permissions; + } + + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public long getLastAuthenticated() { + return lastAuthenticated; + } + + public void setLastAuthenticated(long lastAuthenticated) { + this.lastAuthenticated = lastAuthenticated; + } + + @Override + public String toString() { + return "BitbucketPersonalAccessToken{" + + "id=" + + id + + ", createdDate=" + + createdDate + + ", lastAuthenticated=" + + lastAuthenticated + + ", name='" + + name + + '\'' + + ", token='" + + token + + '\'' + + ", user=" + + user + + ", permissions=" + + permissions + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApi.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApi.java new file mode 100644 index 00000000000..8e48ddac991 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApi.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import java.util.List; +import java.util.Set; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.subject.Subject; + +public interface BitbucketServerApi { + + boolean isConnected(String bitbucketServerUrl); + /** + * @param cheUser - Che user. + * @return - {@link BitbucketUser} that is linked with given {@link Subject} + * @throws ScmUnauthorizedException - in case if {@link Subject} is not linked to any {@link + * BitbucketUser} + */ + BitbucketUser getUser(Subject cheUser) throws ScmUnauthorizedException, ScmCommunicationException; + + /** + * @param slug + * @return - Retrieve the {@link BitbucketUser} matching the supplied userSlug. + * @throws ScmItemNotFoundException + * @throws ScmUnauthorizedException + * @throws ScmCommunicationException + */ + BitbucketUser getUser(String slug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; + + /** + * @return Retrieve a list of {@link BitbucketUser}. Only authenticated users may call this + * resource. + * @throws ScmBadRequestException + * @throws ScmUnauthorizedException + * @throws ScmCommunicationException + */ + List getUsers() + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException; + + /** + * @return Retrieve a list of {@link BitbucketUser}, optionally run through provided filters. Only + * authenticated users may call this resource. + * @throws ScmBadRequestException + * @throws ScmUnauthorizedException + * @throws ScmCommunicationException + */ + List getUsers(String filter) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException; + + /** + * Modify an access token for the user according to the given request. Any fields not specified + * will not be altered + * + * @param userSlug + * @param tokenId - the token id + * @throws ScmItemNotFoundException + * @throws ScmUnauthorizedException + * @throws ScmCommunicationException + */ + void deletePersonalAccessTokens(String userSlug, Long tokenId) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; + + /** + * Create an access token for the user according to the given request. + * + * @param userSlug + * @param tokenName + * @param permissions + * @return + * @throws ScmBadRequestException + * @throws ScmUnauthorizedException + * @throws ScmCommunicationException + */ + BitbucketPersonalAccessToken createPersonalAccessTokens( + String userSlug, String tokenName, Set permissions) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException; + + /** + * Get all access tokens associated with the given user + * + * @param userSlug + * @return + * @throws ScmItemNotFoundException + * @throws ScmUnauthorizedException + * @throws ScmBadRequestException + * @throws ScmCommunicationException + */ + List getPersonalAccessTokens(String userSlug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException; +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiHttpClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiHttpClient.java new file mode 100644 index 00000000000..1b00770e4a3 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketServerApiHttpClient.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import static java.time.Duration.ofSeconds; + +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 java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.ws.rs.core.MediaType; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BitbucketServerApiHttpClient implements BitbucketServerApi { + + private static final ObjectMapper OM = new ObjectMapper(); + + private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerApiHttpClient.class); + private final URI serverUri; + private final AuthorizationHeaderProvider headerProvider; + + @Inject + public BitbucketServerApiHttpClient( + String serverUrl, AuthorizationHeaderProvider authorizationHeaderProvider) { + this.serverUri = URI.create(serverUrl); + this.headerProvider = authorizationHeaderProvider; + } + + @Override + public boolean isConnected(String bitbucketServerUrl) { + boolean equals = serverUri.equals(URI.create(bitbucketServerUrl)); + LOG.info("{}={}={}", serverUri, bitbucketServerUrl, equals); + return equals; + } + + @Override + public BitbucketUser getUser(Subject cheUser) + throws ScmUnauthorizedException, ScmCommunicationException { + try { + Set usersByName = + getUsers(cheUser.getUserName()) + .stream() + .map(u -> u.getSlug()) + .collect(Collectors.toSet()); + + Optional currentUser = findCurrentUser(usersByName); + if (currentUser.isPresent()) { + return currentUser.get(); + } + Set usersAllExceptByName = + getUsers() + .stream() + .map(u -> u.getSlug()) + .filter(s -> !usersByName.contains(s)) + .collect(Collectors.toSet()); + currentUser = findCurrentUser(usersAllExceptByName); + if (currentUser.isPresent()) { + return currentUser.get(); + } + } catch (ScmBadRequestException | ScmItemNotFoundException scmBadRequestException) { + throw new ScmCommunicationException( + scmBadRequestException.getMessage(), scmBadRequestException); + } + throw new ScmUnauthorizedException( + "Current user not found. That is possible only if user are not authorized against " + + serverUri); + } + + @Override + public BitbucketUser getUser(String slug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + HttpClient httpClient = HttpClient.newHttpClient(); + URI uri = serverUri.resolve("/rest/api/1.0/users/" + slug); + HttpRequest request = + HttpRequest.newBuilder(uri) + .headers( + "Authorization", headerProvider.computeAuthorizationHeader("GET", uri.toString())) + .timeout(ofSeconds(10)) + .build(); + + try { + return executeRequest( + httpClient, + request, + inputStream -> { + try { + return OM.readValue(inputStream, BitbucketUser.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (ScmBadRequestException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + public List getUsers() + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + try { + return doGetItems(BitbucketUser.class, "/rest/api/1.0/users", null); + } catch (ScmItemNotFoundException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + public List getUsers(String filter) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + try { + return doGetItems(BitbucketUser.class, "/rest/api/1.0/users", filter); + } catch (ScmItemNotFoundException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + public void deletePersonalAccessTokens(String userSlug, Long tokenId) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + HttpClient httpClient = HttpClient.newHttpClient(); + URI uri = serverUri.resolve("/rest/access-tokens/1.0/users/" + userSlug + "/" + tokenId); + HttpRequest request = + HttpRequest.newBuilder(uri) + .DELETE() + .headers( + HttpHeaders.AUTHORIZATION, + headerProvider.computeAuthorizationHeader("DELETE", uri.toString()), + HttpHeaders.ACCEPT, + MediaType.APPLICATION_JSON, + HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_JSON) + .timeout(ofSeconds(10)) + .build(); + + try { + executeRequest( + httpClient, + request, + inputStream -> { + try { + return OM.readValue(inputStream, String.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (ScmBadRequestException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + public BitbucketPersonalAccessToken createPersonalAccessTokens( + String userSlug, String tokenName, Set permissions) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + HttpClient httpClient = HttpClient.newHttpClient(); + URI uri = serverUri.resolve("/rest/access-tokens/1.0/users/" + userSlug); + HttpRequest request = + HttpRequest.newBuilder(uri) + .PUT( + JsonBodyPublishers.fromObject( + new BitbucketPersonalAccessToken(tokenName, permissions))) + .headers( + HttpHeaders.AUTHORIZATION, + headerProvider.computeAuthorizationHeader("PUT", uri.toString()), + HttpHeaders.ACCEPT, + MediaType.APPLICATION_JSON, + HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_JSON) + .timeout(ofSeconds(10)) + .build(); + + try { + return executeRequest( + httpClient, + request, + inputStream -> { + try { + return OM.readValue(inputStream, BitbucketPersonalAccessToken.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } catch (ScmItemNotFoundException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + @Override + public List getPersonalAccessTokens(String userSlug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + try { + return doGetItems( + BitbucketPersonalAccessToken.class, "/rest/access-tokens/1.0/users/" + userSlug, null); + } catch (ScmBadRequestException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + private Optional findCurrentUser(Set userSlugs) + throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException { + + LOG.info("Testing {} items {}", userSlugs.size(), userSlugs); + for (String userSlug : userSlugs) { + LOG.info("findCurrentUser={}", userSlug); + BitbucketUser user = getUser(userSlug); + try { + getPersonalAccessTokens(userSlug); + return Optional.of(user); + } catch (ScmItemNotFoundException | ScmUnauthorizedException e) { + // ok + } + } + return Optional.empty(); + } + + private List doGetItems(Class tClass, String api, String filter) + throws ScmUnauthorizedException, ScmCommunicationException, ScmBadRequestException, + ScmItemNotFoundException { + List result = new ArrayList<>(); + Page currentPage = doGetPage(tClass, api, 0, 25, filter); + result.addAll(currentPage.getValues()); + while (!currentPage.isLastPage()) { + currentPage = doGetPage(tClass, api, currentPage.getNextPageStart(), 25, filter); + result.addAll(currentPage.getValues()); + } + return result; + } + + private Page doGetPage(Class tClass, String api, int start, int limit, String filter) + throws ScmUnauthorizedException, ScmBadRequestException, ScmCommunicationException, + ScmItemNotFoundException { + HttpClient httpClient = HttpClient.newHttpClient(); + String suffix = api + "?start=" + start + "&limit=" + limit; + if (!Strings.isNullOrEmpty(filter)) { + suffix += "&filter=" + filter; + } + + URI uri = serverUri.resolve(suffix); + HttpRequest request = + HttpRequest.newBuilder(uri) + .headers( + "Authorization", headerProvider.computeAuthorizationHeader("GET", uri.toString())) + .timeout(ofSeconds(10)) + .build(); + final JavaType typeReference = + TypeFactory.defaultInstance().constructParametricType(Page.class, tClass); + return executeRequest( + httpClient, + request, + inputStream -> { + try { + return OM.readValue(inputStream, typeReference); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + private T executeRequest( + HttpClient httpClient, HttpRequest request, Function function) + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { + try { + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + LOG.info("executeRequest={} response {}", request, response.statusCode()); + if (response.statusCode() == 200) { + return function.apply(response.body()); + } else if (response.statusCode() == 204) { + return null; + } else { + String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8)); + if (response.statusCode() == 400) { + throw new ScmBadRequestException(body); + } else if (response.statusCode() == 401) { + throw new ScmUnauthorizedException(body); + } else if (response.statusCode() == 404) { + throw new ScmItemNotFoundException(body); + } else { + throw new ScmCommunicationException( + "Unexpected status code " + response.statusCode() + " " + response.toString()); + } + } + + } catch (IOException | InterruptedException | UncheckedIOException e) { + throw new ScmCommunicationException(e.getMessage(), e); + } + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketUser.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketUser.java new file mode 100644 index 00000000000..cc9566418a8 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/BitbucketUser.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.Objects; + +@JsonIgnoreProperties(value = "links") +public class BitbucketUser { + + private String displayName; + private String name; + private long id; + private String type; + private boolean isActive; + private String slug; + private String emailAddress; + + public BitbucketUser( + String displayName, + String name, + long id, + String type, + boolean isActive, + String slug, + String emailAddress) { + this.displayName = displayName; + this.name = name; + this.id = id; + this.type = type; + this.isActive = isActive; + this.slug = slug; + this.emailAddress = emailAddress; + } + + public BitbucketUser() {} + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BitbucketUser that = (BitbucketUser) o; + return id == that.id + && isActive == that.isActive + && Objects.equals(displayName, that.displayName) + && Objects.equals(name, that.name) + && Objects.equals(type, that.type) + && Objects.equals(slug, that.slug) + && Objects.equals(emailAddress, that.emailAddress); + } + + @Override + public int hashCode() { + return Objects.hash(displayName, name, id, type, isActive, slug, emailAddress); + } + + @Override + public String toString() { + return "BitbucketUser{" + + "displayName='" + + displayName + + '\'' + + ", name='" + + name + + '\'' + + ", id=" + + id + + ", type='" + + type + + '\'' + + ", isActive=" + + isActive + + ", slug='" + + slug + + '\'' + + ", emailAddress='" + + emailAddress + + '\'' + + '}'; + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/JsonBodyPublishers.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/JsonBodyPublishers.java new file mode 100644 index 00000000000..76454333c6d --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/JsonBodyPublishers.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.UncheckedIOException; +import java.net.http.HttpRequest; +import java.util.function.Supplier; + +/** + * {@link Supplier} because https://bugs.openjdk.java.net/browse/JDK-8217264 see + * https://stackoverflow.com/questions/53379087/wrapping-bodysubscriberinputstream-in-gzipinputstream-leads-to-hang + */ +public class JsonBodyPublishers { + private static final ObjectMapper OM = new ObjectMapper(); + + private JsonBodyPublishers() {} + + public static HttpRequest.BodyPublisher fromObject(Object object) { + try { + return HttpRequest.BodyPublishers.ofString(OM.writeValueAsString(object)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e.getMessage(), e); + } + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NopBitbucketServerApi.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NopBitbucketServerApi.java new file mode 100644 index 00000000000..9d2ad7fcaf3 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/NopBitbucketServerApi.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import java.util.List; +import java.util.Set; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.subject.Subject; + +public class NopBitbucketServerApi implements BitbucketServerApi { + @Override + public boolean isConnected(String bitbucketServerUrl) { + return false; + } + + @Override + public BitbucketUser getUser(Subject cheUser) + throws ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public BitbucketUser getUser(String slug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public List getUsers() + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public List getUsers(String filter) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public void deletePersonalAccessTokens(String userSlug, Long tokenId) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public BitbucketPersonalAccessToken createPersonalAccessTokens( + String userSlug, String tokenName, Set permissions) + throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } + + @Override + public List getPersonalAccessTokens(String userSlug) + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + throw new RuntimeException("Invalid usage of BitbucketServerApi"); + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/Page.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/Page.java new file mode 100644 index 00000000000..6681acc87bf --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/server/Page.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket.server; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +public class Page { + private int start; + private int size; + private int limit; + + @JsonProperty(value = "isLastPage") + private boolean isLastPage; + + private int nextPageStart; + List values; + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public boolean isLastPage() { + return isLastPage; + } + + public void setLastPage(boolean lastPage) { + isLastPage = lastPage; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public int getNextPageStart() { + return nextPageStart; + } + + public void setNextPageStart(int nextPageStart) { + this.nextPageStart = nextPageStart; + } + + @Override + public String toString() { + return "Page{" + + "start=" + + start + + ", size=" + + size + + ", limit=" + + limit + + ", isLastPage=" + + isLastPage + + ", nextPageStart=" + + nextPageStart + + ", values=" + + values + + '}'; + } +} 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/security/oauth1/BitbucketServerApiProvider.java new file mode 100644 index 00000000000..f7248c1b316 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerApiProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.util.Optional; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApi; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiHttpClient; +import org.eclipse.che.api.factory.server.bitbucket.server.NopBitbucketServerApi; +import org.eclipse.che.commons.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class BitbucketServerApiProvider implements Provider { + private final BitbucketServerApi bitbucketServerApi; + private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerApiProvider.class); + + @Inject + public BitbucketServerApiProvider( + @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints, + @Nullable @Named("che.oauth1.bitbucket.endpoint") String bitbucketEndpoint, + Set authenticators) { + bitbucketServerApi = doGet(bitbucketEndpoints, bitbucketEndpoint, authenticators); + LOG.info("bitbucketServerApi={}", bitbucketServerApi); + } + + @Override + public BitbucketServerApi get() { + return bitbucketServerApi; + } + + private static BitbucketServerApi doGet( + String bitbucketEndpoints, String bitbucketEndpoint, Set authenticators) { + LOG.info("bitbucketEndpoint={}", bitbucketEndpoint); + if (isNullOrEmpty(bitbucketEndpoint)) { + return new NopBitbucketServerApi(); + } else { + LOG.info("bitbucketEndpoint={}", bitbucketEndpoints); + if (isNullOrEmpty(bitbucketEndpoints)) { + throw new RuntimeException( + "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing."); + } else { + LOG.info( + "bitbucketEndpoints.contains(bitbucketEndpoint)={}", + bitbucketEndpoints.contains(bitbucketEndpoint)); + LOG.info("authenticators={}", authenticators); + if (bitbucketEndpoints.contains(bitbucketEndpoint)) { + Optional authenticator = + authenticators + .stream() + .filter(a -> a.getOAuthProvider().equals("bitbucket-server")) + .findFirst(); + LOG.info("authenticator={}", authenticators); + if (authenticator.isEmpty()) { + throw new RuntimeException( + "BitbucketServerOAuthAuthenticator is not configured correctly"); + } + return new BitbucketServerApiHttpClient( + bitbucketEndpoint, + new BitbucketServerOAuth1AuthorizationHeaderProvider( + (BitbucketServerOAuthAuthenticator) authenticator.get())); + } else { + throw new RuntimeException( + "`che.integration.bitbucket.server_endpoints` mast contain `" + + bitbucketEndpoint + + "` value"); + } + } + } + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderProvider.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderProvider.java new file mode 100644 index 00000000000..8ad12d339b0 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth1; + +import com.google.common.base.Strings; +import javax.inject.Inject; +import org.eclipse.che.api.factory.server.bitbucket.server.AuthorizationHeaderProvider; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; + +public class BitbucketServerOAuth1AuthorizationHeaderProvider + implements AuthorizationHeaderProvider { + private final BitbucketServerOAuthAuthenticator authenticator; + + @Inject + public BitbucketServerOAuth1AuthorizationHeaderProvider( + BitbucketServerOAuthAuthenticator authenticator) { + this.authenticator = authenticator; + } + + @Override + public String computeAuthorizationHeader(String requestMethod, String requestUrl) + throws ScmUnauthorizedException { + try { + Subject subject = EnvironmentContext.getCurrent().getSubject(); + String authorizationHeader = + authenticator.computeAuthorizationHeader(subject.getUserId(), requestMethod, requestUrl); + if (Strings.isNullOrEmpty(authorizationHeader)) { + throw new ScmUnauthorizedException( + subject.getUserName() + + " is not authorized in " + + authenticator.getOAuthProvider() + + " OAuth1 provider"); + } + return authorizationHeader; + } catch (OAuthAuthenticationException e) { + throw new ScmUnauthorizedException(e.getMessage(), e); + } + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiHttpClientTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiHttpClientTest.java new file mode 100644 index 00000000000..a1bd9cbdf25 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerApiHttpClientTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.bitbucket; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +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.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.net.HttpHeaders; +import java.util.List; +import java.util.stream.Collectors; +import javax.ws.rs.core.MediaType; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApi; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiHttpClient; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; +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.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class BitbucketServerApiHttpClientTest { + private final String AUTHORIZATION_TOKEN = + "OAuth oauth_consumer_key=\"key123321\", oauth_nonce=\"6c0eace252f8dcda\"," + + " oauth_signature=\"dPCm521TAF56FfGxabBAZDs9YTNeCg%2BiRK49afoJve8Mxk5ILlfkZKH693udqOig5k5ydeVxX%2FTso%2Flxx1pv2bqdbCqj3Nq82do1hJN5eTDLSvbHfGvjFuOGRobHTHwP6oJkaBSafjMUY8i8Vnz6hLfxToPj2ktd6ug4nKc1WGg%3D\", " + + "oauth_signature_method=\"RSA-SHA1\", oauth_timestamp=\"1609250025\", " + + "oauth_token=\"JmpyDe9sgYNn6pYHP6eGLaIU0vxdKLCJ\", oauth_version=\"1.0\""; + WireMockServer wireMockServer; + WireMock wireMock; + BitbucketServerApi bitbucketServer; + + @BeforeMethod + void start() { + int httpPort = getHttpPort(); + wireMockServer = + new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).port(httpPort)); + wireMockServer.start(); + WireMock.configureFor("localhost", httpPort); + wireMock = new WireMock("localhost", httpPort); + bitbucketServer = + new BitbucketServerApiHttpClient( + wireMockServer.url("/"), (requestMethod, requestUrl) -> AUTHORIZATION_TOKEN); + } + + @AfterMethod + void stop() { + wireMockServer.stop(); + } + + int getHttpPort() { + return 3301; + } + + @Test + public void testGetUser() + throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException { + stubFor( + get(urlEqualTo("/rest/api/1.0/users/ksmster")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/ksmster/response.json"))); + + BitbucketUser user = bitbucketServer.getUser("ksmster"); + assertNotNull(user); + } + + @Test + public void testGetUsers() + throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { + stubFor( + get(urlPathEqualTo("/rest/api/1.0/users")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("0")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/response_s0_l25.json"))); + stubFor( + get(urlPathEqualTo("/rest/api/1.0/users")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("3")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/response_s3_l25.json"))); + stubFor( + get(urlPathEqualTo("/rest/api/1.0/users")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("6")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/response_s6_l25.json"))); + stubFor( + get(urlPathEqualTo("/rest/api/1.0/users")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("9")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/response_s9_l25.json"))); + + List page = + bitbucketServer + .getUsers() + .stream() + .map(BitbucketUser::getSlug) + .collect(Collectors.toList()); + assertEquals( + page, + ImmutableList.of( + "admin", + "ksmster", + "skabashn", + "user1", + "user2", + "user3", + "user4", + "user5", + "user6", + "user7")); + } + + @Test + public void testGetUsersFiltered() + throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { + stubFor( + get(urlPathEqualTo("/rest/api/1.0/users")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("0")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/api/1.0/users/filtered/response.json"))); + + List page = + bitbucketServer + .getUsers("ksmster") + .stream() + .map(BitbucketUser::getSlug) + .collect(Collectors.toList()); + assertEquals(page, ImmutableList.of("admin", "ksmster")); + } + + @Test + public void testGetPersonalAccessTokens() + throws ScmCommunicationException, ScmBadRequestException, ScmItemNotFoundException, + ScmUnauthorizedException { + stubFor( + get(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withQueryParam("start", equalTo("0")) + .withQueryParam("limit", equalTo("25")) + .willReturn( + aResponse() + .withHeader("Content-Type", "application/json; charset=utf-8") + .withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/response.json"))); + + List page = + bitbucketServer + .getPersonalAccessTokens("ksmster") + .stream() + .map(BitbucketPersonalAccessToken::getName) + .collect(Collectors.toList()); + assertEquals(page, ImmutableList.of("che", "t2")); + } + + @Test + public void shouldBeAbleToCreatePAT() + throws ScmCommunicationException, ScmBadRequestException, ScmUnauthorizedException { + + // given + stubFor( + put(urlPathEqualTo("/rest/access-tokens/1.0/users/ksmster")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo(AUTHORIZATION_TOKEN)) + .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON)) + .withHeader(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON)) + .withHeader(HttpHeaders.CONTENT_LENGTH, equalTo("63")) + .willReturn( + ok().withBodyFile("bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json"))); + + // when + BitbucketPersonalAccessToken result = + bitbucketServer.createPersonalAccessTokens( + "ksmster", "myToKen", ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); + // then + assertNotNull(result); + assertEquals(result.getToken(), "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs"); + } +} 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 new file mode 100644 index 00000000000..6e79b11bbd6 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2012-2018 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/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +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.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketPersonalAccessToken; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApi; +import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser; +import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +@Listeners(MockitoTestNGListener.class) +public class BitbucketServerPersonalAccessTokenFetcherTest { + String someNotBitbucketURL = "https://notabitbucket.com"; + String someBitbucketURL = "https://some.bitbucketserver.com"; + Subject subject; + @Mock BitbucketServerApi bitbucketServerApi; + BitbucketUser bitbucketUser; + BitbucketServerPersonalAccessTokenFetcher fetcher; + BitbucketPersonalAccessToken bitbucketPersonalAccessToken; + BitbucketPersonalAccessToken bitbucketPersonalAccessToken2; + BitbucketPersonalAccessToken bitbucketPersonalAccessToken3; + + @BeforeMethod + public void setup() throws MalformedURLException { + URL apiEndpoint = new URL("https://che.server.com"); + subject = new SubjectImpl("another_user", "user987", "token111", false); + bitbucketUser = + new BitbucketUser("User", "user", 32423523, "NORMAL", true, "user", "user@users.com"); + bitbucketPersonalAccessToken = + new BitbucketPersonalAccessToken( + 234234, + 234345345, + 23534534, + "che-token--", + "2340590skdf3<0>945i0923i4jasoidfj934ui50", + bitbucketUser, + ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); + bitbucketPersonalAccessToken2 = + new BitbucketPersonalAccessToken( + 3647456, + 234345345, + 23534534, + "che-token--", + "34545<0>945i0923i4jasoidfj934ui50", + bitbucketUser, + ImmutableSet.of("REPO_READ")); + bitbucketPersonalAccessToken3 = + new BitbucketPersonalAccessToken( + 132423, + 234345345, + 23534534, + "che-token--", + "3456\\<0>945//i0923i4jasoidfj934ui50", + bitbucketUser, + ImmutableSet.of("PROJECT_READ", "REPO_READ")); + fetcher = new BitbucketServerPersonalAccessTokenFetcher(bitbucketServerApi, apiEndpoint); + EnvironmentContext context = new EnvironmentContext(); + context.setSubject(subject); + EnvironmentContext.setCurrent(context); + } + + @Test + public void shouldSkipToFetchUnknownUrls() + throws ScmUnauthorizedException, ScmCommunicationException { + // given + when(bitbucketServerApi.isConnected(eq(someNotBitbucketURL))).thenReturn(false); + // when + PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someNotBitbucketURL); + // then + assertNull(result); + } + + @Test( + dataProvider = "expectedExceptions", + expectedExceptions = {ScmUnauthorizedException.class, ScmCommunicationException.class}) + public void shouldRethrowBasicExceptionsOnGetUserStep(Class exception) + throws ScmUnauthorizedException, ScmCommunicationException { + // given + when(bitbucketServerApi.isConnected(eq(someNotBitbucketURL))).thenReturn(true); + doThrow(exception).when(bitbucketServerApi).getUser(eq(subject)); + // when + fetcher.fetchPersonalAccessToken(subject, someNotBitbucketURL); + } + + @Test + public void shouldBeAbleToFetchPersonalAccessToken() + throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, + ScmBadRequestException { + // given + when(bitbucketServerApi.isConnected(eq(someBitbucketURL))).thenReturn(true); + when(bitbucketServerApi.getUser(eq(subject))).thenReturn(bitbucketUser); + when(bitbucketServerApi.getPersonalAccessTokens(eq(bitbucketUser.getSlug()))) + .thenReturn(Collections.emptyList()); + + when(bitbucketServerApi.createPersonalAccessTokens( + eq(bitbucketUser.getSlug()), + eq("che-token--"), + eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")))) + .thenReturn(bitbucketPersonalAccessToken); + // when + PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); + // then + assertNotNull(result); + } + + @Test + public void shouldDeleteExistedCheTokenBeforeCreatingNew() + throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, + ScmBadRequestException { + when(bitbucketServerApi.isConnected(eq(someBitbucketURL))).thenReturn(true); + when(bitbucketServerApi.getUser(eq(subject))).thenReturn(bitbucketUser); + when(bitbucketServerApi.getPersonalAccessTokens(eq(bitbucketUser.getSlug()))) + .thenReturn(ImmutableList.of(bitbucketPersonalAccessToken, bitbucketPersonalAccessToken2)); + when(bitbucketServerApi.createPersonalAccessTokens( + eq(bitbucketUser.getSlug()), + eq("che-token--"), + eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")))) + .thenReturn(bitbucketPersonalAccessToken3); + // when + PersonalAccessToken result = fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); + // then + assertNotNull(result); + verify(bitbucketServerApi) + .deletePersonalAccessTokens( + eq(bitbucketUser.getSlug()), eq(bitbucketPersonalAccessToken.getId())); + verify(bitbucketServerApi) + .deletePersonalAccessTokens( + eq(bitbucketUser.getSlug()), eq(bitbucketPersonalAccessToken2.getId())); + } + + @Test(expectedExceptions = {ScmCommunicationException.class}) + public void shouldRethrowUnExceptionsOnCreatePersonalAccessTokens() + throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException, + ScmBadRequestException { + // given + when(bitbucketServerApi.isConnected(eq(someBitbucketURL))).thenReturn(true); + when(bitbucketServerApi.getUser(eq(subject))).thenReturn(bitbucketUser); + when(bitbucketServerApi.getPersonalAccessTokens(eq(bitbucketUser.getSlug()))) + .thenReturn(Collections.emptyList()); + doThrow(ScmBadRequestException.class) + .when(bitbucketServerApi) + .createPersonalAccessTokens( + eq(bitbucketUser.getSlug()), + eq("che-token--"), + eq(ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"))); + // when + + fetcher.fetchPersonalAccessToken(subject, someBitbucketURL); + } + + @DataProvider + public static Object[][] expectedExceptions() { + return new Object[][] {{ScmUnauthorizedException.class}, {ScmCommunicationException.class}}; + } + + @DataProvider + public static Object[][] unExpectedExceptions() { + return new Object[][] {{ScmBadRequestException.class}, {ScmItemNotFoundException.class}}; + } +} diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json new file mode 100644 index 00000000000..11040668039 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json @@ -0,0 +1,26 @@ +{ + "id": "158910532909", + "createdDate": 1609249808751, + "name": "che5", + "permissions": [ + "PROJECT_WRITE", + "REPO_WRITE" + ], + "user": { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "ksmster", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" + } + ] + } + }, + "token": "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs" +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/response.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/response.json new file mode 100644 index 00000000000..0430bfa0f53 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/response.json @@ -0,0 +1,58 @@ +{ + "size": 2, + "limit": 25, + "isLastPage": true, + "values": [ + { + "id": "898123953680", + "createdDate": 1609227270831, + "name": "che", + "permissions": [ + "PROJECT_WRITE", + "REPO_WRITE" + ], + "user": { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "ksmster", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" + } + ] + } + } + }, + { + "id": "080920112506", + "createdDate": 1609227263410, + "name": "t2", + "permissions": [ + "REPO_ADMIN", + "PROJECT_WRITE" + ], + "user": { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "ksmster", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" + } + ] + } + } + } + ], + "start": 0 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/filtered/response.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/filtered/response.json new file mode 100644 index 00000000000..af2b8fc2ba9 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/filtered/response.json @@ -0,0 +1,40 @@ +{ + "size": 2, + "limit": 25, + "isLastPage": true, + "values": [ + { + "name": "admin", + "emailAddress": "admin@ksmster.com", + "id": 1, + "displayName": "Admin", + "active": true, + "slug": "admin", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/admin" + } + ] + } + }, + { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "ksmster", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c5ac.devtools-c5ac.example.opentlc.com/users/ksmster" + } + ] + } + } + ], + "start": 0 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/ksmster/response.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/ksmster/response.json new file mode 100644 index 00000000000..7dcb40e18df --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/ksmster/response.json @@ -0,0 +1,16 @@ +{ + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "Sergii Kabashniuk", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" + } + ] + } +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response.json new file mode 100644 index 00000000000..d8c9abcaff5 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response.json @@ -0,0 +1,56 @@ +{ + "size": 3, + "limit": 25, + "isLastPage": true, + "values": [ + { + "name": "admin", + "emailAddress": "admin@ksmster.com", + "id": 1, + "displayName": "Admin", + "active": true, + "slug": "admin", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/admin" + } + ] + } + }, + { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "Sergii Kabashniuk", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" + } + ] + } + }, + { + "name": "skabashn", + "emailAddress": "skabashniuk@redhat.com", + "id": 3, + "displayName": "Kabashn", + "active": true, + "slug": "skabashn", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/skabashn" + } + ] + } + } + ], + "start": 0 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s0_l25.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s0_l25.json new file mode 100644 index 00000000000..0ac1dee1ff6 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s0_l25.json @@ -0,0 +1,57 @@ +{ + "size": 3, + "limit": 3, + "isLastPage": false, + "values": [ + { + "name": "admin", + "emailAddress": "admin@ksmster.com", + "id": 1, + "displayName": "Admin", + "active": true, + "slug": "admin", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/admin" + } + ] + } + }, + { + "name": "ksmster", + "emailAddress": "ksmster@gmail.com", + "id": 2, + "displayName": "Sergii Kabashniuk", + "active": true, + "slug": "ksmster", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/ksmster" + } + ] + } + }, + { + "name": "skabashn", + "emailAddress": "skabashniuk@redhat.com", + "id": 3, + "displayName": "Kabashn", + "active": true, + "slug": "skabashn", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/skabashn" + } + ] + } + } + ], + "start": 0, + "nextPageStart": 3 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s3_l25.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s3_l25.json new file mode 100644 index 00000000000..a8dc6f0a708 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s3_l25.json @@ -0,0 +1,57 @@ +{ + "size": 3, + "limit": 3, + "isLastPage": false, + "values": [ + { + "name": "user1", + "emailAddress": "user1@gmail.com", + "id": 52, + "displayName": "User1", + "active": true, + "slug": "user1", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user1" + } + ] + } + }, + { + "name": "user2", + "emailAddress": "user2@gmail.com", + "id": 53, + "displayName": "user2", + "active": true, + "slug": "user2", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user2" + } + ] + } + }, + { + "name": "user3@gmail.com", + "emailAddress": "user3@gmail.com", + "id": 54, + "displayName": "user3", + "active": true, + "slug": "user3", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user3_gmail.com" + } + ] + } + } + ], + "start": 3, + "nextPageStart": 6 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s6_l25.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s6_l25.json new file mode 100644 index 00000000000..eac9465a729 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s6_l25.json @@ -0,0 +1,57 @@ +{ + "size": 3, + "limit": 3, + "isLastPage": false, + "values": [ + { + "name": "user4", + "emailAddress": "user4@gmail.com", + "id": 55, + "displayName": "user4", + "active": true, + "slug": "user4", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user4" + } + ] + } + }, + { + "name": "user5", + "emailAddress": "user5@gmail.com", + "id": 56, + "displayName": "user5", + "active": true, + "slug": "user5", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user5" + } + ] + } + }, + { + "name": "user6", + "emailAddress": "user6@gmail.com", + "id": 57, + "displayName": "user6", + "active": true, + "slug": "user6", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user6" + } + ] + } + } + ], + "start": 6, + "nextPageStart": 9 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s9_l25.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s9_l25.json new file mode 100644 index 00000000000..a5b58aa0ca3 --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/api/1.0/users/response_s9_l25.json @@ -0,0 +1,24 @@ +{ + "size": 1, + "limit": 3, + "isLastPage": true, + "values": [ + { + "name": "user7", + "emailAddress": "user7@gmail.com", + "id": 58, + "displayName": "user7", + "active": true, + "slug": "user7", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "https://bitbucket-bitbucket.apps.cluster-devtools-c9ac.devtools-c9ac.example.opentlc.com/users/user7" + } + ] + } + } + ], + "start": 9 +} \ No newline at end of file diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/logback-test.xml b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..09f9fe3ec7a --- /dev/null +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/logback-test.xml @@ -0,0 +1,26 @@ + + + + + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex + + + + + + + + diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java index 784b9294338..3fa3db166bf 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java @@ -21,7 +21,8 @@ public interface PersonalAccessTokenFetcher { * * @param cheUser * @param scmServerUrl - * @return - personal access token. + * @return - personal access token. Mast return null if scmServerUrl is not applicable for the + * current fetcher. * @throws ScmUnauthorizedException - in case if user are not authorized che server to create new * token. Further user interaction is needed before calling next time this method. * @throws ScmCommunicationException - Some unexpected problem occurred during communication with diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java index 28281b49780..0c11fafbbd5 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java @@ -100,6 +100,8 @@ public Optional createFactoryFromDevfile( ex.getMessage()); continue; } catch (DevfileException e) { + // must never happen as devfile location is always an absolute URL + LOG.error(e.getMessage(), e); LOG.debug("Unexpected devfile exception: {}", e.getMessage()); throw new BadRequestException( format( diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index 402fb791ca8..96c5d7264e0 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -26,6 +26,7 @@ che-core-api-auth-shared che-core-api-auth + che-core-api-auth-bitbucket che-core-api-auth-github che-core-api-auth-openshift che-core-api-workspace-shared