return mapper(dbSession).findAll();
}
- public List<ScimGroupDto> findScimGroups(DbSession dbSession, int offset, int limit) {
- return mapper(dbSession).findScimGroups(new RowBounds(offset, limit));
+ public List<ScimGroupDto> findScimGroups(DbSession dbSession, ScimGroupQuery query, int offset, int limit) {
+ return mapper(dbSession).findScimGroups(query, new RowBounds(offset, limit));
}
public Optional<ScimGroupDto> findByScimUuid(DbSession dbSession, String scimGroupUuid) {
return Optional.ofNullable(mapper(dbSession).findByGroupUuid(groupUuid));
}
- public int countScimGroups(DbSession dbSession) {
- return mapper(dbSession).countScimGroups();
+ public int countScimGroups(DbSession dbSession, ScimGroupQuery query) {
+ return mapper(dbSession).countScimGroups(query);
}
public ScimGroupDto enableScimForGroup(DbSession dbSession, String groupUuid) {
@CheckForNull
ScimGroupDto findByGroupUuid(@Param("groupUuid") String groupUuid);
- List<ScimGroupDto> findScimGroups(RowBounds rowBounds);
+ List<ScimGroupDto> findScimGroups(@Param("query") ScimGroupQuery query, RowBounds rowBounds);
- int countScimGroups();
+ int countScimGroups(@Param("query") ScimGroupQuery query);
void insert(@Param("scimGroupDto") ScimGroupDto scimGroupDto);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.db.scim;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+public class ScimGroupQuery {
+ private static final Pattern DISPLAY_NAME_FILTER_PATTERN = Pattern.compile("^displayName\\s+eq\\s+\"([^\"]*?)\"$", CASE_INSENSITIVE);
+ private static final String UNSUPPORTED_FILTER = "Unsupported filter or value: %s. The only supported filter and operator is 'displayName eq \"displayName\"";
+ @VisibleForTesting
+ static final ScimGroupQuery ALL = new ScimGroupQuery(null);
+
+ private final String displayName;
+
+ @VisibleForTesting
+ protected ScimGroupQuery(@Nullable String displayName) {
+ this.displayName = displayName;
+ }
+
+ public static ScimGroupQuery fromScimFilter(@Nullable String filter) {
+ if (isBlank(filter)) {
+ return ALL;
+ }
+ String userName = getDisplayNameFromFilter(filter)
+ .orElseThrow(() -> new IllegalArgumentException(String.format(UNSUPPORTED_FILTER, filter)));
+
+ return new ScimGroupQuery(userName);
+ }
+
+ private static Optional<String> getDisplayNameFromFilter(String filter) {
+ Matcher matcher = DISPLAY_NAME_FILTER_PATTERN.matcher(filter.trim());
+ return matcher.find()
+ ? Optional.of(matcher.group(1))
+ : Optional.empty();
+
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+}
select
<include refid="scimGroupsColumns"/>
from scim_groups s
+ <if test="query.displayName != null">
+ inner join groups g on g.uuid=s.group_uuid
+ where g.name = #{query.displayName,jdbcType=VARCHAR}
+ </if>
order by s.scim_uuid asc
</select>
<select id="countScimGroups" resultType="int">
select count(1)
- from scim_groups
+ from scim_groups s
+ <if test="query.displayName != null">
+ inner join groups g on g.uuid=s.group_uuid
+ where g.name = #{query.displayName,jdbcType=VARCHAR}
+ </if>
</select>
<insert id="insert" parameterType="map" useGeneratedKeys="false">
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.stream.IntStream;
+import org.apache.commons.lang.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.db.DbTester;
+import org.sonar.db.user.GroupDto;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(DataProviderRunner.class)
public class ScimGroupDaoTest {
+ private static final String DISPLAY_NAME_FILTER = "displayName eq \"group2\"";
@Rule
public DbTester db = DbTester.create();
private final ScimGroupDao scimGroupDao = db.getDbClient().scimGroupDao();
.extracting(ScimGroupDto::getGroupUuid, ScimGroupDto::getScimGroupUuid)
.containsExactlyInAnyOrder(
tuple(scimGroup1.getGroupUuid(), scimGroup1.getScimGroupUuid()),
- tuple(scimGroup2.getGroupUuid(), scimGroup2.getScimGroupUuid())
- );
+ tuple(scimGroup2.getGroupUuid(), scimGroup2.getScimGroupUuid()));
}
@Test
int totalScimGroups = 15;
generateScimGroups(totalScimGroups);
- assertThat(scimGroupDao.countScimGroups(db.getSession())).isEqualTo(totalScimGroups);
+ assertThat(scimGroupDao.countScimGroups(db.getSession(), ScimGroupQuery.ALL)).isEqualTo(totalScimGroups);
}
@Test
public void countScimGroups_shouldReturnZero_whenNoScimGroups() {
- assertThat(scimGroupDao.countScimGroups(db.getSession())).isZero();
+ assertThat(scimGroupDao.countScimGroups(db.getSession(), ScimGroupQuery.ALL)).isZero();
}
@DataProvider
@Test
@UseDataProvider("paginationData")
- public void findScimGroups_whenPaginationAndStartIndex_shouldReturnTheCorrectNumberOfScimGroups(int totalScimGroups, int offset, int pageSize, List<String> expectedScimGroupUuids) {
+ public void findScimGroups_whenPaginationAndStartIndex_shouldReturnTheCorrectNumberOfScimGroups(int totalScimGroups, int offset, int pageSize,
+ List<String> expectedScimGroupUuidSuffixes) {
generateScimGroups(totalScimGroups);
- List<ScimGroupDto> scimUserDtos = scimGroupDao.findScimGroups(db.getSession(), offset, pageSize);
+ List<ScimGroupDto> scimGroupDtos = scimGroupDao.findScimGroups(db.getSession(), ScimGroupQuery.ALL, offset, pageSize);
- List<String> scimGroupsUuids = toScimGroupsUuids(scimUserDtos);
- assertThat(scimGroupsUuids).containsExactlyElementsOf(expectedScimGroupUuids);
+ List<String> actualScimGroupsUuids = toScimGroupsUuids(scimGroupDtos);
+ List<String> expectedScimGroupUuids = toExpectedscimGroupUuids(expectedScimGroupUuidSuffixes);
+ assertThat(actualScimGroupsUuids).containsExactlyElementsOf(expectedScimGroupUuids);
+ }
+
+ private static List<String> toExpectedscimGroupUuids(List<String> expectedScimGroupUuidSuffixes) {
+ return expectedScimGroupUuidSuffixes.stream()
+ .map(expectedScimGroupUuidSuffix -> "scim_uuid_Scim Group" + expectedScimGroupUuidSuffix)
+ .collect(Collectors.toList());
+ }
+
+ @Test
+ public void findScimGroups_whenFilteringByDisplayName_shouldReturnTheExpectedScimGroups() {
+ insertGroupAndScimGroup("group1");
+ insertGroupAndScimGroup("group2");
+ ScimGroupQuery query = ScimGroupQuery.fromScimFilter(DISPLAY_NAME_FILTER);
+
+ List<ScimGroupDto> scimGroups = scimGroupDao.findScimGroups(db.getSession(), query, 0, 100);
+
+ assertThat(scimGroups).hasSize(1);
+ assertThat(scimGroups.get(0).getScimGroupUuid()).isEqualTo(createScimGroupUuid("group2"));
+ }
+
+ @Test
+ public void countScimGroups_whenFilteringByDisplayName_shouldReturnCorrectCount() {
+ insertGroupAndScimGroup("group1");
+ insertGroupAndScimGroup("group2");
+ ScimGroupQuery query = ScimGroupQuery.fromScimFilter(DISPLAY_NAME_FILTER);
+
+ int groupCount = scimGroupDao.countScimGroups(db.getSession(), query);
+
+ assertThat(groupCount).isEqualTo(1);
+ }
+
+ private void insertGroupAndScimGroup(String groupName) {
+ GroupDto groupDto = insertGroup(groupName);
+ insertScimGroup(createScimGroupUuid(groupName), groupDto.getUuid());
}
@Test
public void getManagedGroupsSqlFilter_whenFilterByManagedIsFalse_returnsCorrectQuery() {
String filterNonManagedUser = scimGroupDao.getManagedGroupSqlFilter(false);
assertThat(filterNonManagedUser).isEqualTo("not exists (select group_uuid from scim_groups sg where sg.group_uuid = uuid)");
+ }
+ private List<ScimGroupDto> generateScimGroups(int totalScimGroups) {
+ return IntStream.range(1, totalScimGroups + 1)
+ .mapToObj(i -> insertGroup(createGroupName(i)))
+ .map(groupDto -> insertScimGroup(createScimGroupUuid(groupDto.getName()), groupDto.getUuid()))
+ .toList();
}
- private void generateScimGroups(int totalScimGroups) {
- List<ScimGroupDto> allScimGroups = Stream.iterate(1, i -> i + 1)
- .map(i -> insertScimGroup(i.toString()))
- .limit(totalScimGroups)
- .collect(Collectors.toList());
- assertThat(allScimGroups).hasSize(totalScimGroups);
+ private static String createGroupName(int i) {
+ return "Scim Group" + i;
}
- private ScimGroupDto insertScimGroup(String scimGroupUuid) {
- return insertScimGroup(scimGroupUuid, randomAlphanumeric(40));
+ private GroupDto insertGroup(String name) {
+ return db.users().insertGroup(name);
+ }
+
+ private static String createScimGroupUuid(String groupName) {
+ return StringUtils.substring("scim_uuid_" + groupName, 0, 40);
}
private ScimGroupDto insertScimGroup(String scimGroupUuid, String groupUuid) {
ScimGroupDto scimGroupDto = new ScimGroupDto(scimGroupUuid, groupUuid);
Map<String, Object> data = Map.of("scim_uuid", scimGroupDto.getScimGroupUuid(), "group_uuid", scimGroupDto.getGroupUuid());
db.executeInsert("scim_groups", data);
-
return scimGroupDto;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.db.scim;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+@RunWith(DataProviderRunner.class)
+public class ScimGroupQueryTest {
+
+ @DataProvider
+ public static Object[][] filterData() {
+ ScimGroupQuery queryWithDisplayName = new ScimGroupQuery("testGroup");
+ return new Object[][] {
+ {"displayName eq \"testGroup\"", queryWithDisplayName},
+ {" displayName eq \"testGroup\" ", queryWithDisplayName},
+ {"displayName eq \"testGroup\"", queryWithDisplayName},
+ {"DIsPlaynaMe eq \"testGroup\"", queryWithDisplayName},
+ {"displayName EQ \"testGroup\"", queryWithDisplayName},
+ {null, ScimGroupQuery.ALL},
+ {"", ScimGroupQuery.ALL}
+ };
+ }
+
+ @Test
+ @UseDataProvider("filterData")
+ public void fromScimFilter_shouldCorrectlyResolveProperties(String filter, ScimGroupQuery expected) {
+ ScimGroupQuery scimGroupQuery = ScimGroupQuery.fromScimFilter(filter);
+
+ assertThat(scimGroupQuery).usingRecursiveComparison().isEqualTo(expected);
+ }
+
+ @DataProvider
+ public static Object[][] unsupportedFilterData() {
+ return new Object[][] {
+ {"otherProp eq \"testGroup\""},
+ {"displayName eq \"testGroup\" or displayName eq \"testGroup2\""},
+ {"displayName eq \"testGroup\" and email eq \"test.user2@okta.local\""},
+ {"displayName eq \"testGroup\"xjdkfgldkjfhg"}
+ };
+ }
+
+ @Test
+ @UseDataProvider("unsupportedFilterData")
+ public void fromScimFilter_shouldThrowAnException(String filter) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> ScimGroupQuery.fromScimFilter(filter))
+ .withMessage(format("Unsupported filter or value: %s. The only supported filter and operator is 'displayName eq \"displayName\"", filter));
+ }
+
+ @Test
+ public void empty_shouldHaveNoProperties() {
+ ScimGroupQuery scimGroupQuery = ScimGroupQuery.ALL;
+
+ assertThat(scimGroupQuery.getDisplayName()).isNull();
+ }
+
+}