import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
-
import java.io.IOException;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import javax.annotation.Nullable;
-
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
.build();
}
+ public void checkPAT(String serverUrl, String token) {
+ String url = String.format("%s/_apis/projects", getTrimmedUrl(serverUrl));
+ LOG.debug(String.format("check pat : [%s]", url));
+ doGet(token, url);
+ }
+
public GsonAzureProjectList getProjects(String serverUrl, String token) {
String url = String.format("%s/_apis/projects", getTrimmedUrl(serverUrl));
LOG.debug(String.format("get projects : [%s]", url));
return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), GsonAzureProjectList.class));
}
-
public GsonAzureRepoList getRepos(String serverUrl, String token, @Nullable String projectName) {
String url;
if (projectName != null && !projectName.isEmpty()) {
return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), GsonAzureRepo.class));
}
- protected <G> G doGet(String token, String url, Function<Response, G> handler) {
+ private void doGet(String token, String url) {
+ Request request = prepareRequestWithToken(token, GET, url, null);
+ doCall(request);
+ }
+
+ protected void doCall(Request request) {
+ try (Response response = client.newCall(request).execute()) {
+ checkResponseIsSuccessful(response);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(UNABLE_TO_CONTACT_AZURE_SERVER, e);
+ }
+ }
+
+ protected <G> G doGet(String token, String url, Function<Response, G> handler) {
Request request = prepareRequestWithToken(token, GET, url, null);
return doCall(request, handler);
}
- protected <G> G doCall(Request request, Function<Response, G> handler) {
+ protected <G> G doCall(Request request, Function<Response, G> handler) {
try (Response response = client.newCall(request).execute()) {
checkResponseIsSuccessful(response);
return handler.apply(response);
return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), Repository.class));
}
+ public RepositoryList getRecentRepo(String serverUrl, String token) {
+ HttpUrl url = buildUrl(serverUrl, "/rest/api/1.0/profile/recent/repos");
+ return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), RepositoryList.class));
+ }
+
public ProjectList getProjects(String serverUrl, String token) {
HttpUrl url = buildUrl(serverUrl, "/rest/api/1.0/projects");
return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), ProjectList.class));
*/
package org.sonar.alm.client.azure;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
-import javax.annotation.Nullable;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
public class AzureDevOpsHttpClientTest {
public static final String UNABLE_TO_CONTACT_AZURE = "Unable to contact Azure DevOps server, got an unexpected response";
server.shutdown();
}
+ @Test
+ public void check_pat() throws InterruptedException {
+ enqueueResponse(200, " { \"count\": 1,\n" +
+ " \"value\": [\n" +
+ " {\n" +
+ " \"id\": \"3311cd05-3f00-4a5e-b47f-df94a9982b6e\",\n" +
+ " \"name\": \"Project\",\n" +
+ " \"description\": \"Project Description\",\n" +
+ " \"url\": \"https://ado.sonarqube.com/DefaultCollection/_apis/projects/3311cd05-3f00-4a5e-b47f-df94a9982b6e\",\n" +
+ " \"state\": \"wellFormed\",\n" +
+ " \"revision\": 63,\n" +
+ " \"visibility\": \"private\"\n" +
+ " }]}");
+
+ underTest.checkPAT(server.url("").toString(), "token");
+
+ RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS);
+ String azureDevOpsUrlCall = request.getRequestUrl().toString();
+ assertThat(azureDevOpsUrlCall).isEqualTo(server.url("") + "_apis/projects");
+ assertThat(request.getMethod()).isEqualTo("GET");
+
+ assertThat(logTester.logs()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.DEBUG))
+ .contains("check pat : [" + server.url("").toString() + "_apis/projects]");
+ }
+
+ @Test
+ public void check_invalid_pat() {
+ enqueueResponse(401);
+
+ String serverUrl = server.url("").toString();
+ assertThatThrownBy(() -> underTest.checkPAT(serverUrl, "invalid-token"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid personal access token");
+ }
+
+ @Test
+ public void check_pat_with_server_error() {
+ enqueueResponse(500);
+
+ String serverUrl = server.url("").toString();
+ assertThatThrownBy(() -> underTest.checkPAT(serverUrl, "token"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Unable to contact Azure DevOps server");
+ }
+
@Test
public void get_projects() throws InterruptedException {
enqueueResponse(200, " { \"count\": 2,\n" +
tuple(1L, "potato", "potato", 1L, "HEY", "hey"));
}
+ @Test
+ public void get_recent_repos() {
+ server.enqueue(new MockResponse()
+ .setHeader("Content-Type", "application/json;charset=UTF-8")
+ .setBody("{\n" +
+ " \"isLastPage\": true,\n" +
+ " \"values\": [\n" +
+ " {\n" +
+ " \"slug\": \"banana\",\n" +
+ " \"id\": 2,\n" +
+ " \"name\": \"banana\",\n" +
+ " \"project\": {\n" +
+ " \"key\": \"HOY\",\n" +
+ " \"id\": 2,\n" +
+ " \"name\": \"hoy\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"slug\": \"potato\",\n" +
+ " \"id\": 1,\n" +
+ " \"name\": \"potato\",\n" +
+ " \"project\": {\n" +
+ " \"key\": \"HEY\",\n" +
+ " \"id\": 1,\n" +
+ " \"name\": \"hey\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}"));
+
+ RepositoryList gsonBBSRepoList = underTest.getRecentRepo(server.url("/").toString(), "token");
+ assertThat(gsonBBSRepoList.isLastPage()).isTrue();
+ assertThat(gsonBBSRepoList.getValues()).hasSize(2);
+ assertThat(gsonBBSRepoList.getValues()).extracting(Repository::getId, Repository::getName, Repository::getSlug,
+ g -> g.getProject().getId(), g -> g.getProject().getKey(), g -> g.getProject().getName())
+ .containsExactlyInAnyOrder(
+ tuple(2L, "banana", "banana", 2L, "HOY", "hoy"),
+ tuple(1L, "potato", "potato", 1L, "HEY", "hey"));
+ }
+
@Test
public void get_repo() {
server.enqueue(new MockResponse()
@Override
protected void configureModule() {
add(
+ CheckPatAction.class,
+ SetPatAction.class,
ImportBitbucketServerProjectAction.class,
ListBitbucketServerProjectsAction.class,
SearchBitbucketServerReposAction.class,
--- /dev/null
+/*
+ * 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 org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
+import org.sonar.alm.client.gitlab.GitlabHttpClient;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.pat.AlmPatDto;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+
+import static java.util.Objects.requireNonNull;
+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 PAT_CANNOT_BE_NULL = "PAT cannot be null";
+ private static final String URL_CANNOT_BE_NULL = "URL cannot be null";
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final AzureDevOpsHttpClient azureDevOpsHttpClient;
+ private final BitbucketServerRestClient bitbucketServerRestClient;
+ private final GitlabHttpClient gitlabHttpClient;
+
+ public CheckPatAction(DbClient dbClient, UserSession userSession,
+ AzureDevOpsHttpClient azureDevOpsHttpClient,
+ BitbucketServerRestClient bitbucketServerRestClient,
+ GitlabHttpClient gitlabHttpClient) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.azureDevOpsHttpClient = azureDevOpsHttpClient;
+ this.bitbucketServerRestClient = bitbucketServerRestClient;
+ this.gitlabHttpClient = gitlabHttpClient;
+ }
+
+ @Override
+ public void define(WebService.NewController context) {
+ WebService.NewAction action = context.createAction("check_pat")
+ .setDescription("Check validity of a Personal Access Token for the given ALM setting<br/>" +
+ "Requires the 'Create Projects' permission")
+ .setPost(false)
+ .setInternal(true)
+ .setSince("8.2")
+ .setHandler(this);
+
+ action.createParam(PARAM_ALM_SETTING)
+ .setRequired(true)
+ .setMaximumLength(200)
+ .setDescription("ALM setting key");
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ doHandle(request);
+ response.noContent();
+ }
+
+ private void doHandle(Request request) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ userSession.checkLoggedIn().checkPermission(PROVISION_PROJECTS);
+
+ String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING);
+ String userUuid = requireNonNull(userSession.getUuid(), "User cannot be null");
+ AlmSettingDto almSettingDto = dbClient.almSettingDao().selectByKey(dbSession, almSettingKey)
+ .orElseThrow(() -> new NotFoundException(String.format("ALM Setting '%s' not found", almSettingKey)));
+
+ AlmPatDto almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSettingDto)
+ .orElseThrow(() -> new IllegalArgumentException(String.format("personal access token for '%s' is missing", almSettingKey)));
+
+ switch (almSettingDto.getAlm()) {
+ case AZURE_DEVOPS:
+ azureDevOpsHttpClient.checkPAT(
+ requireNonNull(almSettingDto.getUrl(), URL_CANNOT_BE_NULL),
+ requireNonNull(almPatDto.getPersonalAccessToken(), PAT_CANNOT_BE_NULL));
+ break;
+ case BITBUCKET:
+ // Do an authenticate call to Bitbucket Server to validate that the user's personal access token is valid
+ bitbucketServerRestClient.getRecentRepo(
+ requireNonNull(almSettingDto.getUrl(), URL_CANNOT_BE_NULL),
+ requireNonNull(almPatDto.getPersonalAccessToken(), PAT_CANNOT_BE_NULL));
+ break;
+ case GITLAB:
+ gitlabHttpClient.searchProjects(
+ requireNonNull(almSettingDto.getUrl(), URL_CANNOT_BE_NULL),
+ requireNonNull(almPatDto.getPersonalAccessToken(), PAT_CANNOT_BE_NULL),
+ null, 1, 10);
+ break;
+ case GITHUB:
+ default:
+ throw new IllegalArgumentException(String.format("unsupported ALM %s", almSettingDto.getAlm()));
+ }
+
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.Arrays;
+import java.util.Optional;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.Preconditions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.alm.pat.AlmPatDto;
+import org.sonar.db.alm.setting.AlmSettingDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+
+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.GITLAB;
+import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
+
+public class SetPatAction implements AlmIntegrationsWsAction {
+
+ private static final String PARAM_ALM_SETTING = "almSetting";
+ private static final String PARAM_PAT = "pat";
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+
+ public SetPatAction(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ @Override
+ 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/>" +
+ "Requires the 'Create Projects' permission")
+ .setPost(true)
+ .setSince("8.2")
+ .setHandler(this);
+
+ action.createParam(PARAM_ALM_SETTING)
+ .setRequired(true)
+ .setDescription("ALM setting key");
+ action.createParam(PARAM_PAT)
+ .setRequired(true)
+ .setMaximumLength(2000)
+ .setDescription("Personal Access Token");
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ doHandle(request);
+ response.noContent();
+ }
+
+ private void doHandle(Request request) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ userSession.checkLoggedIn().checkPermission(PROVISION_PROJECTS);
+
+ String pat = request.mandatoryParam(PARAM_PAT);
+ String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING);
+
+ 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.");
+
+ Optional<AlmPatDto> almPatDto = dbClient.almPatDao().selectByUserAndAlmSetting(dbSession, userUuid, almSetting);
+ if (almPatDto.isPresent()) {
+ AlmPatDto almPat = almPatDto.get();
+ almPat.setPersonalAccessToken(pat);
+ dbClient.almPatDao().update(dbSession, almPat);
+ } else {
+ AlmPatDto almPat = new AlmPatDto()
+ .setPersonalAccessToken(pat)
+ .setAlmSettingUuid(almSetting.getUuid())
+ .setUserUuid(userUuid);
+ dbClient.almPatDao().insert(dbSession, almPat);
+ }
+ dbSession.commit();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.sonar.alm.client.azure.AzureDevOpsHttpClient;
+import org.sonar.alm.client.bitbucketserver.BitbucketServerRestClient;
+import org.sonar.alm.client.gitlab.GitlabHttpClient;
+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.AlmSettingDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.alm.integration.pat.AlmPatsTesting.newAlmPatDto;
+import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
+
+public class CheckPatActionTest {
+
+ public static final String PAT_SECRET = "pat-secret";
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final AzureDevOpsHttpClient azureDevOpsPrHttpClient = mock(AzureDevOpsHttpClient.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));
+
+ @Test
+ public void check_pat_for_github() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting();
+ 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("unsupported ALM GITHUB");
+ }
+
+ @Test
+ public void check_pat_for_azure_devops() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertAzureAlmSetting();
+ 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.getUrl()).isNotNull();
+ verify(azureDevOpsPrHttpClient).checkPAT(almSetting.getUrl(), PAT_SECRET);
+ }
+
+ @Test
+ public void check_pat_for_bitbucket() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketAlmSetting();
+ 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.getUrl()).isNotNull();
+ verify(bitbucketServerRestClient).getRecentRepo(almSetting.getUrl(), PAT_SECRET);
+ }
+
+ @Test
+ public void check_pat_for_gitlab() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting();
+ 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.getUrl()).isNotNull();
+ verify(gitlabPrHttpClient).searchProjects(almSetting.getUrl(), PAT_SECRET, null, 1, 10);
+ }
+
+ @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();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketAlmSetting();
+ 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_personal_access_token_is_invalid_for_gitlab() {
+ when(gitlabPrHttpClient.searchProjects(any(), any(), any(), anyInt(), anyInt()))
+ .thenThrow(new IllegalArgumentException("Invalid personal access token"));
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertGitlabAlmSetting();
+ 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)
+ .isInstanceOf(UnauthorizedException.class);
+ }
+
+ @Test
+ public void fail_when_no_creation_project_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ TestRequest request = ws.newRequest().setParam("almSetting", "anyvalue");
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
+ }
+
+ @Test
+ public void check_pat_is_missing() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketAlmSetting();
+
+ TestRequest request = ws.newRequest().setParam("almSetting", almSetting.getKey());
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("personal access token for '" + almSetting.getKey() + "' is missing");
+ }
+
+ @Test
+ public void fail_check_pat_alm_setting_not_found() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ AlmPatDto almPatDto = newAlmPatDto();
+ db.getDbClient().almPatDao().insert(db.getSession(), almPatDto);
+
+ TestRequest request = ws.newRequest().setParam("almSetting", "testKey");
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("ALM Setting 'testKey' not found");
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action def = ws.getDef();
+
+ assertThat(def.since()).isEqualTo("8.2");
+ assertThat(def.isPost()).isFalse();
+ assertThat(def.isInternal()).isTrue();
+ assertThat(def.params())
+ .extracting(WebService.Param::key, WebService.Param::isRequired)
+ .containsExactlyInAnyOrder(tuple("almSetting", true));
+ }
+
+}
--- /dev/null
+/*
+ * 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.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+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.AlmSettingDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
+
+public class SetPatActionTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final WsActionTester ws = new WsActionTester(new SetPatAction(db.getDbClient(), userSession));
+
+ @Test
+ public void set_new_azuredevops_pat() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertAzureAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "12345678987654321")
+ .execute();
+
+ Optional<AlmPatDto> actualAlmPat = db.getDbClient().almPatDao().selectByUserAndAlmSetting(db.getSession(), user.getUuid(), almSetting);
+ assertThat(actualAlmPat).isPresent();
+ assertThat(actualAlmPat.get().getPersonalAccessToken()).isEqualTo("12345678987654321");
+ assertThat(actualAlmPat.get().getUserUuid()).isEqualTo(user.getUuid());
+ assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid());
+ }
+
+ @Test
+ public void set_new_bitbucketserver_pat() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "12345678987654321")
+ .execute();
+
+ Optional<AlmPatDto> actualAlmPat = db.getDbClient().almPatDao().selectByUserAndAlmSetting(db.getSession(), user.getUuid(), almSetting);
+ assertThat(actualAlmPat).isPresent();
+ assertThat(actualAlmPat.get().getPersonalAccessToken()).isEqualTo("12345678987654321");
+ 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();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "12345678987654321")
+ .execute();
+
+ Optional<AlmPatDto> actualAlmPat = db.getDbClient().almPatDao().selectByUserAndAlmSetting(db.getSession(), user.getUuid(), almSetting);
+ assertThat(actualAlmPat).isPresent();
+ assertThat(actualAlmPat.get().getPersonalAccessToken()).isEqualTo("12345678987654321");
+ assertThat(actualAlmPat.get().getUserUuid()).isEqualTo(user.getUuid());
+ assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid());
+ }
+
+ @Test
+ public void set_existing_pat() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertBitbucketAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+ db.almPats().insert(p -> p.setUserUuid(user.getUuid()), p -> p.setAlmSettingUuid(almSetting.getUuid()));
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "newtoken")
+ .execute();
+
+ Optional<AlmPatDto> actualAlmPat = db.getDbClient().almPatDao().selectByUserAndAlmSetting(db.getSession(), user.getUuid(), almSetting);
+ assertThat(actualAlmPat).isPresent();
+ assertThat(actualAlmPat.get().getPersonalAccessToken()).isEqualTo("newtoken");
+ assertThat(actualAlmPat.get().getUserUuid()).isEqualTo(user.getUuid());
+ assertThat(actualAlmPat.get().getAlmSettingUuid()).isEqualTo(almSetting.getUuid());
+ }
+
+ @Test
+ public void fail_when_alm_setting_unknow() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("ALM Setting 'notExistingKey' not found");
+
+ ws.newRequest()
+ .setParam("almSetting", "notExistingKey")
+ .setParam("pat", "12345678987654321")
+ .execute();
+ }
+
+ @Test
+ public void fail_when_alm_setting_not_bitbucket_server_nor_gitlab() {
+ UserDto user = db.users().insertUser();
+ AlmSettingDto almSetting = db.almSettings().insertGitHubAlmSetting();
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Only Azure DevOps, Bibucket Server and GitLab ALM Settings are supported.");
+
+ ws.newRequest()
+ .setParam("almSetting", almSetting.getKey())
+ .setParam("pat", "12345678987654321")
+ .execute();
+ }
+
+ @Test
+ public void fail_when_not_logged_in() {
+ expectedException.expect(UnauthorizedException.class);
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void fail_when_no_creation_project_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ ws.newRequest().execute();
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action def = ws.getDef();
+
+ assertThat(def.since()).isEqualTo("8.2");
+ assertThat(def.isPost()).isTrue();
+ assertThat(def.params())
+ .extracting(WebService.Param::key, WebService.Param::isRequired)
+ .containsExactlyInAnyOrder(tuple("almSetting", true), tuple("pat", true));
+ }
+
+}