From c2c94079901598f28301fffb1d65d4d27974e5a7 Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Tue, 6 Aug 2024 15:47:32 +0200 Subject: SONAR-22559 Add method to fetch projects' members from GitLab --- .../alm/client/gitlab/GitlabApplicationClient.java | 10 ++++-- .../client/gitlab/GitlabApplicationClientTest.java | 42 ++++++++++++++++++++++ .../gitlab/project-members-full-response.json | 37 +++++++++++++++++++ .../org/sonar/auth/gitlab/GsonProjectMember.java | 30 ++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/project-members-full-response.json create mode 100644 server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GsonProjectMember.java diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java index 4db9851c99d..47178442d52 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java @@ -26,7 +26,6 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; import java.net.URLEncoder; import java.util.Arrays; import java.util.List; @@ -45,6 +44,7 @@ import org.slf4j.LoggerFactory; import org.sonar.alm.client.TimeoutConfiguration; import org.sonar.api.server.ServerSide; import org.sonar.auth.gitlab.GsonGroup; +import org.sonar.auth.gitlab.GsonProjectMember; import org.sonar.auth.gitlab.GsonUser; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.client.OkHttpClientBuilder; @@ -62,6 +62,8 @@ public class GitlabApplicationClient { }; private static final TypeToken> GITLAB_USER = new TypeToken<>() { }; + private static final TypeToken> GITLAB_PROJECT_MEMBER = new TypeToken<>() { + }; protected static final String PRIVATE_TOKEN = "Private-Token"; private static final String GITLAB_GROUPS_MEMBERS_ENDPOINT = "/groups/%s/members"; @@ -370,9 +372,13 @@ public class GitlabApplicationClient { return Set.copyOf(executePaginatedQuery(gitlabUrl, token, endpoint, resp -> GSON.fromJson(resp, GITLAB_USER))); } + public Set getAllProjectMembers(String gitlabUrl, String token, long projectId) { + String url = format("/projects/%s/members/all", projectId); + return Set.copyOf(executePaginatedQuery(gitlabUrl, token, url, resp -> GSON.fromJson(resp, GITLAB_PROJECT_MEMBER))); + } + private List executePaginatedQuery(String appUrl, String token, String query, Function> responseDeserializer) { GitlabToken gitlabToken = new GitlabToken(token); return gitlabPaginatedHttpClient.get(appUrl, gitlabToken, query, responseDeserializer); } - } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java index 37cc5f9defb..afa35587b88 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java @@ -40,6 +40,7 @@ import org.sonar.alm.client.ConstantTimeoutConfiguration; import org.sonar.alm.client.TimeoutConfiguration; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.auth.gitlab.GsonGroup; +import org.sonar.auth.gitlab.GsonProjectMember; import org.sonar.auth.gitlab.GsonUser; import static org.assertj.core.api.Assertions.assertThat; @@ -659,6 +660,47 @@ public class GitlabApplicationClientTest { return gsonUser; } + @Test + public void getAllProjectMembers_whenCallIsSuccesfull_deserializesAndReturnsCorrectlyProjectsMembers() throws IOException { + ArgumentCaptor>> deserializerCaptor = ArgumentCaptor.forClass(Function.class); + + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + List expectedProjectMembers = expectedProjectMembers(); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/projects/42/members/all"), deserializerCaptor.capture())).thenReturn(expectedProjectMembers); + + Set actualProjectMembers = underTest.getAllProjectMembers(gitlabUrl, token, 42); + assertThat(actualProjectMembers).containsExactlyInAnyOrderElementsOf(expectedProjectMembers); + + String responseContent = getResponseContent("project-members-full-response.json"); + + List deserializedProjectMembers = deserializerCaptor.getValue().apply(responseContent); + assertThat(deserializedProjectMembers).isEqualTo(expectedProjectMembers); + + } + + @Test + public void getAllProjectMembers_whenCallIsInError_rethrows() { + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + List expectedProjectMembers = expectedProjectMembers(); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/projects/42/members/all"), any())).thenThrow(new IllegalStateException("exception")); + + assertThatIllegalStateException() + .isThrownBy(() -> underTest.getAllProjectMembers(gitlabUrl, token, 42)) + .withMessage("exception"); + } + + private static List expectedProjectMembers() { + GsonProjectMember user1 = createGsonProjectMember(12818153, 5); + GsonProjectMember user2 = createGsonProjectMember(22330087, 50); + return List.of(user1, user2); + } + + private static GsonProjectMember createGsonProjectMember(int id, int accessLevel) { + return new GsonProjectMember(id, accessLevel); + } + private static String getResponseContent(String path) throws IOException { return IOUtils.toString(GitlabApplicationClientTest.class.getResourceAsStream(path), StandardCharsets.UTF_8); } diff --git a/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/project-members-full-response.json b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/project-members-full-response.json new file mode 100644 index 00000000000..aed1ada7893 --- /dev/null +++ b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/project-members-full-response.json @@ -0,0 +1,37 @@ +[ + { + "id": 12818153, + "username": "aurelien-poscia-sonarsource", + "name": "Aurélien Poscia 2", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/171fd98c95e67a87cb7bd05a42842adebd14818d7e7d6d5c838225e9240e01b0?s=80&d=identicon", + "web_url": "https://gitlab.com/aurelien-poscia-sonarsource", + "access_level": 5, + "created_at": "2023-06-01T08:22:56.677Z", + "created_by": { + "id": 12818153, + "username": "aurelien-poscia-sonarsource", + "name": "Aurélien Poscia 2", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/171fd98c95e67a87cb7bd05a42842adebd14818d7e7d6d5c838225e9240e01b0?s=80&d=identicon", + "web_url": "https://gitlab.com/aurelien-poscia-sonarsource" + }, + "expires_at": null, + "membership_state": "active" + }, + { + "id": 22330087, + "username": "project_46494175_bot_d518a7d5f68ba2e623d8dba785ca8b5f", + "name": "Guillaume Peoch", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/60254275467af5e8861e2d91a3e3a09a5819fafaa00f34813b9ebef61f9bb44b?s=80&d=identicon", + "web_url": "https://gitlab.com/project_46494175_bot_d518a7d5f68ba2e623d8dba785ca8b5f", + "access_level": 50, + "created_at": "2024-08-05T14:38:37.647Z", + "expires_at": "2024-09-04", + "membership_state": "active" + } +] diff --git a/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GsonProjectMember.java b/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GsonProjectMember.java new file mode 100644 index 00000000000..14a5965c667 --- /dev/null +++ b/server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GsonProjectMember.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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.auth.gitlab; + +import com.google.gson.annotations.SerializedName; + +public record GsonProjectMember ( + @SerializedName("id") + long id, + @SerializedName("access_level") + int accessLevel +){ +} -- cgit v1.2.3