diff options
author | Belen Pruvost <belen.pruvost@sonarsource.com> | 2021-05-12 13:50:29 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-05-21 20:03:36 +0000 |
commit | 5333756be234c4bb50c9edf3c4b2cab5e0483559 (patch) | |
tree | 05803c4c1266e5d33bf55008d8c6148a8202eefd | |
parent | 4660fbc49cbe0f6fd3fc3dc705aaa8858b4e41f7 (diff) | |
download | sonarqube-5333756be234c4bb50c9edf3c4b2cab5e0483559.tar.gz sonarqube-5333756be234c4bb50c9edf3c4b2cab5e0483559.zip |
SONAR-14826 - BBC Authenticate through SetPatAction and CheckPatAction
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, |