]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21119 Add method to get Gitlab group members
authorAntoine Vigneau <antoine.vigneau@sonarsource.com>
Fri, 1 Dec 2023 09:59:40 +0000 (10:59 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 22 Dec 2023 20:03:02 +0000 (20:03 +0000)
server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java
server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java
server/sonar-alm-client/src/test/resources/org/sonar/alm/client/gitlab/group-members-full-response.json [new file with mode: 0644]

index 13088aec6e7a1029f0005e612b8090bbafd5e9f6..cb260fcec334e76dd3c776eacfc178716af934c0 100644 (file)
@@ -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);
index 4ec9f5d33fe17680702490e54a735d9ed87ed944..a16c0bfc79b75b14cadb0db60e22e3a96f6f1c2d 100644 (file)
@@ -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 (file)
index 0000000..826e30d
--- /dev/null
@@ -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"
+  }
+]