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 0000000000..22f09621cd
--- /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,121 @@
+/*
+ * 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;
+import java.util.Objects;
+
+/**
+ * Bitbucket's paging object. Combines collections of items with some metadata.
+ *
+ * See more
+ *
+ *
https://docs.atlassian.com/bitbucket-server/rest/5.6.1/bitbucket-rest.html
+ *
+ * @param
+ */
+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
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Page> page = (Page>) o;
+ return start == page.start
+ && size == page.size
+ && limit == page.limit
+ && isLastPage == page.isLastPage
+ && nextPageStart == page.nextPageStart
+ && Objects.equals(values, page.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(start, size, limit, isLastPage, nextPageStart, 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 0000000000..b589066e18
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerApiProvider.java
@@ -0,0 +1,91 @@
+/*
+ * 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.BitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.inject.ConfigurationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class BitbucketServerApiProvider implements Provider {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BitbucketServerApiProvider.class);
+
+ private final BitbucketServerApiClient bitbucketServerApiClient;
+
+ @Inject
+ public BitbucketServerApiProvider(
+ @Nullable @Named("che.integration.bitbucket.server_endpoints") String bitbucketEndpoints,
+ @Nullable @Named("che.oauth1.bitbucket.endpoint") String bitbucketOauth1Endpoint,
+ Set authenticators) {
+ bitbucketServerApiClient = doGet(bitbucketEndpoints, bitbucketOauth1Endpoint, authenticators);
+ LOG.debug("Bitbucket server api is used {}", bitbucketServerApiClient);
+ }
+
+ @Override
+ public BitbucketServerApiClient get() {
+ return bitbucketServerApiClient;
+ }
+
+ private static BitbucketServerApiClient doGet(
+ String bitbucketEndpoints,
+ String bitbucketOauth1Endpoint,
+ Set authenticators) {
+ if (isNullOrEmpty(bitbucketOauth1Endpoint)) {
+ return new NoopBitbucketServerApiClient();
+ } else {
+ if (isNullOrEmpty(bitbucketEndpoints)) {
+ throw new ConfigurationException(
+ "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing."
+ + " It should contain values from 'che.oauth1.bitbucket.endpoint'");
+ } else {
+ if (bitbucketEndpoints.contains(bitbucketOauth1Endpoint)) {
+ Optional authenticator =
+ authenticators
+ .stream()
+ .filter(
+ a ->
+ a.getOAuthProvider()
+ .equals(BitbucketServerOAuthAuthenticator.AUTHENTICATOR_NAME))
+ .filter(
+ a -> BitbucketServerOAuthAuthenticator.class.isAssignableFrom(a.getClass()))
+ .findFirst();
+ if (authenticator.isEmpty()) {
+ throw new ConfigurationException(
+ "'che.oauth1.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly");
+ }
+ return new HttpBitbucketServerApiClient(
+ bitbucketOauth1Endpoint,
+ new BitbucketServerOAuth1AuthorizationHeaderSupplier(
+ (BitbucketServerOAuthAuthenticator) authenticator.get()));
+ } else {
+ throw new ConfigurationException(
+ "`che.integration.bitbucket.server_endpoints` must contain `"
+ + bitbucketOauth1Endpoint
+ + "` value");
+ }
+ }
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplier.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplier.java
new file mode 100644
index 0000000000..fd6897f0a2
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplier.java
@@ -0,0 +1,55 @@
+/*
+ * 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.AuthorizationHeaderSupplier;
+import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
+import org.eclipse.che.commons.env.EnvironmentContext;
+import org.eclipse.che.commons.subject.Subject;
+
+/**
+ * Implementation of @{@link AuthorizationHeaderSupplier} that is used @{@link
+ * BitbucketServerOAuthAuthenticator} to compute authorization headers.
+ */
+public class BitbucketServerOAuth1AuthorizationHeaderSupplier
+ implements AuthorizationHeaderSupplier {
+ private final BitbucketServerOAuthAuthenticator authenticator;
+
+ @Inject
+ public BitbucketServerOAuth1AuthorizationHeaderSupplier(
+ BitbucketServerOAuthAuthenticator authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ @Override
+ public String computeAuthorizationHeader(String requestMethod, String requestUrl)
+ throws ScmUnauthorizedException, ScmCommunicationException {
+ 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 ScmCommunicationException(e.getMessage(), e);
+ }
+ }
+}
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 0000000000..e601216a51
--- /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.BitbucketServerApiClient;
+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 BitbucketServerApiClient bitbucketServerApiClient;
+ 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(bitbucketServerApiClient, apiEndpoint);
+ EnvironmentContext context = new EnvironmentContext();
+ context.setSubject(subject);
+ EnvironmentContext.setCurrent(context);
+ }
+
+ @Test
+ public void shouldSkipToFetchUnknownUrls()
+ throws ScmUnauthorizedException, ScmCommunicationException {
+ // given
+ when(bitbucketServerApiClient.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 extends Throwable> exception)
+ throws ScmUnauthorizedException, ScmCommunicationException {
+ // given
+ when(bitbucketServerApiClient.isConnected(eq(someNotBitbucketURL))).thenReturn(true);
+ doThrow(exception).when(bitbucketServerApiClient).getUser(eq(subject));
+ // when
+ fetcher.fetchPersonalAccessToken(subject, someNotBitbucketURL);
+ }
+
+ @Test
+ public void shouldBeAbleToFetchPersonalAccessToken()
+ throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException,
+ ScmBadRequestException {
+ // given
+ when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true);
+ when(bitbucketServerApiClient.getUser(eq(subject))).thenReturn(bitbucketUser);
+ when(bitbucketServerApiClient.getPersonalAccessTokens(eq(bitbucketUser.getSlug())))
+ .thenReturn(Collections.emptyList());
+
+ when(bitbucketServerApiClient.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(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true);
+ when(bitbucketServerApiClient.getUser(eq(subject))).thenReturn(bitbucketUser);
+ when(bitbucketServerApiClient.getPersonalAccessTokens(eq(bitbucketUser.getSlug())))
+ .thenReturn(ImmutableList.of(bitbucketPersonalAccessToken, bitbucketPersonalAccessToken2));
+ when(bitbucketServerApiClient.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(bitbucketServerApiClient)
+ .deletePersonalAccessTokens(
+ eq(bitbucketUser.getSlug()), eq(bitbucketPersonalAccessToken.getId()));
+ verify(bitbucketServerApiClient)
+ .deletePersonalAccessTokens(
+ eq(bitbucketUser.getSlug()), eq(bitbucketPersonalAccessToken2.getId()));
+ }
+
+ @Test(expectedExceptions = {ScmCommunicationException.class})
+ public void shouldRethrowUnExceptionsOnCreatePersonalAccessTokens()
+ throws ScmUnauthorizedException, ScmCommunicationException, ScmItemNotFoundException,
+ ScmBadRequestException {
+ // given
+ when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true);
+ when(bitbucketServerApiClient.getUser(eq(subject))).thenReturn(bitbucketUser);
+ when(bitbucketServerApiClient.getPersonalAccessTokens(eq(bitbucketUser.getSlug())))
+ .thenReturn(Collections.emptyList());
+ doThrow(ScmBadRequestException.class)
+ .when(bitbucketServerApiClient)
+ .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/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java
new file mode 100644
index 0000000000..49ec891aee
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.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.BitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketUser;
+import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException;
+import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class HttpBitbucketServerApiClientTest {
+ 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;
+ BitbucketServerApiClient 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 HttpBitbucketServerApiClient(
+ 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/security/oauth1/BitbucketServerApiClientProviderTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerApiClientProviderTest.java
new file mode 100644
index 0000000000..dee74107b9
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerApiClientProviderTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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 org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.bitbucket.server.HttpBitbucketServerApiClient;
+import org.eclipse.che.api.factory.server.bitbucket.server.NoopBitbucketServerApiClient;
+import org.eclipse.che.commons.annotation.Nullable;
+import org.eclipse.che.inject.ConfigurationException;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class BitbucketServerApiClientProviderTest {
+ BitbucketServerOAuthAuthenticator oAuthAuthenticator;
+
+ @BeforeClass
+ public void setUp() {
+ oAuthAuthenticator =
+ new BitbucketServerOAuthAuthenticator(
+ "df", "private", " https://bitbucket2.server.com", " https://che.server.com");
+ }
+
+ @Test
+ public void shouldBeAbleToCreateBitbucketServerApi() {
+ // given
+ BitbucketServerApiProvider bitbucketServerApiProvider =
+ new BitbucketServerApiProvider(
+ "https://bitbucket.server.com, https://bitbucket2.server.com",
+ "https://bitbucket.server.com",
+ ImmutableSet.of(oAuthAuthenticator));
+ // when
+ BitbucketServerApiClient actual = bitbucketServerApiProvider.get();
+ // then
+ assertNotNull(actual);
+ assertTrue(HttpBitbucketServerApiClient.class.isAssignableFrom(actual.getClass()));
+ }
+
+ @Test(dataProvider = "noopConfig")
+ public void shouldProvideNoopOAuthAuthenticatorIfSomeConfigurationIsNotSet(
+ @Nullable String bitbucketEndpoints,
+ @Nullable String bitbucketOauth1Endpoint,
+ Set authenticators)
+ throws IOException {
+ // given
+ BitbucketServerApiProvider bitbucketServerApiProvider =
+ new BitbucketServerApiProvider(bitbucketEndpoints, bitbucketOauth1Endpoint, authenticators);
+ // when
+ BitbucketServerApiClient actual = bitbucketServerApiProvider.get();
+ // then
+ assertNotNull(actual);
+ assertTrue(NoopBitbucketServerApiClient.class.isAssignableFrom(actual.getClass()));
+ }
+
+ @Test(
+ expectedExceptions = ConfigurationException.class,
+ expectedExceptionsMessageRegExp =
+ "`che.integration.bitbucket.server_endpoints` bitbucket configuration is missing. It should contain values from 'che.oauth1.bitbucket.endpoint'")
+ public void shouldFailToBuildIfEndpointsAreMisconfigured() {
+ // given
+ // when
+ BitbucketServerApiProvider bitbucketServerApiProvider =
+ new BitbucketServerApiProvider(
+ "", "https://bitbucket.server.com", ImmutableSet.of(oAuthAuthenticator));
+ }
+
+ @Test(
+ expectedExceptions = ConfigurationException.class,
+ expectedExceptionsMessageRegExp =
+ "'che.oauth1.bitbucket.endpoint' is set but BitbucketServerOAuthAuthenticator is not deployed correctly")
+ public void shouldFailToBuildIfEndpointsAreMisconfigured2() {
+ // given
+ // when
+ BitbucketServerApiProvider bitbucketServerApiProvider =
+ new BitbucketServerApiProvider(
+ "https://bitbucket.server.com, https://bitbucket2.server.com",
+ "https://bitbucket.server.com",
+ Collections.emptySet());
+ }
+
+ @Test(
+ expectedExceptions = ConfigurationException.class,
+ expectedExceptionsMessageRegExp =
+ "`che.integration.bitbucket.server_endpoints` must contain `https://bitbucket.server.com` value")
+ public void shouldFailToBuildIfEndpointsAreMisconfigured3() {
+ // given
+ // when
+ BitbucketServerApiProvider bitbucketServerApiProvider =
+ new BitbucketServerApiProvider(
+ "https://bitbucket3.server.com, https://bitbucket2.server.com",
+ "https://bitbucket.server.com",
+ ImmutableSet.of(oAuthAuthenticator));
+ }
+
+ @DataProvider(name = "noopConfig")
+ public Object[][] noopConfig() {
+ return new Object[][] {
+ {null, null, null},
+ {"https://bitbucket.server.com, https://bitbucket2.server.com", null, null},
+ {
+ "https://bitbucket.server.com, https://bitbucket2.server.com",
+ null,
+ ImmutableSet.of(oAuthAuthenticator)
+ }
+ };
+ }
+}
diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplierTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplierTest.java
new file mode 100644
index 0000000000..1e25cdd189
--- /dev/null
+++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/security/oauth1/BitbucketServerOAuth1AuthorizationHeaderSupplierTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
+import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException;
+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.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class BitbucketServerOAuth1AuthorizationHeaderSupplierTest {
+ @Mock BitbucketServerOAuthAuthenticator authenticator;
+ @InjectMocks BitbucketServerOAuth1AuthorizationHeaderSupplier supplier;
+
+ Subject subject = new SubjectImpl("user", "234234", "t234234", false);
+
+ @BeforeMethod
+ public void setUp() {
+ EnvironmentContext.getCurrent().setSubject(subject);
+ }
+
+ @Test
+ public void shouldBeAbleToComputeAuthorizationHeader()
+ throws ScmUnauthorizedException, OAuthAuthenticationException, ScmCommunicationException {
+ // given
+ when(authenticator.computeAuthorizationHeader(
+ eq(subject.getUserId()), eq("POST"), eq("/api/user")))
+ .thenReturn("signature");
+ // when
+ String actual = supplier.computeAuthorizationHeader("POST", "/api/user");
+ // then
+ assertEquals(actual, "signature");
+ }
+
+ @Test(
+ expectedExceptions = ScmUnauthorizedException.class,
+ expectedExceptionsMessageRegExp =
+ "user is not authorized in bitbucket-server OAuth1 provider")
+ public void shouldThrowScmUnauthorizedExceptionIfHeaderIsNull()
+ throws OAuthAuthenticationException, ScmUnauthorizedException, ScmCommunicationException {
+ // given
+ when(authenticator.computeAuthorizationHeader(
+ eq(subject.getUserId()), eq("POST"), eq("/api/user")))
+ .thenReturn(null);
+ // when
+ supplier.computeAuthorizationHeader("POST", "/api/user");
+ }
+
+ @Test(
+ expectedExceptions = ScmUnauthorizedException.class,
+ expectedExceptionsMessageRegExp =
+ "user is not authorized in bitbucket-server OAuth1 provider")
+ public void shouldThrowScmUnauthorizedExceptionIfHeaderIsEmpty()
+ throws OAuthAuthenticationException, ScmUnauthorizedException, ScmCommunicationException {
+ // given
+ when(authenticator.computeAuthorizationHeader(
+ eq(subject.getUserId()), eq("POST"), eq("/api/user")))
+ .thenReturn("");
+ // when
+ supplier.computeAuthorizationHeader("POST", "/api/user");
+ }
+
+ @Test(
+ expectedExceptions = ScmCommunicationException.class,
+ expectedExceptionsMessageRegExp = "this is a message")
+ public void shouldThrowScmUnauthorizedExceptionOnOAuthAuthenticationException()
+ throws OAuthAuthenticationException, ScmUnauthorizedException, ScmCommunicationException {
+ // given
+ when(authenticator.computeAuthorizationHeader(
+ eq(subject.getUserId()), eq("POST"), eq("/api/user")))
+ .thenThrow(new OAuthAuthenticationException("this is a message"));
+ // when
+ supplier.computeAuthorizationHeader("POST", "/api/user");
+ }
+}
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 0000000000..1104066803
--- /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 0000000000..0430bfa0f5
--- /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 0000000000..af2b8fc2ba
--- /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 0000000000..7dcb40e18d
--- /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 0000000000..d8c9abcaff
--- /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 0000000000..0ac1dee1ff
--- /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 0000000000..a8dc6f0a70
--- /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 0000000000..eac9465a72
--- /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 0000000000..a5b58aa0ca
--- /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 0000000000..2ce6b01e9a
--- /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 784b929433..cc6e05013b 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. Must return {@code 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/pom.xml b/wsmaster/pom.xml
index 30c3c631c0..9cf453e0fc 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