From cac8b706867b5b56813df253516b2431d7f47889 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 1 Jun 2021 12:02:29 +0200 Subject: [PATCH] SONAR-14871 Add Gitlab project binding validation --- .../sonar/alm/client/gitlab/AccessLevel.java | 42 +++++++++++++++ .../alm/client/gitlab/GitlabHttpClient.java | 16 +++--- .../client/gitlab/GitlabServerException.java | 34 ++++++++++++ .../sonar/alm/client/gitlab/Permissions.java | 52 +++++++++++++++++++ .../alm/client/gitlab/ProjectDetails.java | 49 +++++++++++++++++ .../client/gitlab/GitlabHttpClientTest.java | 39 ++++++++++++++ .../gitlab/ImportGitLabProjectActionTest.java | 11 ++-- 7 files changed, 230 insertions(+), 13 deletions(-) create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/AccessLevel.java create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabServerException.java create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/Permissions.java create mode 100644 server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/ProjectDetails.java diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/AccessLevel.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/AccessLevel.java new file mode 100644 index 00000000000..4e8d43b0b8a --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/AccessLevel.java @@ -0,0 +1,42 @@ +/* + * 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.alm.client.gitlab; + +import com.google.gson.annotations.SerializedName; + +public class AccessLevel { + public static final int REPORTER_ACCESS_LEVEL = 20; + + @SerializedName("access_level") + private int level; + + public AccessLevel() { + // http://stackoverflow.com/a/18645370/229031 + } + + public AccessLevel(int level) { + this.level = level; + } + + public int getLevel() { + return level; + } + +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java index 36011e4e96e..a91a7ad7a4c 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabHttpClient.java @@ -161,17 +161,17 @@ public class GitlabHttpClient { String body = response.body().string(); LOG.error(String.format("Gitlab API call to [%s] failed with %s http code. gitlab response content : [%s]", response.request().url().toString(), response.code(), body)); if (isTokenRevoked(response, body)) { - throw new IllegalArgumentException("Your GitLab token was revoked"); + throw new GitlabServerException(response.code(), "Your GitLab token was revoked"); } else if (isTokenExpired(response, body)) { - throw new IllegalArgumentException("Your GitLab token is expired"); + throw new GitlabServerException(response.code(), "Your GitLab token is expired"); } else if (isInsufficientScope(response, body)) { - throw new IllegalArgumentException("Your GitLab token has insufficient scope"); + throw new GitlabServerException(response.code(), "Your GitLab token has insufficient scope"); } else if (response.code() == HTTP_UNAUTHORIZED) { - throw new IllegalArgumentException("Invalid personal access token"); + throw new GitlabServerException(response.code(), "Invalid personal access token"); } else if (response.isRedirect()) { - throw new IllegalArgumentException("Request was redirected, please provide the correct URL"); + throw new GitlabServerException(response.code(), "Request was redirected, please provide the correct URL"); } else { - throw new IllegalArgumentException(errorMessage); + throw new GitlabServerException(response.code(), errorMessage); } } } @@ -212,7 +212,7 @@ public class GitlabHttpClient { return false; } - public Project getProject(String gitlabUrl, String pat, Long gitlabProjectId) { + public ProjectDetails getProject(String gitlabUrl, String pat, Long gitlabProjectId) { String url = String.format("%s/projects/%s", gitlabUrl, gitlabProjectId); LOG.debug(String.format("get project : [%s]", url)); Request request = new Request.Builder() @@ -225,7 +225,7 @@ public class GitlabHttpClient { checkResponseIsSuccessful(response); String body = response.body().string(); LOG.trace(String.format("loading project payload result : [%s]", body)); - return new GsonBuilder().create().fromJson(body, Project.class); + return new GsonBuilder().create().fromJson(body, ProjectDetails.class); } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Could not parse GitLab answer to retrieve a project. Got a non-json payload as result."); } catch (IOException e) { diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabServerException.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabServerException.java new file mode 100644 index 00000000000..a1f3c0826d3 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabServerException.java @@ -0,0 +1,34 @@ +/* + * 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.alm.client.gitlab; + +public class GitlabServerException extends IllegalArgumentException { + + private final int httpStatus; + + public GitlabServerException(int httpStatus, String clientErrorMessage) { + super(clientErrorMessage); + this.httpStatus = httpStatus; + } + + public int getHttpStatus() { + return httpStatus; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/Permissions.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/Permissions.java new file mode 100644 index 00000000000..a8a0808763d --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/Permissions.java @@ -0,0 +1,52 @@ +/* + * 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.alm.client.gitlab; + +import com.google.gson.annotations.SerializedName; +import javax.annotation.CheckForNull; + +public class Permissions { + // https://docs.gitlab.com/ee/api/projects.html#get-single-project + + @SerializedName("project_access") + private AccessLevel projectAccess; + + @SerializedName("group_access") + private AccessLevel groupAccess; + + public Permissions() { + // http://stackoverflow.com/a/18645370/229031 + } + + public Permissions(AccessLevel projectAccess, AccessLevel groupAccess) { + this.projectAccess = projectAccess; + this.groupAccess = groupAccess; + } + + @CheckForNull + public AccessLevel getProjectAccess() { + return projectAccess; + } + + @CheckForNull + public AccessLevel getGroupAccess() { + return groupAccess; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/ProjectDetails.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/ProjectDetails.java new file mode 100644 index 00000000000..ce73db54dea --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/ProjectDetails.java @@ -0,0 +1,49 @@ +/* + * 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.alm.client.gitlab; + +public class ProjectDetails extends Project { + // https://docs.gitlab.com/ee/api/projects.html#get-single-project + + private Permissions permissions; + + public ProjectDetails(String name, String pathWithNamespace) { + super(name, pathWithNamespace); + } + + public ProjectDetails() { + // http://stackoverflow.com/a/18645370/229031 + this(0, "", "", "", "", ""); + } + + public ProjectDetails(long id, String name, String nameWithNamespace, String path, String pathWithNamespace, + String webUrl) { + super(id, name, nameWithNamespace, path, pathWithNamespace, webUrl); + } + + public Permissions getPermissions() { + return permissions; + } + + public ProjectDetails setPermissions(Permissions permissions) { + this.permissions = permissions; + return this; + } +} diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java index 228d21a2349..9869ec4f28d 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java @@ -283,6 +283,45 @@ public class GitlabHttpClientTest { assertThat(projectGitlabRequest.getMethod()).isEqualTo("GET"); } + @Test + public void get_project_details() throws InterruptedException { + MockResponse projectDetails = new MockResponse() + .setResponseCode(200) + .setBody("{" + + " \"id\": 1234," + + " \"name\": \"SonarQube example 2\"," + + " \"name_with_namespace\": \"SonarSource / SonarQube / SonarQube example 2\"," + + " \"path\": \"sonarqube-example-2\"," + + " \"path_with_namespace\": \"sonarsource/sonarqube/sonarqube-example-2\"," + + " \"web_url\": \"https://example.gitlab.com/sonarsource/sonarqube/sonarqube-example-2\"," + + " \"permissions\": {" + + " \"project_access\": {" + + " \"access_level\": 50," + + " \"notification_level\": 3" + + " }," + + " \"group_access\": {" + + " \"access_level\": 10," + + " \"notification_level\": 3" + + " }" + + " }" + + "}"); + + server.enqueue(projectDetails); + + ProjectDetails project = underTest.getProject(gitlabUrl, "pat", 1234L); + + RecordedRequest projectGitlabRequest = server.takeRequest(10, TimeUnit.SECONDS); + String gitlabUrlCall = projectGitlabRequest.getRequestUrl().toString(); + + assertThat(project).isNotNull(); + assertThat(project.getPermissions().getProjectAccess().getLevel()).isEqualTo(50); + assertThat(project.getPermissions().getGroupAccess().getLevel()).isEqualTo(10); + + assertThat(gitlabUrlCall).isEqualTo( + server.url("") + "projects/1234"); + assertThat(projectGitlabRequest.getMethod()).isEqualTo("GET"); + } + @Test public void search_projects_fail_if_could_not_parse_pagination_number() { MockResponse projects = new MockResponse() diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java index 9ad81f6beda..d60cdc24580 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/gitlab/ImportGitLabProjectActionTest.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.sonar.alm.client.gitlab.GitLabBranch; import org.sonar.alm.client.gitlab.GitlabHttpClient; import org.sonar.alm.client.gitlab.Project; +import org.sonar.alm.client.gitlab.ProjectDetails; import org.sonar.api.utils.System2; import org.sonar.core.i18n.I18n; import org.sonar.core.util.SequenceUuidFactory; @@ -97,7 +98,7 @@ public class ImportGitLabProjectActionTest { dto.setUserUuid(user.getUuid()); dto.setPersonalAccessToken("PAT"); }); - Project project = getGitlabProject(); + ProjectDetails project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("master", true))); when(uuidFactory.create()).thenReturn("uuid"); @@ -128,7 +129,7 @@ public class ImportGitLabProjectActionTest { dto.setUserUuid(user.getUuid()); dto.setPersonalAccessToken("PAT"); }); - Project project = getGitlabProject(); + ProjectDetails project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(singletonList(new GitLabBranch("main", true))); when(uuidFactory.create()).thenReturn("uuid"); @@ -164,7 +165,7 @@ public class ImportGitLabProjectActionTest { dto.setUserUuid(user.getUuid()); dto.setPersonalAccessToken("PAT"); }); - Project project = getGitlabProject(); + ProjectDetails project = getGitlabProject(); when(gitlabHttpClient.getProject(any(), any(), any())).thenReturn(project); when(gitlabHttpClient.getBranches(any(), any(), any())).thenReturn(emptyList()); when(uuidFactory.create()).thenReturn("uuid"); @@ -221,7 +222,7 @@ public class ImportGitLabProjectActionTest { assertThat(importGitLabProjectAction.generateProjectKey(name, "uuid")).isEqualTo("a_b_c_uuid"); } - private Project getGitlabProject() { - return new Project(randomAlphanumeric(5), randomAlphanumeric(5)); + private ProjectDetails getGitlabProject() { + return new ProjectDetails(randomAlphanumeric(5), randomAlphanumeric(5)); } } -- 2.39.5