return scimUserTestData;
}
+ @Test
+ public void getManagedUserSqlFilter_isNotEmpty() {
+ String filterManagedUser = scimUserDao.getManagedUserSqlFilter(true);
+ assertThat(filterManagedUser).isNotEmpty();
+ String filterNonManagedUser = scimUserDao.getManagedUserSqlFilter(false);
+ assertThat(filterNonManagedUser).isNotEmpty();
+
+ assertThat(filterManagedUser).isNotEqualTo(filterNonManagedUser);
+ }
+
private static class ScimUserTestData {
private final String scimUserUuid;
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 void deleteByScimUuid(DbSession dbSession, String scimUuid) {
mapper(dbSession).deleteByScimUuid(scimUuid);
}
+
+ public String getManagedUserSqlFilter(boolean filterByManaged) {
+ return String.format("%s exists (select user_uuid from scim_users su where su.user_uuid = uuid)", filterByManaged ? "" : "not");
+ }
}
import org.apache.commons.lang.StringUtils;
public class UserQuery {
+ private static final String MATCH_NOTHING = "1=2";
private final String searchText;
private final Boolean isActive;
+ private final String isManagedSqlClause;
- public UserQuery(@Nullable String searchText, @Nullable Boolean isActive) {
+ public UserQuery(@Nullable String searchText, @Nullable Boolean isActive, @Nullable String isManagedSqlClause) {
this.searchText = searchTextToSearchTextSql(searchText);
this.isActive = isActive;
+ this.isManagedSqlClause = isManagedSqlClause;
}
private static String searchTextToSearchTextSql(@Nullable String text) {
}
@CheckForNull
- private String getSearchText() {
+ public String getSearchText() {
return searchText;
}
@CheckForNull
- private Boolean isActive() {
+ public Boolean isActive() {
return isActive;
}
+ @CheckForNull
+ public String getIsManagedSqlClause() {
+ return isManagedSqlClause;
+ }
+
public static UserQueryBuilder builder() {
return new UserQueryBuilder();
}
public static final class UserQueryBuilder {
- private String searchText;
- private Boolean isActive;
+ private String searchText = null;
+ private Boolean isActive = null;
+ private String isManagedSqlClause = null;
private UserQueryBuilder() {
}
return this;
}
+ public UserQueryBuilder isManagedClause(@Nullable String isManagedSqlClause) {
+ this.isManagedSqlClause = isManagedSqlClause;
+ return this;
+ }
+
public UserQuery build() {
- return new UserQuery(searchText, isActive);
+ return new UserQuery(searchText, isActive, isManagedSqlClause);
}
}
}
<include refid="userColumns"/>
FROM
(SELECT
- u.uuid,
- u.login,
- u.name,
- u.email,
- u.active,
- u.scm_accounts,
- u.salt,
- u.crypted_password,
- u.hash_method,
- u.external_id,
- u.external_login,
- u.external_identity_provider,
- u.user_local,
- u.reset_password,
- u.homepage_type,
- u.homepage_parameter,
- u.last_connection_date,
- u.last_sonarlint_connection,
- u.created_at,
- u.updated_at
+ <include refid="searchByQueryInnerQueryColumns"/>
FROM users u
<include refid="searchByQueryWhereClause"/>
ORDER BY u.login
FROM
(SELECT rownum as rn, t.* from (
SELECT
- u.uuid,
- u.login,
- u.name,
- u.email,
- u.active,
- u.scm_accounts,
- u.salt,
- u.crypted_password,
- u.hash_method,
- u.external_id,
- u.external_login,
- u.external_identity_provider,
- u.user_local,
- u.reset_password,
- u.homepage_type,
- u.homepage_parameter,
- u.last_connection_date,
- u.last_sonarlint_connection,
- u.created_at,
- u.updated_at
+ <include refid="searchByQueryInnerQueryColumns"/>
FROM users u
<include refid="searchByQueryWhereClause"/>
ORDER BY u.login ASC
<include refid="searchByQueryWhereClause"/>
</select>
+ <sql id="searchByQueryInnerQueryColumns">
+ u.uuid,
+ u.login,
+ u.name,
+ u.email,
+ u.active,
+ u.scm_accounts,
+ u.salt,
+ u.crypted_password,
+ u.hash_method,
+ u.external_id,
+ u.external_login,
+ u.external_identity_provider,
+ u.user_local,
+ u.reset_password,
+ u.homepage_type,
+ u.homepage_parameter,
+ u.last_connection_date,
+ u.last_sonarlint_connection,
+ u.created_at,
+ u.updated_at
+ </sql>
+
<sql id="searchByQueryWhereClause">
- <where>
+ <where>
+ 1=1
<if test="query.isActive != null">
- u.active=#{query.isActive, jdbcType=BOOLEAN}
+ AND u.active=#{query.isActive, jdbcType=BOOLEAN}
</if>
<if test="query.searchText != null">
AND (
OR (lower(u.email) LIKE lower(#{query.searchText, jdbcType=VARCHAR}) ESCAPE '/')
)
</if>
+ <if test="query.isManagedSqlClause != null">
+ AND ${query.isManagedSqlClause}
+ </if>
</where>
-
</sql>
<select id="selectUsersForTelemetry" parameterType="map" resultType="UserTelemetry">
.orElse(returnNonManagedForAllGroups(groupUuids));
}
+ @Override
+ public String getManagedUsersSqlFilter(boolean filterByManaged) {
+ return findManagedInstanceService()
+ .map(managedInstanceService -> managedInstanceService.getManagedUsersSqlFilter(filterByManaged))
+ .orElseThrow(() -> new IllegalStateException("This instance is not managed."));
+ }
+
private Optional<ManagedInstanceService> findManagedInstanceService() {
Set<ManagedInstanceService> managedInstanceServices = delegates.stream()
.filter(ManagedInstanceService::isInstanceExternallyManaged)
Map<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids);
Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids);
+
+ String getManagedUsersSqlFilter(boolean filterByManaged);
}
.withMessage("The instance can't be managed by more than one identity provider and 2 were found.");
}
+ @Test
+ public void getManagedUsersSqlFilter_whenNoDelegates_throws() {
+ Set<ManagedInstanceService> managedInstanceServices = emptySet();
+ DelegatingManagedInstanceService delegatingManagedInstanceService = new DelegatingManagedInstanceService(managedInstanceServices);
+ assertThatIllegalStateException()
+ .isThrownBy(() -> delegatingManagedInstanceService.getManagedUsersSqlFilter(true))
+ .withMessage("This instance is not managed.");
+ }
+
+ @Test
+ public void getManagedUsersSqlFilter_delegatesToRightService_andPropagateAnswer() {
+ AlwaysManagedInstanceService alwaysManagedInstanceService = new AlwaysManagedInstanceService();
+ DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), alwaysManagedInstanceService));
+
+ assertThat(managedInstanceService.getManagedUsersSqlFilter(true)).isNotNull().isEqualTo(alwaysManagedInstanceService.getManagedUsersSqlFilter(
+ true));
+ }
+
private ManagedInstanceService getManagedInstanceService(Set<String> userUuids, Map<String, Boolean> uuidToManaged) {
ManagedInstanceService anotherManagedInstanceService = mock(ManagedInstanceService.class);
when(anotherManagedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) {
return null;
}
+
+ @Override
+ public String getManagedUsersSqlFilter(boolean filterByManaged) {
+ return null;
+ }
}
private static class AlwaysManagedInstanceService implements ManagedInstanceService {
public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) {
return null;
}
+
+ @Override
+ public String getManagedUsersSqlFilter(boolean filterByManaged) {
+ return "any filter";
+ }
}
}
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbTester;
+import org.sonar.db.scim.ScimUserDao;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.issue.AvatarResolverImpl;
import org.sonar.server.management.ManagedInstanceService;
import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Common.Paging;
import org.sonarqube.ws.Users.SearchWsResponse;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.DateUtils.formatDateTime;
}
@Test
- public void return_isManaged() {
+ public void return_isManagedFlag() {
UserDto nonManagedUser = db.users().insertUser(u -> u.setEmail("john@doe.com"));
UserDto managedUser = db.users().insertUser(u -> u.setEmail("externalUser@doe.com"));
mockUsersAsManaged(managedUser.getUuid());
);
}
+ @Test
+ public void search_whenFilteringByManagedAndInstanceNotManaged_throws() {
+ userSession.logIn().setSystemAdministrator();
+
+ TestRequest testRequest = ws.newRequest()
+ .setParam("managed", "true");
+
+ assertThatExceptionOfType(BadRequestException.class)
+ .isThrownBy(() -> testRequest.executeProtobuf(SearchWsResponse.class))
+ .withMessage("The 'managed' parameter is only available for managed instances.");
+ }
+
+ @Test
+ public void search_whenFilteringByManagedAndInstanceManaged_returnsCorrectResults() {
+ UserDto nonManagedUser = db.users().insertUser(u -> u.setEmail("john@doe.com"));
+ UserDto managedUser = db.users().insertUser(u -> u.setEmail("externalUser@doe.com"));
+ db.users().enableScimForUser(managedUser);
+ mockUsersAsManaged(managedUser.getUuid());
+ mockInstanceExternallyManagedAndFilterForManagedUsers();
+ userSession.logIn().setSystemAdministrator();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam("managed", "true")
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getUsersList())
+ .extracting(User::getLogin, User::getManaged)
+ .containsExactlyInAnyOrder(
+ tuple(managedUser.getLogin(), true)
+ );
+ }
+
+ @Test
+ public void search_whenFilteringByNonManagedAndInstanceManaged_returnsCorrectResults() {
+ UserDto nonManagedUser = db.users().insertUser(u -> u.setEmail("john@doe.com"));
+ UserDto managedUser = db.users().insertUser(u -> u.setEmail("externalUser@doe.com"));
+ db.users().enableScimForUser(managedUser);
+ mockUsersAsManaged(managedUser.getUuid());
+ mockInstanceExternallyManagedAndFilterForManagedUsers();
+ userSession.logIn().setSystemAdministrator();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam("managed", "false")
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getUsersList())
+ .extracting(User::getLogin, User::getManaged)
+ .containsExactlyInAnyOrder(
+ tuple(nonManagedUser.getLogin(), false)
+ );
+ }
+
+ private void mockInstanceExternallyManagedAndFilterForManagedUsers() {
+ when(managedInstanceService.isInstanceExternallyManaged()).thenReturn(true);
+ when(managedInstanceService.getManagedUsersSqlFilter(anyBoolean()))
+ .thenAnswer(invocation -> {
+ Boolean managed = invocation.getArgument(0, Boolean.class);
+ return new ScimUserDao(mock(UuidFactory.class)).getManagedUserSqlFilter(managed);
+ });
+ }
+
@Test
public void return_scm_accounts() {
UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("john1", "john2")));
assertThat(action).isNotNull();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
- assertThat(action.params()).hasSize(4);
+ assertThat(action.params()).hasSize(5);
}
private void mockUsersAsManaged(String... userUuids) {
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserQuery;
import org.sonar.server.es.SearchOptions;
+import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.issue.AvatarResolver;
import org.sonar.server.management.ManagedInstanceService;
import org.sonar.server.user.UserSession;
public class SearchAction implements UsersWsAction {
private static final String DEACTIVATED_PARAM = "deactivated";
+ private static final String MANAGED_PARAM = "managed";
private static final int MAX_PAGE_SIZE = 500;
private final UserSession userSession;
.setSince("3.6")
.setChangelog(
new Change("10.0", "'q' parameter values is now always performing a case insensitive match"),
+ new Change("10.0", "New parameter 'managed' to optionally search by managed status"),
new Change("10.0", "Response includes 'managed' field."),
new Change("9.7", "New parameter 'deactivated' to optionally search for deactivated users"),
new Change("7.7", "New field 'lastConnectionDate' is added to response"),
.setRequired(false)
.setDefaultValue(false)
.setBooleanPossibleValues();
+ action.createParam(MANAGED_PARAM)
+ .setSince("10.0")
+ .setDescription("Return managed or non-managed users. Only available for managed instances, throws for non-managed instances.")
+ .setRequired(false)
+ .setDefaultValue(null)
+ .setBooleanPossibleValues();
}
@Override
private Users.SearchWsResponse doHandle(SearchRequest request) {
UserQuery userQuery = buildUserQuery(request);
try (DbSession dbSession = dbClient.openSession(false)) {
- List<UserDto> users = fetchUsersAndSortByLogin(request, dbSession, userQuery);
+ List<UserDto> users = findUsersAndSortByLogin(request, dbSession, userQuery);
int totalUsers = dbClient.userDao().countUsers(dbSession, userQuery);
List<String> logins = users.stream().map(UserDto::getLogin).collect(toList());
return users.stream().map(UserDto::getUuid).collect(Collectors.toSet());
}
- private static UserQuery buildUserQuery(SearchRequest request) {
+ private UserQuery buildUserQuery(SearchRequest request) {
+ if (managedInstanceService.isInstanceExternallyManaged()) {
+ String managedInstanceSql = Optional.ofNullable(request.isManaged())
+ .map(managedInstanceService::getManagedUsersSqlFilter)
+ .orElse(null);
+ return UserQuery.builder()
+ .isActive(!request.isDeactivated())
+ .searchText(request.getQuery())
+ .isManagedClause(managedInstanceSql)
+ .build();
+ }
+ if (request.isManaged() != null) {
+ throw BadRequestException.create("The 'managed' parameter is only available for managed instances.");
+ }
return UserQuery.builder()
.isActive(!request.isDeactivated())
.searchText(request.getQuery())
.build();
}
- private List<UserDto> fetchUsersAndSortByLogin(SearchRequest request, DbSession dbSession, UserQuery userQuery) {
+ private List<UserDto> findUsersAndSortByLogin(SearchRequest request, DbSession dbSession, UserQuery userQuery) {
return dbClient.userDao().selectUsers(dbSession, userQuery, request.getPage(), request.getPageSize())
.stream()
.sorted(comparing(UserDto::getLogin))
return responseBuilder.build();
}
- private User towsUser(UserDto user, @Nullable Integer tokensCount, Collection<String> groups, Boolean isManaged) {
+ private User towsUser(UserDto user, @Nullable Integer tokensCount, Collection<String> groups, Boolean managed) {
User.Builder userBuilder = User.newBuilder().setLogin(user.getLogin());
ofNullable(user.getName()).ifPresent(userBuilder::setName);
if (userSession.isLoggedIn()) {
ofNullable(user.getExternalLogin()).ifPresent(userBuilder::setExternalIdentity);
ofNullable(tokensCount).ifPresent(userBuilder::setTokensCount);
ofNullable(user.getLastConnectionDate()).ifPresent(date -> userBuilder.setLastConnectionDate(formatDateTime(date)));
- userBuilder.setManaged(TRUE.equals(isManaged));
+ userBuilder.setManaged(TRUE.equals(managed));
}
return userBuilder.build();
}
return SearchRequest.builder()
.setQuery(request.param(TEXT_QUERY))
.setDeactivated(request.mandatoryParamAsBoolean(DEACTIVATED_PARAM))
+ .setManaged(request.paramAsBoolean(MANAGED_PARAM))
.setPage(request.mandatoryParamAsInt(PAGE))
.setPageSize(pageSize)
.build();
private final Integer pageSize;
private final String query;
private final boolean deactivated;
+ private final Boolean managed;
private SearchRequest(Builder builder) {
this.page = builder.page;
this.pageSize = builder.pageSize;
this.query = builder.query;
this.deactivated = builder.deactivated;
+ this.managed = builder.managed;
}
public Integer getPage() {
return deactivated;
}
+ @CheckForNull
+ private Boolean isManaged() {
+ return managed;
+ }
+
public static Builder builder() {
return new Builder();
}
private Integer pageSize;
private String query;
private boolean deactivated;
+ private Boolean managed;
private Builder() {
// enforce factory method use
return this;
}
+ public Builder setManaged(@Nullable Boolean managed) {
+ this.managed = managed;
+ return this;
+ }
+
public SearchRequest build() {
return new SearchRequest(this);
}