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;
assertThat(scimUserDtos).hasSize(2)
.map(scimUserDto -> new ScimUserTestData(scimUserDto.getScimUserUuid(), scimUserDto.getUserUuid()))
.containsExactlyInAnyOrder(scimUser1TestData, scimUser2TestData);
-
}
@Test
{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()},
};
}
@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
@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);
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");
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) {
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;
}
*/
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;
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);
}
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);
}
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;
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
return userName;
}
+ @CheckForNull
+ public Set<String> getScimUserUuids() {
+ return scimUserUuids;
+ }
+
+ @CheckForNull
+ public Set<String> getUserUuids() {
+ return userUuids;
+ }
+
public static ScimUserQuery empty() {
return builder().build();
}
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);
+ }
}
}
/**
* 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);
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>
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;
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);
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());
}