|
|
@@ -20,6 +20,7 @@ |
|
|
|
package org.sonar.server.user.ws; |
|
|
|
|
|
|
|
import com.google.common.collect.Multimap; |
|
|
|
import java.time.OffsetDateTime; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
@@ -33,6 +34,8 @@ import org.sonar.api.server.ws.Change; |
|
|
|
import org.sonar.api.server.ws.Request; |
|
|
|
import org.sonar.api.server.ws.Response; |
|
|
|
import org.sonar.api.server.ws.WebService; |
|
|
|
import org.sonar.api.utils.DateUtils; |
|
|
|
import org.sonar.api.utils.MessageException; |
|
|
|
import org.sonar.api.utils.Paging; |
|
|
|
import org.sonar.db.DbClient; |
|
|
|
import org.sonar.db.DbSession; |
|
|
@@ -40,6 +43,7 @@ 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.exceptions.ServerException; |
|
|
|
import org.sonar.server.issue.AvatarResolver; |
|
|
|
import org.sonar.server.management.ManagedInstanceService; |
|
|
|
import org.sonar.server.user.UserSession; |
|
|
@@ -49,13 +53,12 @@ import org.sonarqube.ws.Users.SearchWsResponse; |
|
|
|
import static com.google.common.base.MoreObjects.firstNonNull; |
|
|
|
import static com.google.common.base.Preconditions.checkArgument; |
|
|
|
import static com.google.common.base.Strings.emptyToNull; |
|
|
|
import static java.util.Comparator.comparing; |
|
|
|
import static java.lang.Boolean.TRUE; |
|
|
|
import static java.util.Comparator.comparing; |
|
|
|
import static java.util.Optional.ofNullable; |
|
|
|
import static org.sonar.api.server.ws.WebService.Param.PAGE; |
|
|
|
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; |
|
|
|
import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; |
|
|
|
import static org.sonar.api.utils.DateUtils.formatDateTime; |
|
|
|
import static org.sonar.api.utils.Paging.forPageIndex; |
|
|
|
import static org.sonar.core.util.stream.MoreCollectors.toList; |
|
|
|
import static org.sonar.server.ws.WsUtils.writeProtobuf; |
|
|
@@ -67,8 +70,13 @@ import static org.sonarqube.ws.Users.SearchWsResponse.newBuilder; |
|
|
|
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 static final int MAX_PAGE_SIZE = 500; |
|
|
|
private static final String LAST_CONNECTION_DATE_FROM = "lastConnectedAfter"; |
|
|
|
private static final String LAST_CONNECTION_DATE_TO = "lastConnectedBefore"; |
|
|
|
private static final String SONAR_LINT_LAST_CONNECTION_DATE_FROM = "slLastConnectedAfter"; |
|
|
|
private static final String SONAR_LINT_LAST_CONNECTION_DATE_TO = "slLastConnectedBefore"; |
|
|
|
private final UserSession userSession; |
|
|
|
private final DbClient dbClient; |
|
|
|
private final AvatarResolver avatarResolver; |
|
|
@@ -93,11 +101,17 @@ public class SearchAction implements UsersWsAction { |
|
|
|
" <li>'externalProvider'</li>" + |
|
|
|
" <li>'groups'</li>" + |
|
|
|
" <li>'lastConnectionDate'</li>" + |
|
|
|
" <li>'sonarLintLastConnectionDate'</li>" + |
|
|
|
" <li>'tokensCount'</li>" + |
|
|
|
"</ul>" + |
|
|
|
"Field 'lastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.") |
|
|
|
.setSince("3.6") |
|
|
|
.setChangelog( |
|
|
|
new Change("10.1", "New optional parameters " + SONAR_LINT_LAST_CONNECTION_DATE_FROM + |
|
|
|
" and " + SONAR_LINT_LAST_CONNECTION_DATE_TO + " to filter users by SonarLint last connection date"), |
|
|
|
new Change("10.1", "New optional parameters " + LAST_CONNECTION_DATE_FROM + |
|
|
|
" and " + LAST_CONNECTION_DATE_TO + " to filter users by SonarQube last connection date"), |
|
|
|
new Change("10.1", "New field 'sonarLintLastConnectionDate' is added to response"), |
|
|
|
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."), |
|
|
@@ -112,6 +126,7 @@ public class SearchAction implements UsersWsAction { |
|
|
|
|
|
|
|
action.addPagingParams(50, SearchOptions.MAX_PAGE_SIZE); |
|
|
|
|
|
|
|
final String dateExample = "2020-01-01T00:00:00+0100"; |
|
|
|
action.createParam(TEXT_QUERY) |
|
|
|
.setMinimumLength(2) |
|
|
|
.setDescription("Filter on login, name and email.<br />" + |
|
|
@@ -128,6 +143,38 @@ public class SearchAction implements UsersWsAction { |
|
|
|
.setRequired(false) |
|
|
|
.setDefaultValue(null) |
|
|
|
.setBooleanPossibleValues(); |
|
|
|
action.createParam(LAST_CONNECTION_DATE_FROM) |
|
|
|
.setSince("10.1") |
|
|
|
.setDescription(""" |
|
|
|
Filter the users based on the last connection date field. Only users who interacted with this instance at or after the date will be returned. |
|
|
|
The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""") |
|
|
|
.setRequired(false) |
|
|
|
.setDefaultValue(null) |
|
|
|
.setExampleValue(dateExample); |
|
|
|
action.createParam(LAST_CONNECTION_DATE_TO) |
|
|
|
.setSince("10.1") |
|
|
|
.setDescription(""" |
|
|
|
Filter the users based on the last connection date field. Only users who interacted with this instance at or before the date will be returned. |
|
|
|
The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""") |
|
|
|
.setRequired(false) |
|
|
|
.setDefaultValue(null) |
|
|
|
.setExampleValue(dateExample); |
|
|
|
action.createParam(SONAR_LINT_LAST_CONNECTION_DATE_FROM) |
|
|
|
.setSince("10.1") |
|
|
|
.setDescription(""" |
|
|
|
Filter the users based on the sonar lint last connection date field. Only users who interacted with this instance using SonarLint at or after the date will be returned. |
|
|
|
The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""") |
|
|
|
.setRequired(false) |
|
|
|
.setDefaultValue(null) |
|
|
|
.setExampleValue(dateExample); |
|
|
|
action.createParam(SONAR_LINT_LAST_CONNECTION_DATE_TO) |
|
|
|
.setSince("10.1") |
|
|
|
.setDescription(""" |
|
|
|
Filter the users based on the sonar lint last connection date field. Only users who interacted with this instance using SonarLint at or before the date will be returned. |
|
|
|
The format must be ISO 8601 datetime format (YYYY-MM-DDThh:mm:ss±hhmm)""") |
|
|
|
.setRequired(false) |
|
|
|
.setDefaultValue(null) |
|
|
|
.setExampleValue(dateExample); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
@@ -156,20 +203,22 @@ public class SearchAction implements UsersWsAction { |
|
|
|
} |
|
|
|
|
|
|
|
private UserQuery buildUserQuery(SearchRequest request) { |
|
|
|
UserQuery.UserQueryBuilder builder = UserQuery.builder(); |
|
|
|
request.getLastConnectionDateFrom().ifPresent(builder::lastConnectionDateFrom); |
|
|
|
request.getLastConnectionDateTo().ifPresent(builder::lastConnectionDateTo); |
|
|
|
request.getSonarLintLastConnectionDateFrom().ifPresent(builder::sonarLintLastConnectionDateFrom); |
|
|
|
request.getSonarLintLastConnectionDateTo().ifPresent(builder::sonarLintLastConnectionDateTo); |
|
|
|
|
|
|
|
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) { |
|
|
|
builder.isManagedClause(managedInstanceSql); |
|
|
|
} else if (request.isManaged() != null) { |
|
|
|
throw BadRequestException.create("The 'managed' parameter is only available for managed instances."); |
|
|
|
} |
|
|
|
return UserQuery.builder() |
|
|
|
|
|
|
|
return builder |
|
|
|
.isActive(!request.isDeactivated()) |
|
|
|
.searchText(request.getQuery()) |
|
|
|
.build(); |
|
|
@@ -215,7 +264,9 @@ public class SearchAction implements UsersWsAction { |
|
|
|
} |
|
|
|
ofNullable(user.getExternalLogin()).ifPresent(userBuilder::setExternalIdentity); |
|
|
|
ofNullable(tokensCount).ifPresent(userBuilder::setTokensCount); |
|
|
|
ofNullable(user.getLastConnectionDate()).ifPresent(date -> userBuilder.setLastConnectionDate(formatDateTime(date))); |
|
|
|
ofNullable(user.getLastConnectionDate()).map(DateUtils::formatDateTime).ifPresent(userBuilder::setLastConnectionDate); |
|
|
|
ofNullable(user.getLastSonarlintConnectionDate()) |
|
|
|
.map(DateUtils::formatDateTime).ifPresent(userBuilder::setSonarLintLastConnectionDate); |
|
|
|
userBuilder.setManaged(TRUE.equals(managed)); |
|
|
|
} |
|
|
|
return userBuilder.build(); |
|
|
@@ -228,6 +279,10 @@ public class SearchAction implements UsersWsAction { |
|
|
|
.setQuery(request.param(TEXT_QUERY)) |
|
|
|
.setDeactivated(request.mandatoryParamAsBoolean(DEACTIVATED_PARAM)) |
|
|
|
.setManaged(request.paramAsBoolean(MANAGED_PARAM)) |
|
|
|
.setLastConnectionDateFrom(request.param(LAST_CONNECTION_DATE_FROM)) |
|
|
|
.setLastConnectionDateTo(request.param(LAST_CONNECTION_DATE_TO)) |
|
|
|
.setSonarLintLastConnectionDateFrom(request.param(SONAR_LINT_LAST_CONNECTION_DATE_FROM)) |
|
|
|
.setSonarLintLastConnectionDateTo(request.param(SONAR_LINT_LAST_CONNECTION_DATE_TO)) |
|
|
|
.setPage(request.mandatoryParamAsInt(PAGE)) |
|
|
|
.setPageSize(pageSize) |
|
|
|
.build(); |
|
|
@@ -239,6 +294,10 @@ public class SearchAction implements UsersWsAction { |
|
|
|
private final String query; |
|
|
|
private final boolean deactivated; |
|
|
|
private final Boolean managed; |
|
|
|
private final OffsetDateTime lastConnectionDateFrom; |
|
|
|
private final OffsetDateTime lastConnectionDateTo; |
|
|
|
private final OffsetDateTime sonarLintLastConnectionDateFrom; |
|
|
|
private final OffsetDateTime sonarLintLastConnectionDateTo; |
|
|
|
|
|
|
|
private SearchRequest(Builder builder) { |
|
|
|
this.page = builder.page; |
|
|
@@ -246,6 +305,14 @@ public class SearchAction implements UsersWsAction { |
|
|
|
this.query = builder.query; |
|
|
|
this.deactivated = builder.deactivated; |
|
|
|
this.managed = builder.managed; |
|
|
|
try { |
|
|
|
this.lastConnectionDateFrom = Optional.ofNullable(builder.lastConnectionDateFrom).map(DateUtils::parseOffsetDateTime).orElse(null); |
|
|
|
this.lastConnectionDateTo = Optional.ofNullable(builder.lastConnectionDateTo).map(DateUtils::parseOffsetDateTime).orElse(null); |
|
|
|
this.sonarLintLastConnectionDateFrom = Optional.ofNullable(builder.sonarLintLastConnectionDateFrom).map(DateUtils::parseOffsetDateTime).orElse(null); |
|
|
|
this.sonarLintLastConnectionDateTo = Optional.ofNullable(builder.sonarLintLastConnectionDateTo).map(DateUtils::parseOffsetDateTime).orElse(null); |
|
|
|
} catch (MessageException me) { |
|
|
|
throw new ServerException(400, me.getMessage()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public Integer getPage() { |
|
|
@@ -270,6 +337,22 @@ public class SearchAction implements UsersWsAction { |
|
|
|
return managed; |
|
|
|
} |
|
|
|
|
|
|
|
public Optional<OffsetDateTime> getLastConnectionDateFrom() { |
|
|
|
return Optional.ofNullable(lastConnectionDateFrom); |
|
|
|
} |
|
|
|
|
|
|
|
public Optional<OffsetDateTime> getLastConnectionDateTo() { |
|
|
|
return Optional.ofNullable(lastConnectionDateTo); |
|
|
|
} |
|
|
|
|
|
|
|
public Optional<OffsetDateTime> getSonarLintLastConnectionDateFrom() { |
|
|
|
return Optional.ofNullable(sonarLintLastConnectionDateFrom); |
|
|
|
} |
|
|
|
|
|
|
|
public Optional<OffsetDateTime> getSonarLintLastConnectionDateTo() { |
|
|
|
return Optional.ofNullable(sonarLintLastConnectionDateTo); |
|
|
|
} |
|
|
|
|
|
|
|
public static Builder builder() { |
|
|
|
return new Builder(); |
|
|
|
} |
|
|
@@ -281,6 +364,11 @@ public class SearchAction implements UsersWsAction { |
|
|
|
private String query; |
|
|
|
private boolean deactivated; |
|
|
|
private Boolean managed; |
|
|
|
private String lastConnectionDateFrom; |
|
|
|
private String lastConnectionDateTo; |
|
|
|
private String sonarLintLastConnectionDateFrom; |
|
|
|
private String sonarLintLastConnectionDateTo; |
|
|
|
|
|
|
|
|
|
|
|
private Builder() { |
|
|
|
// enforce factory method use |
|
|
@@ -311,6 +399,26 @@ public class SearchAction implements UsersWsAction { |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public Builder setLastConnectionDateFrom(@Nullable String lastConnectionDateFrom) { |
|
|
|
this.lastConnectionDateFrom = lastConnectionDateFrom; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public Builder setLastConnectionDateTo(@Nullable String lastConnectionDateTo) { |
|
|
|
this.lastConnectionDateTo = lastConnectionDateTo; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public Builder setSonarLintLastConnectionDateFrom(@Nullable String sonarLintLastConnectionDateFrom) { |
|
|
|
this.sonarLintLastConnectionDateFrom = sonarLintLastConnectionDateFrom; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public Builder setSonarLintLastConnectionDateTo(@Nullable String sonarLintLastConnectionDateTo) { |
|
|
|
this.sonarLintLastConnectionDateTo = sonarLintLastConnectionDateTo; |
|
|
|
return this; |
|
|
|
} |
|
|
|
|
|
|
|
public SearchRequest build() { |
|
|
|
return new SearchRequest(this); |
|
|
|
} |