@@ -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(); | |||
} | |||
@@ -143,6 +143,33 @@ public class BitbucketCloudRestClientTest { | |||
.withMessage("Error returned by Bitbucket Cloud: Your credentials lack one or more required privilege scopes."); | |||
} | |||
@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); |
@@ -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())); |
@@ -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)); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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() { | |||
@@ -133,6 +138,25 @@ public class CheckPatActionTest { | |||
verify(gitlabPrHttpClient).searchProjects(almSetting.getUrl(), PAT_SECRET, null, 1, 10); | |||
} | |||
@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")); | |||
@@ -168,6 +192,25 @@ public class CheckPatActionTest { | |||
.hasMessage("Invalid personal access token"); | |||
} | |||
@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"); |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
@@ -85,6 +86,28 @@ public class SetPatActionTest { | |||
assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid()); | |||
} | |||
@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(); | |||
@@ -122,6 +145,21 @@ public class SetPatActionTest { | |||
assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid()); | |||
} | |||
@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(); | |||
@@ -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)); | |||
} | |||
} |
@@ -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, |