diff options
author | Antoine Vigneau <antoine.vigneau@sonarsource.com> | 2023-12-01 10:59:40 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-22 20:03:02 +0000 |
commit | 480e883a21bd361dfa8233c950535603385d3e62 (patch) | |
tree | 9c139c5cac33e7946d2cfe1a138826aef26d486d | |
parent | 68de595dc688e9fd09bd8925c94d0bba830c3869 (diff) | |
download | sonarqube-480e883a21bd361dfa8233c950535603385d3e62.tar.gz sonarqube-480e883a21bd361dfa8233c950535603385d3e62.zip |
SONAR-21119 Add method to get Gitlab group members
3 files changed, 133 insertions, 21 deletions
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 13088aec6e7..cb260fcec33 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 @@ -45,9 +45,11 @@ 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.GsonUser; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.client.OkHttpClientBuilder; +import static java.lang.String.format; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.nio.charset.StandardCharsets.UTF_8; @@ -57,6 +59,7 @@ public class GitlabApplicationClient { private static final Logger LOG = LoggerFactory.getLogger(GitlabApplicationClient.class); private static final Gson GSON = new Gson(); private static final Type GITLAB_GROUP = TypeToken.getParameterized(List.class, GsonGroup.class).getType(); + private static final Type GITLAB_USER = TypeToken.getParameterized(List.class, GsonUser.class).getType(); protected static final String PRIVATE_TOKEN = "Private-Token"; protected final OkHttpClient client; @@ -81,9 +84,9 @@ public class GitlabApplicationClient { } private void checkProjectAccess(@Nullable String gitlabUrl, @Nullable String personalAccessToken, String errorMessage) { - String url = String.format("%s/projects", gitlabUrl); + String url = format("%s/projects", gitlabUrl); - LOG.debug(String.format("get projects : [%s]", url)); + LOG.debug("get projects : [{}]", url); Request.Builder builder = new Request.Builder() .url(url) .get(); @@ -106,13 +109,14 @@ public class GitlabApplicationClient { } private static void logException(String url, IOException e) { - LOG.info(String.format("Gitlab API call to [%s] failed with error message : [%s]", url, e.getMessage()), e); + String errorMessage = format("Gitlab API call to [%s] failed with error message : [%s]", url, e.getMessage()); + LOG.info(errorMessage, e); } public void checkToken(String gitlabUrl, String personalAccessToken) { - String url = String.format("%s/user", gitlabUrl); + String url = format("%s/user", gitlabUrl); - LOG.debug(String.format("get current user : [%s]", url)); + LOG.debug("get current user : [{}]", url); Request.Builder builder = new Request.Builder() .addHeader(PRIVATE_TOKEN, personalAccessToken) .url(url) @@ -133,9 +137,9 @@ public class GitlabApplicationClient { } public void checkWritePermission(String gitlabUrl, String personalAccessToken) { - String url = String.format("%s/markdown", gitlabUrl); + String url = format("%s/markdown", gitlabUrl); - LOG.debug(String.format("verify write permission by formating some markdown : [%s]", url)); + LOG.debug("verify write permission by formating some markdown : [{}]", url); Request.Builder builder = new Request.Builder() .url(url) .addHeader(PRIVATE_TOKEN, personalAccessToken) @@ -172,7 +176,7 @@ public class GitlabApplicationClient { protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException { if (!response.isSuccessful()) { 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)); + LOG.error("Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]", response.request().url(), response.code(), body); if (isTokenRevoked(response, body)) { throw new GitlabServerException(response.code(), "Your GitLab token was revoked"); } else if (isTokenExpired(response, body)) { @@ -226,8 +230,8 @@ public class GitlabApplicationClient { } public Project getProject(String gitlabUrl, String pat, Long gitlabProjectId) { - String url = String.format("%s/projects/%s", gitlabUrl, gitlabProjectId); - LOG.debug(String.format("get project : [%s]", url)); + String url = format("%s/projects/%s", gitlabUrl, gitlabProjectId); + LOG.debug("get project : [{}]", url); Request request = new Request.Builder() .addHeader(PRIVATE_TOKEN, pat) .get() @@ -237,7 +241,7 @@ public class GitlabApplicationClient { try (Response response = client.newCall(request).execute()) { checkResponseIsSuccessful(response); String body = response.body().string(); - LOG.trace(String.format("loading project payload result : [%s]", body)); + LOG.trace("loading project payload result : [{}]", body); return new GsonBuilder().create().fromJson(body, Project.class); } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Could not parse GitLab answer to retrieve a project. Got a non-json payload as result."); @@ -252,9 +256,9 @@ public class GitlabApplicationClient { // As of June 9, 2021 there is no better way to do this check and still support GitLab 11.7. // public Optional<Project> getReporterLevelAccessProject(String gitlabUrl, String pat, Long gitlabProjectId) { - String url = String.format("%s/projects?min_access_level=20&id_after=%s&id_before=%s", gitlabUrl, gitlabProjectId - 1, + String url = format("%s/projects?min_access_level=20&id_after=%s&id_before=%s", gitlabUrl, gitlabProjectId - 1, gitlabProjectId + 1); - LOG.debug(String.format("get project : [%s]", url)); + LOG.debug("get project : [{}]", url); Request request = new Request.Builder() .addHeader(PRIVATE_TOKEN, pat) .get() @@ -264,7 +268,7 @@ public class GitlabApplicationClient { try (Response response = client.newCall(request).execute()) { checkResponseIsSuccessful(response); String body = response.body().string(); - LOG.trace(String.format("loading project payload result : [%s]", body)); + LOG.trace("loading project payload result : [{}]", body); List<Project> projects = Project.parseJsonArray(body); if (projects.isEmpty()) { @@ -281,8 +285,8 @@ public class GitlabApplicationClient { } public List<GitLabBranch> getBranches(String gitlabUrl, String pat, Long gitlabProjectId) { - String url = String.format("%s/projects/%s/repository/branches", gitlabUrl, gitlabProjectId); - LOG.debug(String.format("get branches : [%s]", url)); + String url = format("%s/projects/%s/repository/branches", gitlabUrl, gitlabProjectId); + LOG.debug("get branches : [{}]", url); Request request = new Request.Builder() .addHeader(PRIVATE_TOKEN, pat) .get() @@ -292,7 +296,7 @@ public class GitlabApplicationClient { try (Response response = client.newCall(request).execute()) { checkResponseIsSuccessful(response); String body = response.body().string(); - LOG.trace(String.format("loading branches payload result : [%s]", body)); + LOG.trace("loading branches payload result : [{}]", body); return Arrays.asList(new GsonBuilder().create().fromJson(body, GitLabBranch[].class)); } catch (JsonSyntaxException e) { throw new IllegalArgumentException("Could not parse GitLab answer to retrieve project branches. Got a non-json payload as result."); @@ -304,14 +308,14 @@ public class GitlabApplicationClient { public ProjectList searchProjects(String gitlabUrl, String personalAccessToken, @Nullable String projectName, @Nullable Integer pageNumber, @Nullable Integer pageSize) { - String url = String.format("%s/projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=%s%s%s", + String url = format("%s/projects?archived=false&simple=true&membership=true&order_by=name&sort=asc&search=%s%s%s", gitlabUrl, projectName == null ? "" : urlEncode(projectName), - pageNumber == null ? "" : String.format("&page=%d", pageNumber), - pageSize == null ? "" : String.format("&per_page=%d", pageSize) + pageNumber == null ? "" : format("&page=%d", pageNumber), + pageSize == null ? "" : format("&per_page=%d", pageSize) ); - LOG.debug(String.format("get projects : [%s]", url)); + LOG.debug("get projects : [{}]", url); Request request = new Request.Builder() .addHeader(PRIVATE_TOKEN, personalAccessToken) .url(url) @@ -351,6 +355,10 @@ public class GitlabApplicationClient { return Set.copyOf(executePaginatedQuery(gitlabUrl, token, "/groups", resp -> GSON.fromJson(resp, GITLAB_GROUP))); } + public Set<GsonUser> getGroupMembers(String gitlabUrl, String token, String groupId) { + return Set.copyOf(executePaginatedQuery(gitlabUrl, token, format("/groups/%s/members", groupId), resp -> GSON.fromJson(resp, GITLAB_USER))); + } + private <E> List<E> executePaginatedQuery(String appUrl, String token, String query, Function<String, List<E>> 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 4ec9f5d33fe..a16c0bfc79b 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.GsonUser; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -582,6 +583,50 @@ public class GitlabApplicationClientTest { return gsonGroup; } + @Test + public void getGroupMembers_whenCallIsInError_rethrows() throws IOException { + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/groups/42/members"), any())).thenThrow(new IllegalStateException("exception")); + + assertThatIllegalStateException() + .isThrownBy(() -> underTest.getGroupMembers(gitlabUrl, token, "42")) + .withMessage("exception"); + } + + @Test + public void getGroupMembers_whenCallIsSuccessful_deserializesAndReturnsCorrectlyGroupMembers() throws IOException { + ArgumentCaptor<Function<String, List<GsonUser>>> deserializerCaptor = ArgumentCaptor.forClass(Function.class); + + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + List<GsonUser> expectedGroupMembers = expectedGroupMembers(); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/groups/42/members"), deserializerCaptor.capture())).thenReturn(expectedGroupMembers); + + Set<GsonUser> actualGroupMembers = underTest.getGroupMembers(gitlabUrl, token, "42"); + assertThat(actualGroupMembers).containsExactlyInAnyOrderElementsOf(expectedGroupMembers); + + String responseContent = getResponseContent("group-members-full-response.json"); + + List<GsonUser> deserializedUsers = deserializerCaptor.getValue().apply(responseContent); + assertThat(deserializedUsers).usingRecursiveComparison().isEqualTo(expectedGroupMembers); + } + + private static List<GsonUser> expectedGroupMembers() { + GsonUser user1 = createGsonUser(12818153L, "aurelien-poscia-sonarsource", "Aurelien"); + GsonUser user2 = createGsonUser(10941672L, "antoine.vigneau", "Antoine Vigneau"); + GsonUser user3 = createGsonUser(13569073L, "wojciech.wajerowicz.sonarsource", "Wojciech Wajerowicz"); + return List.of(user1, user2, user3); + } + + private static GsonUser createGsonUser(Long id, String username, String name) { + GsonUser gsonUser = mock(); + when(gsonUser.getId()).thenReturn(id); + when(gsonUser.getUsername()).thenReturn(username); + when(gsonUser.getName()).thenReturn(name); + return gsonUser; + } + 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/group-members-full-response.json b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/group-members-full-response.json new file mode 100644 index 00000000000..826e30dda1e --- /dev/null +++ b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/group-members-full-response.json @@ -0,0 +1,59 @@ +[ + { + "access_level": 50, + "created_at": "2023-11-28T08:01:20.708Z", + "expires_at": null, + "id": 12818153, + "username": "aurelien-poscia-sonarsource", + "name": "Aurelien", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/33973b7273855e92d004fa52a7217e19?s=80&d=identicon", + "web_url": "https://gitlab.com/aurelien-poscia-sonarsource", + "membership_state": "active" + }, + { + "access_level": 30, + "created_at": "2023-11-28T08:02:16.440Z", + "created_by": { + "id": 12818153, + "username": "aurelien-poscia-sonarsource", + "name": "Aurelien", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/33973b7273855e92d004fa52a7217e19?s=80&d=identicon", + "web_url": "https://gitlab.com/aurelien-poscia-sonarsource" + }, + "expires_at": null, + "id": 10941672, + "username": "antoine.vigneau", + "name": "Antoine Vigneau", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/088cbb2cea18d9a9b1983f9c93735aed?s=80&d=identicon", + "web_url": "https://gitlab.com/antoine.vigneau", + "membership_state": "active" + }, + { + "access_level": 30, + "created_at": "2023-11-28T08:02:16.532Z", + "created_by": { + "id": 12818153, + "username": "aurelien-poscia-sonarsource", + "name": "Aurelien", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/33973b7273855e92d004fa52a7217e19?s=80&d=identicon", + "web_url": "https://gitlab.com/aurelien-poscia-sonarsource" + }, + "expires_at": null, + "id": 13569073, + "username": "wojciech.wajerowicz.sonarsource", + "name": "Wojciech Wajerowicz", + "state": "active", + "locked": false, + "avatar_url": "https://secure.gravatar.com/avatar/48d3b9f2cb2b314c1b0c42c48c8671ad?s=80&d=identicon", + "web_url": "https://gitlab.com/wojciech.wajerowicz.sonarsource", + "membership_state": "active" + } +] |