aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorBelen Pruvost <belen.pruvost@sonarsource.com>2021-05-12 13:50:29 +0200
committersonartech <sonartech@sonarsource.com>2021-05-21 20:03:36 +0000
commit5333756be234c4bb50c9edf3c4b2cab5e0483559 (patch)
tree05803c4c1266e5d33bf55008d8c6148a8202eefd /server
parent4660fbc49cbe0f6fd3fc3dc705aaa8858b4e41f7 (diff)
downloadsonarqube-5333756be234c4bb50c9edf3c4b2cab5e0483559.tar.gz
sonarqube-5333756be234c4bb50c9edf3c4b2cab5e0483559.zip
SONAR-14826 - BBC Authenticate through SetPatAction and CheckPatAction
Diffstat (limited to 'server')
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java34
-rw-r--r--server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java27
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CheckPatAction.java15
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelper.java41
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/SetPatAction.java29
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CheckPatActionTest.java45
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelperTest.java50
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/SetPatActionTest.java42
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
9 files changed, 268 insertions, 17 deletions
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
index 8731aea80e5..34c7ba2572c 100644
--- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
@@ -46,6 +46,7 @@ import static org.sonar.api.internal.apachecommons.lang.StringUtils.removeEnd;
@ServerSide
public class BitbucketCloudRestClient {
private static final Logger LOG = Loggers.get(BitbucketCloudRestClient.class);
+ private static final String AUTHORIZATION = "Authorization";
private static final String GET = "GET";
private static final String ENDPOINT = "https://api.bitbucket.org";
private static final String ACCESS_TOKEN_ENDPOINT = "https://bitbucket.org/site/oauth2/access_token";
@@ -88,6 +89,17 @@ public class BitbucketCloudRestClient {
}
}
+ /**
+ * Validate parameters provided.
+ */
+ public void validateAppPassword(String encodedCredentials, String workspace) {
+ try {
+ doGetWithBasicAuth(encodedCredentials, buildUrl("/repositories/" + workspace), r -> null);
+ } catch (NotFoundException | IllegalStateException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
private Token validateAccessToken(String clientId, String clientSecret) {
Request request = createAccessTokenRequest(clientId, clientSecret);
try (Response response = client.newCall(request).execute()) {
@@ -133,11 +145,7 @@ public class BitbucketCloudRestClient {
.build();
HttpUrl url = HttpUrl.parse(accessTokenEndpoint);
String credential = Credentials.basic(clientId, clientSecret);
- return new Request.Builder()
- .method("POST", body)
- .url(url)
- .header("Authorization", credential)
- .build();
+ return prepareRequestWithBasicAuthCredentials(credential, "POST", url, body);
}
protected HttpUrl buildUrl(String relativeUrl) {
@@ -149,6 +157,11 @@ public class BitbucketCloudRestClient {
return doCall(request, handler);
}
+ protected <G> G doGetWithBasicAuth(String encodedCredentials, HttpUrl url, Function<Response, G> handler) {
+ Request request = prepareRequestWithBasicAuthCredentials("Basic " + encodedCredentials, GET, url, null);
+ return doCall(request, handler);
+ }
+
protected <G> G doCall(Request request, Function<Response, G> handler) {
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
@@ -232,7 +245,16 @@ public class BitbucketCloudRestClient {
return new Request.Builder()
.method(method, body)
.url(url)
- .header("Authorization", "Bearer " + accessToken)
+ .header(AUTHORIZATION, "Bearer " + accessToken)
+ .build();
+ }
+
+ protected static Request prepareRequestWithBasicAuthCredentials(String encodedCredentials, String method,
+ HttpUrl url, @Nullable RequestBody body) {
+ return new Request.Builder()
+ .method(method, body)
+ .url(url)
+ .header(AUTHORIZATION, encodedCredentials)
.build();
}
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
index c9732a5aeed..387da13f89c 100644
--- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
+++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClientTest.java
@@ -144,6 +144,33 @@ public class BitbucketCloudRestClientTest {
}
@Test
+ public void validate_app_password_success() throws Exception {
+ String reposResponse = "{\"pagelen\": 10,\n" +
+ "\"values\": [],\n" +
+ "\"page\": 1,\n" +
+ "\"size\": 0\n" +
+ "}";
+
+ server.enqueue(new MockResponse().setBody(reposResponse));
+ server.enqueue(new MockResponse().setBody("OK"));
+
+ underTest.validateAppPassword("user:password", "workspace");
+
+ RecordedRequest request = server.takeRequest();
+ assertThat(request.getPath()).isEqualTo("/2.0/repositories/workspace");
+ assertThat(request.getHeader("Authorization")).isNotNull();
+ }
+
+ @Test
+ public void validate_app_password_with_invalid_credentials() {
+ server.enqueue(new MockResponse().setResponseCode(401).setHeader("Content-Type", JSON_MEDIA_TYPE));
+
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> underTest.validateAppPassword("wrong:wrong", "workspace"))
+ .withMessage("Unable to contact Bitbucket Cloud servers");
+ }
+
+ @Test
public void nullErrorBodyIsSupported() throws IOException {
OkHttpClient clientMock = mock(OkHttpClient.class);
Call callMock = mock(Call.class);
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CheckPatAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CheckPatAction.java
index 85e7f31bd02..c39513b6f9c 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CheckPatAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CheckPatAction.java
@@ -20,8 +20,10 @@
package org.sonar.server.almintegration.ws;
import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
import org.sonar.alm.client.gitlab.GitlabHttpClient;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -38,22 +40,27 @@ import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
public class CheckPatAction implements AlmIntegrationsWsAction {
private static final String PARAM_ALM_SETTING = "almSetting";
+ private static final String APP_PASSWORD_CANNOT_BE_NULL = "App Password and Username cannot be null";
private static final String PAT_CANNOT_BE_NULL = "PAT cannot be null";
private static final String URL_CANNOT_BE_NULL = "URL cannot be null";
+ private static final String WORKSPACE_CANNOT_BE_NULL = "Workspace cannot be null";
private final DbClient dbClient;
private final UserSession userSession;
private final AzureDevOpsHttpClient azureDevOpsHttpClient;
+ private final BitbucketCloudRestClient bitbucketCloudRestClient;
private final BitbucketServerRestClient bitbucketServerRestClient;
private final GitlabHttpClient gitlabHttpClient;
public CheckPatAction(DbClient dbClient, UserSession userSession,
AzureDevOpsHttpClient azureDevOpsHttpClient,
+ BitbucketCloudRestClient bitbucketCloudRestClient,
BitbucketServerRestClient bitbucketServerRestClient,
GitlabHttpClient gitlabHttpClient) {
this.dbClient = dbClient;
this.userSession = userSession;
this.azureDevOpsHttpClient = azureDevOpsHttpClient;
+ this.bitbucketCloudRestClient = bitbucketCloudRestClient;
this.bitbucketServerRestClient = bitbucketServerRestClient;
this.gitlabHttpClient = gitlabHttpClient;
}
@@ -66,7 +73,8 @@ public class CheckPatAction implements AlmIntegrationsWsAction {
.setPost(false)
.setInternal(true)
.setSince("8.2")
- .setHandler(this);
+ .setHandler(this)
+ .setChangelog(new Change("9.0", "Bitbucket Cloud support was added"));
action.createParam(PARAM_ALM_SETTING)
.setRequired(true)
@@ -110,6 +118,11 @@ public class CheckPatAction implements AlmIntegrationsWsAction {
requireNonNull(almPatDto.getPersonalAccessToken(), PAT_CANNOT_BE_NULL),
null, 1, 10);
break;
+ case BITBUCKET_CLOUD:
+ bitbucketCloudRestClient.validateAppPassword(
+ requireNonNull(almPatDto.getPersonalAccessToken(), APP_PASSWORD_CANNOT_BE_NULL),
+ requireNonNull(almSettingDto.getAppId(), WORKSPACE_CANNOT_BE_NULL));
+ break;
case GITHUB:
default:
throw new IllegalArgumentException(String.format("unsupported ALM %s", almSettingDto.getAlm()));
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelper.java
new file mode 100644
index 00000000000..a6c02c31317
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelper.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.almintegration.ws;
+
+import java.util.Base64;
+import javax.annotation.Nullable;
+import org.sonar.db.alm.setting.ALM;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonar.db.alm.setting.ALM.BITBUCKET_CLOUD;
+
+public class CredentialsEncoderHelper {
+ private CredentialsEncoderHelper() {
+
+ }
+
+ public static String encodeCredentials(ALM alm, String pat, @Nullable String username) {
+ if (!alm.equals(BITBUCKET_CLOUD)) {
+ return pat;
+ }
+
+ return Base64.getEncoder().encodeToString((username + ":" + pat).getBytes(UTF_8));
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/SetPatAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/SetPatAction.java
index d040049a778..3d66e0f1b5e 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/SetPatAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almintegration/ws/SetPatAction.java
@@ -19,8 +19,10 @@
*/
package org.sonar.server.almintegration.ws;
+import com.google.common.base.Strings;
import java.util.Arrays;
import java.util.Optional;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -36,6 +38,7 @@ import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static org.sonar.db.alm.setting.ALM.AZURE_DEVOPS;
import static org.sonar.db.alm.setting.ALM.BITBUCKET;
+import static org.sonar.db.alm.setting.ALM.BITBUCKET_CLOUD;
import static org.sonar.db.alm.setting.ALM.GITLAB;
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
@@ -43,6 +46,7 @@ public class SetPatAction implements AlmIntegrationsWsAction {
private static final String PARAM_ALM_SETTING = "almSetting";
private static final String PARAM_PAT = "pat";
+ private static final String PARAM_USERNAME = "username";
private final DbClient dbClient;
private final UserSession userSession;
@@ -56,11 +60,12 @@ public class SetPatAction implements AlmIntegrationsWsAction {
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("set_pat")
.setDescription("Set a Personal Access Token for the given ALM setting<br/>" +
- "Only valid for Azure DevOps, Bitbucket Server & GitLab Alm Setting<br/>" +
+ "Only valid for Azure DevOps, Bitbucket Server, GitLab Alm and Bitbucket Cloud Setting<br/>" +
"Requires the 'Create Projects' permission")
.setPost(true)
.setSince("8.2")
- .setHandler(this);
+ .setHandler(this)
+ .setChangelog(new Change("9.0", "Bitbucket Cloud support and optional Username parameter were added"));
action.createParam(PARAM_ALM_SETTING)
.setRequired(true)
@@ -69,6 +74,10 @@ public class SetPatAction implements AlmIntegrationsWsAction {
.setRequired(true)
.setMaximumLength(2000)
.setDescription("Personal Access Token");
+ action.createParam(PARAM_USERNAME)
+ .setRequired(false)
+ .setMaximumLength(2000)
+ .setDescription("Username");
}
@Override
@@ -83,22 +92,29 @@ public class SetPatAction implements AlmIntegrationsWsAction {
String pat = request.mandatoryParam(PARAM_PAT);
String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING);
+ String username = request.param(PARAM_USERNAME);
String userUuid = requireNonNull(userSession.getUuid(), "User UUID cannot be null");
AlmSettingDto almSetting = dbClient.almSettingDao().selectByKey(dbSession, almSettingKey)
.orElseThrow(() -> new NotFoundException(format("ALM Setting '%s' not found", almSettingKey)));
- Preconditions.checkArgument(Arrays.asList(AZURE_DEVOPS, BITBUCKET, GITLAB)
- .contains(almSetting.getAlm()), "Only Azure DevOps, Bibucket Server and GitLab ALM Settings are supported.");
+ Preconditions.checkArgument(Arrays.asList(AZURE_DEVOPS, BITBUCKET, GITLAB, BITBUCKET_CLOUD)
+ .contains(almSetting.getAlm()), "Only Azure DevOps, Bitbucket Server, GitLab ALM and Bitbucket Cloud Settings are supported.");
+
+ if(almSetting.getAlm().equals(BITBUCKET_CLOUD)) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(username), "Username cannot be null for Bitbucket Cloud");
+ }
+
+ String resultingPat = CredentialsEncoderHelper.encodeCredentials(almSetting.getAlm(), pat, username);
Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSetting);
if (almPatDto.isPresent()) {
AlmPatDto almPat = almPatDto.get();
- almPat.setPersonalAccessToken(pat);
+ almPat.setPersonalAccessToken(resultingPat);
dbClient.almPatDao().update(dbSession, almPat);
} else {
AlmPatDto almPat = new AlmPatDto()
- .setPersonalAccessToken(pat)
+ .setPersonalAccessToken(resultingPat)
.setAlmSettingUuid(almSetting.getUuid())
.setUserUuid(userUuid);
dbClient.almPatDao().insert(dbSession, almPat);
@@ -106,5 +122,4 @@ public class SetPatAction implements AlmIntegrationsWsAction {
dbSession.commit();
}
}
-
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CheckPatActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CheckPatActionTest.java
index 5b09e6a4f9f..fe26b0a498e 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CheckPatActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CheckPatActionTest.java
@@ -22,6 +22,7 @@ package org.sonar.server.almintegration.ws;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient;
import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
import org.sonar.alm.client.gitlab.GitlabHttpClient;
import org.sonar.api.server.ws.WebService;
@@ -41,6 +42,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -56,9 +59,11 @@ public class CheckPatActionTest {
public DbTester db = DbTester.create();
private final AzureDevOpsHttpClient azureDevOpsPrHttpClient = mock(AzureDevOpsHttpClient.class);
+ private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
private final GitlabHttpClient gitlabPrHttpClient = mock(GitlabHttpClient.class);
- private final WsActionTester ws = new WsActionTester(new CheckPatAction(db.getDbClient(), userSession, azureDevOpsPrHttpClient, bitbucketServerRestClient, gitlabPrHttpClient));
+ private final WsActionTester ws = new WsActionTester(new CheckPatAction(db.getDbClient(), userSession, azureDevOpsPrHttpClient,
+ bitbucketCloudRestClient, bitbucketServerRestClient, gitlabPrHttpClient));
@Test
public void check_pat_for_github() {
@@ -134,6 +139,25 @@ public class CheckPatActionTest {
}
@Test
+ public void check_pat_for_bitbucketcloud() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting();
+ db.almPats().insert(dto -> {
+ dto.setAlmSettingUuid(almSetting.getUuid());
+ dto.setUserUuid(user.getUuid());
+ dto.setPersonalAccessToken(PAT_SECRET);
+ });
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .execute();
+
+ assertThat(almSetting.getAppId()).isNotNull();
+ verify(bitbucketCloudRestClient).validateAppPassword(PAT_SECRET, almSetting.getAppId());
+ }
+
+ @Test
public void fail_when_personal_access_token_is_invalid_for_bitbucket() {
when(bitbucketServerRestClient.getRecentRepo(any(), any())).thenThrow(new IllegalArgumentException("Invalid personal access token"));
UserDto user = db.users().insertUser();
@@ -169,6 +193,25 @@ public class CheckPatActionTest {
}
@Test
+ public void fail_when_personal_access_token_is_invalid_for_bitbucketcloud() {
+ doThrow(new IllegalArgumentException("Invalid personal access token"))
+ .when(bitbucketCloudRestClient).validateAppPassword(anyString(), anyString());
+
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting();
+ db.almPats().insert(dto -> {
+ dto.setAlmSettingUuid(almSetting.getUuid());
+ dto.setUserUuid(user.getUuid());
+ });
+
+ TestRequest request = ws.newRequest().setParam("almSetting", almSetting.getKey());
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid personal access token");
+ }
+
+ @Test
public void fail_when_not_logged_in() {
TestRequest request = ws.newRequest().setParam("almSetting", "anyvalue");
assertThatThrownBy(request::execute)
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelperTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelperTest.java
new file mode 100644
index 00000000000..023bd79b668
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/CredentialsEncoderHelperTest.java
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.almintegration.ws;
+
+import java.util.Base64;
+import org.junit.Test;
+import org.sonar.db.alm.setting.ALM;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CredentialsEncoderHelperTest {
+ private static final String PAT = "pat";
+ private static final String USERNAME = "user";
+
+ @Test
+ public void encodes_credential_returns_just_pat_for_non_bitbucketcloud() {
+ assertThat(CredentialsEncoderHelper.encodeCredentials(ALM.GITHUB, PAT, null))
+ .isEqualTo("pat");
+ }
+
+ @Test
+ public void encodes_credential_returns_username_and_encoded_pat_for_bitbucketcloud() {
+ String encodedPat = Base64.getEncoder().encodeToString((USERNAME + ":" + PAT).getBytes(UTF_8));
+
+ String encodedCredential = CredentialsEncoderHelper.encodeCredentials(ALM.BITBUCKET_CLOUD, PAT, USERNAME);
+ assertThat(encodedCredential)
+ .doesNotContain(USERNAME)
+ .doesNotContain(PAT)
+ .contains(encodedPat);
+ }
+
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/SetPatActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/SetPatActionTest.java
index 19aac89688b..556a2970c60 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/SetPatActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/SetPatActionTest.java
@@ -26,6 +26,7 @@ import org.junit.rules.ExpectedException;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
+import org.sonar.db.alm.setting.ALM;
import org.sonar.db.alm.setting.AlmSettingDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.ForbiddenException;
@@ -86,6 +87,28 @@ public class SetPatActionTest {
}
@Test
+ public void set_new_bitbucketcloud_pat() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ String pat = "12345678987654321";
+ String username = "test-user";
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", pat)
+ .setParam("username", username)
+ .execute();
+
+ Optional<AlmPatDto> actualAlmPat = db.getDbClient().almPatDao().selectByUserAndAlmSetting(db.getSession(), user.getUuid(), almSetting);
+ assertThat(actualAlmPat).isPresent();
+ assertThat(actualAlmPat.get().getPersonalAccessToken()).isEqualTo(CredentialsEncoderHelper.encodeCredentials(ALM.BITBUCKET_CLOUD, pat, username));
+ assertThat(actualAlmPat.get().getUserUuid()).isEqualTo(user.getUuid());
+ assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid());
+ }
+
+ @Test
public void set_new_gitlab_pat() {
UserDto user = db.users().insertUser();
AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting();
@@ -123,6 +146,21 @@ public class SetPatActionTest {
}
@Test
+ public void fail_when_bitbucketcloud_without_username() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketCloudAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Username cannot be null for Bitbucket Cloud");
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "12345678987654321")
+ .execute();
+ }
+
+ @Test
public void fail_when_alm_setting_unknow() {
UserDto user = db.users().insertUser();
userSession.logIn(user).addPermission(PROVISION_PROJECTS);
@@ -143,7 +181,7 @@ public class SetPatActionTest {
userSession.logIn(user).addPermission(PROVISION_PROJECTS);
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Only Azure DevOps, Bibucket Server and GitLab ALM Settings are supported.");
+ expectedException.expectMessage("Only Azure DevOps, Bitbucket Server, GitLab ALM and Bitbucket Cloud Settings are supported.");
ws.newRequest()
.setParam("almSetting", almSetting.getKey())
@@ -177,7 +215,7 @@ public class SetPatActionTest {
assertThat(def.isPost()).isTrue();
assertThat(def.params())
.extracting(WebService.Param::key, WebService.Param::isRequired)
- .containsExactlyInAnyOrder(tuple("almSetting", true), tuple("pat", true));
+ .containsExactlyInAnyOrder(tuple("almSetting", true), tuple("pat", true), tuple("username", false));
}
}
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 5e66fc876b0..576d314d475 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -48,6 +48,7 @@ import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.platform.PlatformEditionProvider;
import org.sonar.server.almintegration.ws.AlmIntegrationsWSModule;
+import org.sonar.server.almintegration.ws.CredentialsEncoderHelper;
import org.sonar.server.almintegration.ws.ImportHelper;
import org.sonar.server.almsettings.MultipleAlmFeatureProvider;
import org.sonar.server.almsettings.ws.AlmSettingsWsModule;
@@ -500,6 +501,7 @@ public class PlatformLevel4 extends PlatformLevel {
// ALM integrations
TimeoutConfigurationImpl.class,
+ CredentialsEncoderHelper.class,
ImportHelper.class,
GithubAppSecurityImpl.class,
GithubApplicationClientImpl.class,