]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18532 Add support for group membership PUT in /api/scim/v2/Groups
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Tue, 28 Feb 2023 16:08:58 +0000 (17:08 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 22 Mar 2023 20:04:06 +0000 (20:04 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/scim/ScimUserDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/scim/ScimUserDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/scim/ScimUserQuery.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
server/sonar-db-dao/src/main/resources/org/sonar/db/scim/ScimUserMapper.xml
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserDbTester.java

index fb99c012799a3530ca70a8a85d82b8c5201072b0..6f90e777a365cc8bfdee7dece3eba7186cfb4608 100644 (file)
@@ -26,9 +26,9 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -64,7 +64,6 @@ public class ScimUserDaoIT {
     assertThat(scimUserDtos).hasSize(2)
       .map(scimUserDto -> new ScimUserTestData(scimUserDto.getScimUserUuid(), scimUserDto.getUserUuid()))
       .containsExactlyInAnyOrder(scimUser1TestData, scimUser2TestData);
-
   }
 
   @Test
@@ -109,7 +108,7 @@ public class ScimUserDaoIT {
       {9, 3, 3, List.of("4", "5", "6")},
       {9, 7, 3, List.of("8", "9")},
       {5, 5, 20, List.of()},
-      {5, 0, 0, List.of()}
+      {5, 0, 0, List.of()},
     };
   }
 
@@ -154,18 +153,17 @@ public class ScimUserDaoIT {
 
   @Test
   public void countScimUsers_shoudReturnCorrectNumberOfScimUser_whenFilteredByScimUserName() {
-    inserScimUsersWithUsers(List.of("TEST_A", "TEST_B", "TEST_B_BIS", "TEST_C", "TEST_D"));
+    insertScimUsersWithUsers(List.of("TEST_A", "TEST_B", "TEST_B_BIS", "TEST_C", "TEST_D"));
     ScimUserQuery scimUserQuery = ScimUserQuery.builder().userName("test_b").build();
 
     assertThat(scimUserDao.countScimUsers(dbSession, scimUserQuery)).isEqualTo(1);
   }
 
-  private void generateScimUsers(int totalScimUsers) {
-    List<ScimUserTestData> allScimUsers = Stream.iterate(1, i -> i + 1)
-      .map(i -> insertScimUser(i.toString()))
-      .limit(totalScimUsers)
+  private List<ScimUserTestData> generateScimUsers(int totalScimUsers) {
+    List<String> userNames = IntStream.range(0, totalScimUsers)
+      .mapToObj(i -> "username_" + i)
       .collect(Collectors.toList());
-    assertThat(allScimUsers).hasSize(totalScimUsers);
+    return insertScimUsersWithUsers(userNames);
   }
 
   @Test
@@ -191,7 +189,7 @@ public class ScimUserDaoIT {
   @Test
   @UseDataProvider("filterData")
   public void findScimUsers_whenFilteringByUserName_shouldReturnTheExpectedScimUsers(String search, List<String> userLogins, List<String> expectedScimUserUuids) {
-    inserScimUsersWithUsers(userLogins);
+    insertScimUsersWithUsers(userLogins);
     ScimUserQuery query = ScimUserQuery.builder().userName(search).build();
 
     List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, 100);
@@ -200,6 +198,81 @@ public class ScimUserDaoIT {
     assertThat(scimUsersUuids).containsExactlyElementsOf(expectedScimUserUuids);
   }
 
+  @Test
+  public void findScimUsers_whenFilteringByScimUuidsWithLongRange_shouldReturnTheExpectedScimUsers() {
+    generateScimUsers(3000);
+    Set<String> expectedScimUserUuids = generateStrings(1, 2050);
+
+    ScimUserQuery query = ScimUserQuery.builder().scimUserUuids(expectedScimUserUuids).build();
+
+    List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+
+    List<String> scimUsersUuids = toScimUsersUuids(scimUsersByQuery);
+    assertThat(scimUsersByQuery)
+      .hasSameSizeAs(scimUsersUuids)
+      .extracting(ScimUserDto::getScimUserUuid)
+      .containsAll(expectedScimUserUuids);
+  }
+
+  @Test
+  public void findScimUsers_whenFilteringByScimUuidsAndUserName_shouldReturnTheExpectedScimUser() {
+    Set<String> scimUserUuids = generateScimUsers(10).stream()
+      .map(ScimUserTestData::getScimUserUuid)
+      .collect(Collectors.toSet());
+
+    ScimUserQuery query = ScimUserQuery.builder().scimUserUuids(scimUserUuids).userName("username_5").build();
+
+    List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+
+    assertThat(scimUsersByQuery).hasSize(1)
+      .extracting(ScimUserDto::getScimUserUuid)
+      .contains("6");
+  }
+
+  @Test
+  public void findScimUsers_whenFilteringByUserUuidsWithLongRange_shouldReturnTheExpectedScimUsers() {
+    List<ScimUserTestData> scimUsersTestData = generateScimUsers(3000);
+    Set<String> allUsersUuid = scimUsersTestData.stream()
+      .map(ScimUserTestData::getUserUuid)
+      .collect(Collectors.toSet());
+
+    ScimUserQuery query = ScimUserQuery.builder().userUuids(allUsersUuid).build();
+
+    List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+
+    assertThat(scimUsersByQuery)
+      .hasSameSizeAs(allUsersUuid)
+      .extracting(ScimUserDto::getUserUuid)
+      .containsAll(allUsersUuid);
+  }
+
+  @Test
+  public void findScimUsers_whenFilteringByUserUuidsAndUserName_shouldReturnTheExpectedScimUser() {
+    List<ScimUserTestData> scimUsersTestData = generateScimUsers(10);
+    Set<String> allUsersUuid = scimUsersTestData.stream()
+      .map(ScimUserTestData::getUserUuid)
+      .collect(Collectors.toSet());
+
+    ScimUserQuery query = ScimUserQuery.builder().userUuids(allUsersUuid).userName("username_5").build();
+
+    List<ScimUserDto> scimUsersByQuery = scimUserDao.findScimUsers(dbSession, query, 0, Integer.MAX_VALUE);
+
+    assertThat(scimUsersByQuery).hasSize(1)
+      .extracting(ScimUserDto::getScimUserUuid)
+      .contains("6");
+  }
+
+  private static Set<String> generateStrings(int startInclusive, int endExclusive) {
+    return generateStrings(startInclusive, endExclusive, "");
+  }
+
+  private static Set<String> generateStrings(int startInclusive, int endExclusive, String prefix) {
+    return IntStream.range(startInclusive, endExclusive)
+      .mapToObj(String::valueOf)
+      .map(string -> prefix + string)
+      .collect(Collectors.toSet());
+  }
+
   @Test
   public void deleteByUserUuid_shouldDeleteScimUser() {
     ScimUserTestData scimUserTestData = insertScimUser("scimUser");
@@ -229,14 +302,15 @@ public class ScimUserDaoIT {
     assertThatCode(() -> scimUserDao.deleteByUserUuid(dbSession, randomAlphanumeric(6))).doesNotThrowAnyException();
   }
 
-  private void inserScimUsersWithUsers(List<String> userLogins) {
-    IntStream.range(0, userLogins.size())
-      .forEachOrdered(i -> insertScimUserWithUser(userLogins.get(i), String.valueOf(i + 1)));
+  private List<ScimUserTestData> insertScimUsersWithUsers(List<String> userLogins) {
+    return IntStream.range(0, userLogins.size())
+      .mapToObj(i -> insertScimUserWithUser(userLogins.get(i), String.valueOf(i + 1)))
+      .collect(Collectors.toList());
   }
 
-  private void insertScimUserWithUser(String userLogin, String scimUuid) {
+  private ScimUserTestData insertScimUserWithUser(String userLogin, String scimUuid) {
     UserDto userDto = db.users().insertUser(u -> u.setExternalId(userLogin));
-    insertScimUser(scimUuid, userDto.getUuid());
+    return insertScimUser(scimUuid, userDto.getUuid());
   }
 
   private ScimUserTestData insertScimUser(String scimUserUuid) {
@@ -247,7 +321,6 @@ public class ScimUserDaoIT {
     ScimUserTestData scimUserTestData = new ScimUserTestData(scimUserUuid, userUuid);
     Map<String, Object> data = Map.of("scim_uuid", scimUserTestData.getScimUserUuid(), "user_uuid", scimUserTestData.getUserUuid());
     db.executeInsert("scim_users", data);
-
     return scimUserTestData;
   }
 
index 9476e7d842d88c585fd78699875317ade650d668..d2a9fb25da19a26e375fc89bccde5c31fbc20381 100644 (file)
  */
 package org.sonar.db.scim;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
-import org.sonar.core.util.UuidFactory;
+import java.util.function.BiFunction;
 import org.apache.ibatis.session.RowBounds;
+import org.sonar.core.util.UuidFactory;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 
+import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+
 public class ScimUserDao implements Dao {
   private final UuidFactory uuidFactory;
 
@@ -45,6 +50,7 @@ public class ScimUserDao implements Dao {
     return Optional.ofNullable(mapper(dbSession).findByUserUuid(userUuid));
   }
 
+
   public ScimUserDto enableScimForUser(DbSession dbSession, String userUuid) {
     ScimUserDto scimUserDto = new ScimUserDto(uuidFactory.create(), userUuid);
     mapper(dbSession).insert(scimUserDto);
@@ -52,9 +58,37 @@ public class ScimUserDao implements Dao {
   }
 
   public List<ScimUserDto> findScimUsers(DbSession dbSession, ScimUserQuery scimUserQuery, int offset, int limit) {
+    checkState(scimUserQuery.getUserUuids() == null || scimUserQuery.getScimUserUuids() == null,
+      "Only one of userUuids & scimUserUuids request parameter is supported.");
+    if (scimUserQuery.getScimUserUuids() != null) {
+      return executeLargeInputs(
+        scimUserQuery.getScimUserUuids(),
+        partialSetOfUsers -> createPartialQuery(scimUserQuery, partialSetOfUsers,
+          (builder, scimUserUuids) -> builder.scimUserUuids(new HashSet<>(scimUserUuids)),
+          dbSession, offset, limit)
+      );
+    }
+    if (scimUserQuery.getUserUuids() != null) {
+      return executeLargeInputs(
+        scimUserQuery.getUserUuids(),
+        partialSetOfUsers -> createPartialQuery(scimUserQuery, partialSetOfUsers,
+          (builder, userUuids) -> builder.userUuids(new HashSet<>(userUuids)),
+          dbSession, offset, limit)
+      );
+    }
     return mapper(dbSession).findScimUsers(scimUserQuery, new RowBounds(offset, limit));
   }
 
+  private static List<ScimUserDto> createPartialQuery(ScimUserQuery completeQuery, List<String> strings,
+    BiFunction<ScimUserQuery.ScimUserQueryBuilder, List<String>, ScimUserQuery.ScimUserQueryBuilder> queryModifier,
+    DbSession dbSession, int offset, int limit) {
+
+    ScimUserQuery.ScimUserQueryBuilder partialScimUserQuery = ScimUserQuery.builder()
+      .userName(completeQuery.getUserName());
+    partialScimUserQuery = queryModifier.apply(partialScimUserQuery, strings);
+    return mapper(dbSession).findScimUsers(partialScimUserQuery.build(), new RowBounds(offset, limit));
+  }
+
   public int countScimUsers(DbSession dbSession, ScimUserQuery scimUserQuery) {
     return mapper(dbSession).countScimUsers(scimUserQuery);
   }
index 35908f2ec149d706359a20c9e15f27a45b135e17..99ba5e05211829d0fb26c93d1818c2fedc2abae0 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.scim;
 
 import java.util.Optional;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.annotation.CheckForNull;
@@ -33,9 +34,13 @@ public class ScimUserQuery {
   private static final String UNSUPPORTED_FILTER = "Unsupported filter value: %s. Format should be 'userName eq \"username\"'";
 
   private final String userName;
+  private final Set<String> scimUserUuids;
+  private final Set<String> userUuids;
 
-  private ScimUserQuery(String userName) {
+  private ScimUserQuery(@Nullable String userName, @Nullable Set<String> scimUserUuids, @Nullable Set<String> userUuids) {
     this.userName = userName;
+    this.scimUserUuids = scimUserUuids;
+    this.userUuids = userUuids;
   }
 
   @CheckForNull
@@ -43,6 +48,16 @@ public class ScimUserQuery {
     return userName;
   }
 
+  @CheckForNull
+  public Set<String> getScimUserUuids() {
+    return scimUserUuids;
+  }
+
+  @CheckForNull
+  public Set<String> getUserUuids() {
+    return userUuids;
+  }
+
   public static ScimUserQuery empty() {
     return builder().build();
   }
@@ -72,18 +87,30 @@ public class ScimUserQuery {
   public static final class ScimUserQueryBuilder {
 
     private String userName;
+    private Set<String> scimUserUuids;
+    private Set<String> userUuids;
 
     private ScimUserQueryBuilder() {
     }
 
-    public ScimUserQueryBuilder userName(String userName) {
+    public ScimUserQueryBuilder userName(@Nullable String userName) {
       this.userName = userName;
       return this;
     }
 
-    public ScimUserQuery build() {
-      return new ScimUserQuery(userName);
+
+    public ScimUserQueryBuilder scimUserUuids(Set<String> scimUserUuids) {
+      this.scimUserUuids = scimUserUuids;
+      return this;
     }
 
+    public ScimUserQueryBuilder userUuids(Set<String> userUuids) {
+      this.userUuids = userUuids;
+      return this;
+    }
+
+    public ScimUserQuery build() {
+      return new ScimUserQuery(userName, scimUserUuids, userUuids);
+    }
   }
 }
index 8387c648d9be0ef52829aedbd8be478ac1e8fecd..1949fff807108009904c20ad12530961302cd9f7 100644 (file)
@@ -78,6 +78,7 @@ public class UserDao implements Dao {
   /**
    * Select users by uuids, including disabled users. An empty list is returned
    * if list of uuids is empty, without any db round trips.
+   * @return
    */
   public List<UserDto> selectByUuids(DbSession session, Collection<String> uuids) {
     return executeLargeInputs(uuids, mapper(session)::selectByUuids);
index 734cbb15a262968f375947cfa076444f786085de..a973a535c6bb1359d6d2c86ce9ddaf0322023e0e 100644 (file)
       scim_uuid = #{scimUserUuid,jdbcType=VARCHAR}
   </select>
 
+  <select id="selectByLogins" parameterType="string" resultType="User">
+    select
+    <include refid="scimUsersColumns"/>
+      from scim_users
+    where
+      scim_uuid in
+        <foreach collection="list" open="(" close=")" item="login" separator=",">
+            #{scimUserUuid, jdbcType=VARCHAR}
+        </foreach>
+  </select>
+
   <select id="findByUserUuid" parameterType="String" resultType="org.sonar.db.scim.ScimUserDto">
     select
     <include refid="scimUsersColumns"/>
     )
   </insert>
 
-  <select id="findScimUsers" resultType="org.sonar.db.scim.ScimUserDto">
+  <select id="findScimUsers" parameterType="map" resultType="org.sonar.db.scim.ScimUserDto">
     select
     <include refid="scimUsersColumns"/>
     <include refid="sqlSelectByQuery"/>
     order by s.scim_uuid asc
   </select>
 
-  <select id="countScimUsers" resultType="int">
+  <select id="countScimUsers" parameterType="map" resultType="int">
     select count(1)
     <include refid="sqlSelectByQuery"/>
   </select>
 
   <sql id="sqlSelectByQuery">
     from scim_users s
+    inner join users u on u.uuid=s.user_uuid
+    where 1=1
     <if test="query.userName != null">
-      inner join users u on u.uuid=s.user_uuid
-        where lower(u.external_id) like lower(#{query.userName,jdbcType=VARCHAR}) escape '/'
+        and lower(u.external_id) like lower(#{query.userName,jdbcType=VARCHAR}) escape '/'
+    </if>
+    <if test="query.scimUserUuids != null">
+        and s.scim_uuid in
+        <foreach collection="query.scimUserUuids" open="(" close=")" item="scimUserUuid" separator=",">
+            #{scimUserUuid, jdbcType=VARCHAR}
+        </foreach>
+    </if>
+    <if test="query.userUuids != null">
+        and s.user_uuid in
+        <foreach collection="query.userUuids" open="(" close=")" item="userUuid" separator=",">
+            #{userUuid, jdbcType=VARCHAR}
+        </foreach>
     </if>
   </sql>
 
index 190d02994781b60770beda38bf64bfe6d30a3799..40f77ebcecd8245faeef674ef4e7dbaa47ae7b76 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.db.permission.GroupPermissionDto;
 import org.sonar.db.permission.UserPermissionDto;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.scim.ScimGroupDto;
+import org.sonar.db.scim.ScimUserDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Arrays.stream;
@@ -89,6 +90,12 @@ public class UserDbTester {
     return updatedUser;
   }
 
+  public ScimUserDto enableScimForUser(UserDto userDto) {
+    ScimUserDto scimUSerDto = db.getDbClient().scimUserDao().enableScimForUser(db.getSession(), userDto.getUuid());
+    db.commit();
+    return scimUSerDto;
+  }
+
   public UserDto insertAdminByUserPermission() {
     UserDto user = insertUser();
     insertGlobalPermissionOnUser(user, ADMINISTER);
@@ -175,6 +182,11 @@ public class UserDbTester {
     db.commit();
   }
 
+  public List<UserDto> findMembers(GroupDto group) {
+    Set<String> userUuidsInGroup = db.getDbClient().userGroupDao().selectUserUuidsInGroup(db.getSession(), group.getUuid());
+    return db.getDbClient().userDao().selectByUuids(db.getSession(), userUuidsInGroup);
+  }
+
   public List<String> selectGroupUuidsOfUser(UserDto user) {
     return db.getDbClient().groupMembershipDao().selectGroupUuidsByUserUuid(db.getSession(), user.getUuid());
   }