Obtain and persist Bitbucket personal access token as k8s secret (#18726)
* Obtain and persist Bitbucket personal access token as k8s secret Signed-off-by: Sergii Kabashniuk <skabashniuk@redhat.com>7.28.x
parent
d51e1d0edf
commit
0d0a68fc00
|
|
@ -111,6 +111,10 @@
|
|||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth-bitbucket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth-openshift</artifactId>
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ public class WsMasterModule extends AbstractModule {
|
|||
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 @@ public class WsMasterModule extends AbstractModule {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -184,6 +184,16 @@ che.oauth.openshift.clientsecret=NULL
|
|||
che.oauth.openshift.oauth_endpoint= NULL
|
||||
che.oauth.openshift.verify_token_url= NULL
|
||||
|
||||
# Configuration of Bitbucket Server OAuth1 client. Used to obtain Personal access tokens.
|
||||
# Location of the file with Bitbucket Server application consumer key (equivalent to a username).
|
||||
che.oauth1.bitbucket.consumerkeypath=NULL
|
||||
# Location of the file with Bitbucket Server application private key
|
||||
che.oauth1.bitbucket.privatekeypath=NULL
|
||||
# Bitbucket Server URL. To work correctly with factories the same URL
|
||||
# has to be part of `che.integration.bitbucket.server_endpoints` too.
|
||||
che.oauth1.bitbucket.endpoint=NULL
|
||||
|
||||
|
||||
### Internal
|
||||
|
||||
# Che extensions can be scheduled executions on a time basis.
|
||||
|
|
|
|||
|
|
@ -68,11 +68,6 @@
|
|||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8-standalone</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ public class KeycloakServletModule extends ServletModule {
|
|||
+ "(?!/keycloak/(OIDC|oidc)[^\\/]+$)"
|
||||
// not contains /docs/ (for swagger)
|
||||
+ "(?!.*(/docs/))"
|
||||
// not ends with '/oauth/callback/' or '/keycloak/settings/' or '/system/state'
|
||||
+ "(?!.*(/keycloak/settings/?|/oauth/callback/?|/system/state/?)$)"
|
||||
// not ends with '/oauth/callback/' or '/oauth/1.0/callback/' or '/keycloak/settings/' or
|
||||
// '/system/state'
|
||||
+ "(?!.*(/keycloak/settings/?|/oauth/callback/?|/oauth/1.0/callback/?|/system/state/?)$)"
|
||||
// all other
|
||||
+ ".*";
|
||||
|
||||
|
|
|
|||
5
pom.xml
5
pom.xml
|
|
@ -700,6 +700,11 @@
|
|||
<artifactId>che-core-api-auth</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth-bitbucket</artifactId>
|
||||
<version>${che.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth-github</artifactId>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>che-master-parent</artifactId>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<version>7.27.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>che-core-api-auth-bitbucket</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Che Core :: API :: Authentication Bitbucket</name>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Setup BitbucketServerOAuthAuthenticator in guice container.
|
||||
*
|
||||
* @author Sergii Kabashniuk
|
||||
*/
|
||||
public class BitbucketModule extends AbstractModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
Multibinder<OAuthAuthenticator> oAuthAuthenticators =
|
||||
Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
|
||||
oAuthAuthenticators.addBinding().toProvider(BitbucketServerOAuthAuthenticatorProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.Singleton;
|
||||
|
||||
/**
|
||||
* OAuth1 authentication for Bitbucket Server account.
|
||||
*
|
||||
* @author Igor Vinokur
|
||||
*/
|
||||
@Singleton
|
||||
public class BitbucketServerOAuthAuthenticator extends OAuthAuthenticator {
|
||||
public static final String AUTHENTICATOR_NAME = "bitbucket-server";
|
||||
|
||||
public BitbucketServerOAuthAuthenticator(
|
||||
String consumerKey, String privateKey, String bitbucketEndpoint, 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 AUTHENTICATOR_NAME;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OAuthAuthenticator> {
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(BitbucketServerOAuthAuthenticatorProvider.class);
|
||||
|
||||
private final OAuthAuthenticator authenticator;
|
||||
|
||||
@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.debug("{} Bitbucket OAuthAuthenticator is used.", authenticator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuthAuthenticator get() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
private static OAuthAuthenticator getOAuthAuthenticator(
|
||||
String consumerKeyPath, String privateKeyPath, String bitbucketEndpoint, String apiEndpoint)
|
||||
throws IOException {
|
||||
if (!isNullOrEmpty(bitbucketEndpoint)
|
||||
&& !isNullOrEmpty(consumerKeyPath)
|
||||
&& !isNullOrEmpty(privateKeyPath)) {
|
||||
String consumerKey = Files.readString(Path.of(consumerKeyPath));
|
||||
String privateKey = Files.readString(Path.of(privateKeyPath));
|
||||
if (!isNullOrEmpty(consumerKey) && !isNullOrEmpty(privateKey)) {
|
||||
return new BitbucketServerOAuthAuthenticator(
|
||||
consumerKey, privateKey, bitbucketEndpoint, apiEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return new NoopOAuthAuthenticator();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Dummy implementation of @{@link OAuthAuthenticator} used in the case if no Bitbucket Server
|
||||
* integration is configured.
|
||||
*/
|
||||
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(
|
||||
"The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
String callback(URL requestUrl) throws OAuthAuthenticationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
String computeAuthorizationHeader(String userId, String requestMethod, String requestUrl)
|
||||
throws OAuthAuthenticationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop authenticator cannot be used for authentication. Make sure OAuth is properly configured.");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.io.Files;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class BitbucketServerOAuthAuthenticatorProviderTest {
|
||||
private File cfgFile;
|
||||
private File emptyFile;
|
||||
|
||||
@BeforeClass
|
||||
public void setup() throws IOException {
|
||||
cfgFile = File.createTempFile("BitbucketServerOAuthAuthenticatorProviderTest-", "-cfg");
|
||||
Files.asCharSink(cfgFile, Charset.defaultCharset()).write("tmp-data");
|
||||
cfgFile.deleteOnExit();
|
||||
emptyFile = File.createTempFile("BitbucketServerOAuthAuthenticatorProviderTest-", "-empty");
|
||||
emptyFile.deleteOnExit();
|
||||
}
|
||||
|
||||
@Test(dataProvider = "noopConfig")
|
||||
public void shouldProvideNoopOAuthAuthenticatorIfSomeConfigurationIsNotSet(
|
||||
String consumerKeyPath, String privateKeyPath, String bitbucketEndpoint) throws IOException {
|
||||
// given
|
||||
BitbucketServerOAuthAuthenticatorProvider provider =
|
||||
new BitbucketServerOAuthAuthenticatorProvider(
|
||||
consumerKeyPath, privateKeyPath, bitbucketEndpoint, "http://che.server.com");
|
||||
// when
|
||||
OAuthAuthenticator actual = provider.get();
|
||||
// then
|
||||
assertNotNull(actual);
|
||||
assertTrue(NoopOAuthAuthenticator.class.isAssignableFrom(actual.getClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBeAbleToConfigureValidBitbucketServerOAuthAuthenticator() throws IOException {
|
||||
// given
|
||||
BitbucketServerOAuthAuthenticatorProvider provider =
|
||||
new BitbucketServerOAuthAuthenticatorProvider(
|
||||
cfgFile.getPath(), cfgFile.getPath(), "http://bitubucket.com", "http://che.server.com");
|
||||
// when
|
||||
OAuthAuthenticator actual = provider.get();
|
||||
// then
|
||||
assertNotNull(actual);
|
||||
assertTrue(BitbucketServerOAuthAuthenticator.class.isAssignableFrom(actual.getClass()));
|
||||
}
|
||||
|
||||
@DataProvider(name = "noopConfig")
|
||||
public Object[][] noopConfig() {
|
||||
return new Object[][] {
|
||||
{null, null, null},
|
||||
{cfgFile.getPath(), null, null},
|
||||
{null, cfgFile.getPath(), null},
|
||||
{cfgFile.getPath(), cfgFile.getPath(), null},
|
||||
{emptyFile.getPath(), null, null},
|
||||
{null, emptyFile.getPath(), null},
|
||||
{emptyFile.getPath(), emptyFile.getPath(), null},
|
||||
{cfgFile.getPath(), emptyFile.getPath(), null},
|
||||
{emptyFile.getPath(), cfgFile.getPath(), null},
|
||||
{emptyFile.getPath(), emptyFile.getPath(), "http://bitubucket.com"},
|
||||
{cfgFile.getPath(), emptyFile.getPath(), "http://bitubucket.com"},
|
||||
{emptyFile.getPath(), cfgFile.getPath(), "http://bitubucket.com"},
|
||||
{null, null, "http://bitubucket.com"}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
|
@ -28,6 +28,7 @@ import javax.ws.rs.QueryParam;
|
|||
import javax.ws.rs.core.Response;
|
||||
import org.eclipse.che.api.core.BadRequestException;
|
||||
import org.eclipse.che.api.core.rest.Service;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -82,17 +83,16 @@ public class OAuthAuthenticationService extends Service {
|
|||
@Path("signature")
|
||||
public String signature(
|
||||
@QueryParam("oauth_provider") String providerName,
|
||||
@QueryParam("user_id") String userId,
|
||||
@QueryParam("request_url") String requestUrl,
|
||||
@QueryParam("request_method") String requestMethod)
|
||||
throws OAuthAuthenticationException, BadRequestException {
|
||||
requiredNotNull(providerName, "Provider name");
|
||||
requiredNotNull(userId, "User Id");
|
||||
requiredNotNull(requestUrl, "Request url");
|
||||
requiredNotNull(requestMethod, "Request method");
|
||||
|
||||
return getAuthenticator(providerName)
|
||||
.computeAuthorizationHeader(userId, requestMethod, requestUrl);
|
||||
.computeAuthorizationHeader(
|
||||
EnvironmentContext.getCurrent().getSubject().getUserId(), requestMethod, requestUrl);
|
||||
}
|
||||
|
||||
private OAuthAuthenticator getAuthenticator(String oauthProviderName) throws BadRequestException {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import java.util.Map;
|
|||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
|
||||
/**
|
||||
* Authentication service which allows get access token from OAuth provider site.
|
||||
|
|
@ -105,7 +106,24 @@ public abstract class OAuthAuthenticator {
|
|||
throws OAuthAuthenticationException {
|
||||
try {
|
||||
final GenericUrl callbackUrl = new GenericUrl(redirectUri);
|
||||
callbackUrl.put(STATE_PARAM_KEY, requestUrl.getQuery());
|
||||
String userId = getParameterFromState(requestUrl.getQuery(), USER_ID_PARAM_KEY);
|
||||
String currentUserId = EnvironmentContext.getCurrent().getSubject().getUserId();
|
||||
if (userId != null) {
|
||||
if (currentUserId.equals(userId)) {
|
||||
callbackUrl.put(STATE_PARAM_KEY, requestUrl.getQuery());
|
||||
} else {
|
||||
throw new OAuthAuthenticationException(
|
||||
"Provided query parameter "
|
||||
+ USER_ID_PARAM_KEY
|
||||
+ "="
|
||||
+ userId
|
||||
+ " does not match the current user id: "
|
||||
+ currentUserId);
|
||||
}
|
||||
} else {
|
||||
callbackUrl.put(
|
||||
STATE_PARAM_KEY, requestUrl.getQuery() + "&" + USER_ID_PARAM_KEY + "=" + currentUserId);
|
||||
}
|
||||
|
||||
OAuthGetTemporaryToken temporaryToken;
|
||||
if (requestMethod != null && "post".equalsIgnoreCase(requestMethod)) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@
|
|||
<findbugs.failonerror>false</findbugs.failonerror>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
@ -42,6 +54,18 @@
|
|||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-auth-bitbucket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
|
|
@ -70,15 +94,28 @@
|
|||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.tomakehurst</groupId>
|
||||
<artifactId>wiremock-jre8-standalone</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-json</artifactId>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ package org.eclipse.che.api.factory.server.bitbucket;
|
|||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import org.eclipse.che.api.factory.server.bitbucket.server.BitbucketServerApiClient;
|
||||
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 @@ public class BitbucketServerModule extends AbstractModule {
|
|||
Multibinder<PersonalAccessTokenFetcher> tokenFetcherMultibinder =
|
||||
Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class);
|
||||
tokenFetcherMultibinder.addBinding().to(BitbucketServerPersonalAccessTokenFetcher.class);
|
||||
bind(BitbucketServerApiClient.class).toProvider(BitbucketServerApiProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.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.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,61 @@ import org.eclipse.che.commons.subject.Subject;
|
|||
* 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 BitbucketServerApiClient bitbucketServerApiClient;
|
||||
private final URL apiEndpoint;
|
||||
|
||||
@Inject
|
||||
public BitbucketServerPersonalAccessTokenFetcher(
|
||||
BitbucketServerApiClient bitbucketServerApiClient, @Named("che.api") URL apiEndpoint) {
|
||||
this.bitbucketServerApiClient = bitbucketServerApiClient;
|
||||
this.apiEndpoint = apiEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) {
|
||||
return null;
|
||||
public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl)
|
||||
throws ScmUnauthorizedException, ScmCommunicationException {
|
||||
if (!bitbucketServerApiClient.isConnected(scmServerUrl)) {
|
||||
LOG.debug("not a valid url {} for current fetcher ", scmServerUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
final String tokenName =
|
||||
format(TOKEN_NAME_TEMPLATE, cheUser.getUserId(), apiEndpoint.getHost());
|
||||
try {
|
||||
BitbucketUser user =
|
||||
bitbucketServerApiClient.getUser(EnvironmentContext.getCurrent().getSubject());
|
||||
LOG.debug("Current bitbucket user {} ", user);
|
||||
// cleanup existed
|
||||
List<BitbucketPersonalAccessToken> existingTokens =
|
||||
bitbucketServerApiClient
|
||||
.getPersonalAccessTokens(user.getSlug())
|
||||
.stream()
|
||||
.filter(p -> p.getName().equals(tokenName))
|
||||
.collect(Collectors.toList());
|
||||
for (BitbucketPersonalAccessToken existedToken : existingTokens) {
|
||||
LOG.debug("Deleting existed che token {} {}", existedToken.getId(), existedToken.getName());
|
||||
bitbucketServerApiClient.deletePersonalAccessTokens(user.getSlug(), existedToken.getId());
|
||||
}
|
||||
|
||||
BitbucketPersonalAccessToken token =
|
||||
bitbucketServerApiClient.createPersonalAccessTokens(
|
||||
user.getSlug(), tokenName, ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"));
|
||||
LOG.debug("Token created = {} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.ScmCommunicationException;
|
||||
import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException;
|
||||
|
||||
/** Compute the Authorization header to sign the OAuth 1 request. */
|
||||
public interface AuthorizationHeaderSupplier {
|
||||
String computeAuthorizationHeader(final String requestMethod, final String requestUrl)
|
||||
throws ScmUnauthorizedException, ScmCommunicationException;
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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.Objects;
|
||||
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<String> permissions;
|
||||
|
||||
public BitbucketPersonalAccessToken(String name, Set<String> permissions) {
|
||||
this.name = name;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public BitbucketPersonalAccessToken() {}
|
||||
|
||||
public BitbucketPersonalAccessToken(
|
||||
long id,
|
||||
long createdDate,
|
||||
long lastAuthenticated,
|
||||
String name,
|
||||
String token,
|
||||
BitbucketUser user,
|
||||
Set<String> 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<String> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(Set<String> 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
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
BitbucketPersonalAccessToken that = (BitbucketPersonalAccessToken) o;
|
||||
return id == that.id
|
||||
&& createdDate == that.createdDate
|
||||
&& lastAuthenticated == that.lastAuthenticated
|
||||
&& Objects.equals(name, that.name)
|
||||
&& Objects.equals(token, that.token)
|
||||
&& Objects.equals(user, that.user)
|
||||
&& Objects.equals(permissions, that.permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, createdDate, lastAuthenticated, name, token, user, permissions);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/** Bitbucket Server API client. */
|
||||
public interface BitbucketServerApiClient {
|
||||
/**
|
||||
* @param bitbucketServerUrl
|
||||
* @return - true if client is connected to the given bitbucket server.
|
||||
*/
|
||||
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<BitbucketUser> 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<BitbucketUser> 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<String> 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<BitbucketPersonalAccessToken> getPersonalAccessTokens(String userSlug)
|
||||
throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException;
|
||||
}
|
||||
|
|
@ -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
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* 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.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
||||
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
|
||||
import static java.time.Duration.ofSeconds;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
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.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
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.lang.concurrent.LoggingUncaughtExceptionHandler;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Implementation of @{@link BitbucketServerApiClient} that is using @{@link HttpClient} to
|
||||
* communicate with Bitbucket Server.
|
||||
*/
|
||||
public class HttpBitbucketServerApiClient implements BitbucketServerApiClient {
|
||||
|
||||
private static final ObjectMapper OM = new ObjectMapper();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpBitbucketServerApiClient.class);
|
||||
private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10);
|
||||
private final URI serverUri;
|
||||
private final AuthorizationHeaderSupplier headerProvider;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpBitbucketServerApiClient(
|
||||
String serverUrl, AuthorizationHeaderSupplier authorizationHeaderSupplier) {
|
||||
this.serverUri = URI.create(serverUrl);
|
||||
this.headerProvider = authorizationHeaderSupplier;
|
||||
this.httpClient =
|
||||
HttpClient.newBuilder()
|
||||
.executor(
|
||||
Executors.newCachedThreadPool(
|
||||
new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
|
||||
.setNameFormat(HttpBitbucketServerApiClient.class.getName() + "-%d")
|
||||
.setDaemon(true)
|
||||
.build()))
|
||||
.connectTimeout(DEFAULT_HTTP_TIMEOUT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected(String bitbucketServerUrl) {
|
||||
return serverUri.equals(URI.create(bitbucketServerUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitbucketUser getUser(Subject cheUser)
|
||||
throws ScmUnauthorizedException, ScmCommunicationException {
|
||||
try {
|
||||
// Since Bitbucket server API doesn't provide a way to get an account profile currently
|
||||
// authenticated user we will try to find it and by iterating over the list available to the
|
||||
// current user Bitbucket users and attempting to get their personal access tokens. To speed
|
||||
// up this process first of all we will search among users that contain(somewhere in Bitbucket
|
||||
// user
|
||||
// entity) Che's user username. At the second step, we will search against all visible(to the
|
||||
// current Che's user) bitbucket users that are not included in the first list.
|
||||
Set<String> usersByName =
|
||||
getUsers(cheUser.getUserName())
|
||||
.stream()
|
||||
.map(BitbucketUser::getSlug)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Optional<BitbucketUser> currentUser = findCurrentUser(usersByName);
|
||||
if (currentUser.isPresent()) {
|
||||
return currentUser.get();
|
||||
}
|
||||
Set<String> usersAllExceptByName =
|
||||
getUsers()
|
||||
.stream()
|
||||
.map(BitbucketUser::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 {
|
||||
URI uri = serverUri.resolve("/rest/api/1.0/users/" + slug);
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder(uri)
|
||||
.headers(
|
||||
"Authorization", headerProvider.computeAuthorizationHeader("GET", uri.toString()))
|
||||
.timeout(DEFAULT_HTTP_TIMEOUT)
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOG.trace("executeRequest={}", request);
|
||||
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<BitbucketUser> 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<BitbucketUser> 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 {
|
||||
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(DEFAULT_HTTP_TIMEOUT)
|
||||
.build();
|
||||
|
||||
try {
|
||||
LOG.trace("executeRequest={}", request);
|
||||
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<String> permissions)
|
||||
throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
URI uri = serverUri.resolve("/rest/access-tokens/1.0/users/" + userSlug);
|
||||
|
||||
try {
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder(uri)
|
||||
.PUT(
|
||||
HttpRequest.BodyPublishers.ofString(
|
||||
OM.writeValueAsString(
|
||||
new BitbucketPersonalAccessToken(tokenName, permissions))))
|
||||
.headers(
|
||||
HttpHeaders.AUTHORIZATION,
|
||||
headerProvider.computeAuthorizationHeader("PUT", uri.toString()),
|
||||
HttpHeaders.ACCEPT,
|
||||
MediaType.APPLICATION_JSON,
|
||||
HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_JSON)
|
||||
.timeout(DEFAULT_HTTP_TIMEOUT)
|
||||
.build();
|
||||
LOG.trace("executeRequest={}", request);
|
||||
return executeRequest(
|
||||
httpClient,
|
||||
request,
|
||||
inputStream -> {
|
||||
try {
|
||||
return OM.readValue(inputStream, BitbucketPersonalAccessToken.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
} catch (ScmItemNotFoundException | JsonProcessingException e) {
|
||||
throw new ScmCommunicationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitbucketPersonalAccessToken> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is testing provided collection of user's `slug`s if contains the `slug` of the
|
||||
* currently authenticated user and return it. The major method to test that condition is to get
|
||||
* the list of personal access tokens. Current Che user that is associated with Bitbucket user
|
||||
* should not be able to get someone else list of personal access tokens except his own.
|
||||
*
|
||||
* @param userSlugs set of user's `slug`s to test if it contains currently authenticated user.
|
||||
* @return Bitbucket user from the given set that is associated with the current user. Or
|
||||
* Optional.empty if the given set doesn't contain that user.
|
||||
* @throws ScmCommunicationException can happen if communication between che server and bitbucket
|
||||
* server is failed.
|
||||
* @throws ScmUnauthorizedException can happen if currently authenticated che user is not
|
||||
* associated with bitbucket server.
|
||||
* @throws ScmItemNotFoundException can happen if provided `slug` to test is not associated with
|
||||
* any user on Bitbucket server
|
||||
*/
|
||||
private Optional<BitbucketUser> findCurrentUser(Set<String> userSlugs)
|
||||
throws ScmCommunicationException, ScmUnauthorizedException, ScmItemNotFoundException {
|
||||
|
||||
for (String userSlug : userSlugs) {
|
||||
BitbucketUser user = getUser(userSlug);
|
||||
try {
|
||||
getPersonalAccessTokens(userSlug);
|
||||
return Optional.of(user);
|
||||
} catch (ScmItemNotFoundException | ScmUnauthorizedException e) {
|
||||
// ok
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private <T> List<T> doGetItems(Class<T> tClass, String api, String filter)
|
||||
throws ScmUnauthorizedException, ScmCommunicationException, ScmBadRequestException,
|
||||
ScmItemNotFoundException {
|
||||
List<T> result = new ArrayList<>();
|
||||
Page<T> 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 <T> Page<T> doGetPage(Class<T> tClass, String api, int start, int limit, String filter)
|
||||
throws ScmUnauthorizedException, ScmBadRequestException, ScmCommunicationException,
|
||||
ScmItemNotFoundException {
|
||||
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(DEFAULT_HTTP_TIMEOUT)
|
||||
.build();
|
||||
LOG.trace("executeRequest={}", request);
|
||||
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> T executeRequest(
|
||||
HttpClient httpClient, HttpRequest request, Function<InputStream, T> bodyConverter)
|
||||
throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException,
|
||||
ScmUnauthorizedException {
|
||||
try {
|
||||
HttpResponse<InputStream> response =
|
||||
httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
LOG.trace("executeRequest={} response {}", request, response.statusCode());
|
||||
if (response.statusCode() == 200) {
|
||||
return bodyConverter.apply(response.body());
|
||||
} else if (response.statusCode() == 204) {
|
||||
return null;
|
||||
} else {
|
||||
String body = CharStreams.toString(new InputStreamReader(response.body(), Charsets.UTF_8));
|
||||
switch (response.statusCode()) {
|
||||
case HTTP_BAD_REQUEST:
|
||||
throw new ScmBadRequestException(body);
|
||||
case HTTP_UNAUTHORIZED:
|
||||
throw new ScmUnauthorizedException(body);
|
||||
case HTTP_NOT_FOUND:
|
||||
throw new ScmItemNotFoundException(body);
|
||||
default:
|
||||
throw new ScmCommunicationException(
|
||||
"Unexpected status code " + response.statusCode() + " " + response.toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException | InterruptedException | UncheckedIOException e) {
|
||||
throw new ScmCommunicationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Implementation of @{@link BitbucketServerApiClient} that is going to be deployed in container in
|
||||
* case if no integration with Bitbucket server is needed.
|
||||
*/
|
||||
public class NoopBitbucketServerApiClient implements BitbucketServerApiClient {
|
||||
@Override
|
||||
public boolean isConnected(String bitbucketServerUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitbucketUser getUser(Subject cheUser)
|
||||
throws ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitbucketUser getUser(String slug)
|
||||
throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitbucketUser> getUsers()
|
||||
throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitbucketUser> getUsers(String filter)
|
||||
throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePersonalAccessTokens(String userSlug, Long tokenId)
|
||||
throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException(
|
||||
"The fallback noop api client cannot be used for real operation. Make sure Bitbucket OAuth1 is properly configured.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitbucketPersonalAccessToken createPersonalAccessTokens(
|
||||
String userSlug, String tokenName, Set<String> permissions)
|
||||
throws ScmBadRequestException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException("Invalid usage of BitbucketServerApi");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BitbucketPersonalAccessToken> getPersonalAccessTokens(String userSlug)
|
||||
throws ScmItemNotFoundException, ScmUnauthorizedException, ScmCommunicationException {
|
||||
throw new RuntimeException("Invalid usage of BitbucketServerApi");
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>See more
|
||||
*
|
||||
* <p>https://docs.atlassian.com/bitbucket-server/rest/5.6.1/bitbucket-rest.html
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class Page<T> {
|
||||
private int start;
|
||||
private int size;
|
||||
private int limit;
|
||||
|
||||
@JsonProperty(value = "isLastPage")
|
||||
private boolean isLastPage;
|
||||
|
||||
private int nextPageStart;
|
||||
List<T> 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<T> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public void setValues(List<T> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BitbucketServerApiClient> {
|
||||
|
||||
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<OAuthAuthenticator> 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<OAuthAuthenticator> 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<OAuthAuthenticator> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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-<user987>-<che.server.com>",
|
||||
"2340590skdf3<0>945i0923i4jasoidfj934ui50",
|
||||
bitbucketUser,
|
||||
ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE"));
|
||||
bitbucketPersonalAccessToken2 =
|
||||
new BitbucketPersonalAccessToken(
|
||||
3647456,
|
||||
234345345,
|
||||
23534534,
|
||||
"che-token-<user987>-<che.server.com>",
|
||||
"34545<0>945i0923i4jasoidfj934ui50",
|
||||
bitbucketUser,
|
||||
ImmutableSet.of("REPO_READ"));
|
||||
bitbucketPersonalAccessToken3 =
|
||||
new BitbucketPersonalAccessToken(
|
||||
132423,
|
||||
234345345,
|
||||
23534534,
|
||||
"che-token-<user987>-<che.server.com>",
|
||||
"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-<user987>-<che.server.com>"),
|
||||
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-<user987>-<che.server.com>"),
|
||||
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-<user987>-<che.server.com>"),
|
||||
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}};
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<String> 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<String> 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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<OAuthAuthenticator> 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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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
|
||||
|
||||
-->
|
||||
<configuration>
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
<modules>
|
||||
<module>che-core-api-auth-shared</module>
|
||||
<module>che-core-api-auth</module>
|
||||
<module>che-core-api-auth-bitbucket</module>
|
||||
<module>che-core-api-auth-github</module>
|
||||
<module>che-core-api-auth-openshift</module>
|
||||
<module>che-core-api-workspace-shared</module>
|
||||
|
|
|
|||
Loading…
Reference in New Issue