소스 검색

SONAR-20235 Add externalIdentity filter in api/users/search

tags/10.3.0.82913
Antoine Vigneau 7 달 전
부모
커밋
042cc13bcc

+ 17
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserQuery.java 파일 보기

@@ -36,6 +36,7 @@ public class UserQuery {
private final Long lastConnectionDateTo;
private final Long sonarLintLastConnectionDateFrom;
private final Long sonarLintLastConnectionDateTo;
private final String externalLogin;
private final Set<String> userUuids;

private UserQuery(UserQuery userQuery, Collection<String> userUuids) {
@@ -46,12 +47,14 @@ public class UserQuery {
this.lastConnectionDateTo = userQuery.getLastConnectionDateTo();
this.sonarLintLastConnectionDateTo = userQuery.getSonarLintLastConnectionDateTo();
this.sonarLintLastConnectionDateFrom = userQuery.getSonarLintLastConnectionDateFrom();
this.externalLogin = userQuery.externalLogin;
this.userUuids = new HashSet<>(userUuids);
}

private UserQuery(@Nullable String searchText, @Nullable Boolean isActive, @Nullable String isManagedSqlClause,
@Nullable OffsetDateTime lastConnectionDateFrom, @Nullable OffsetDateTime lastConnectionDateTo,
@Nullable OffsetDateTime sonarLintLastConnectionDateFrom, @Nullable OffsetDateTime sonarLintLastConnectionDateTo, @Nullable Set<String> userUuids) {
@Nullable OffsetDateTime sonarLintLastConnectionDateFrom, @Nullable OffsetDateTime sonarLintLastConnectionDateTo, @Nullable String externalLogin,
@Nullable Set<String> userUuids) {
this.searchText = searchTextToSearchTextSql(searchText);
this.isActive = isActive;
this.isManagedSqlClause = isManagedSqlClause;
@@ -59,6 +62,7 @@ public class UserQuery {
this.lastConnectionDateTo = formatDateToInput(lastConnectionDateTo);
this.sonarLintLastConnectionDateFrom = parseDateToLong(sonarLintLastConnectionDateFrom);
this.sonarLintLastConnectionDateTo = formatDateToInput(sonarLintLastConnectionDateTo);
this.externalLogin = externalLogin;
this.userUuids = userUuids;
}

@@ -128,6 +132,11 @@ public class UserQuery {
return sonarLintLastConnectionDateTo;
}

@CheckForNull
public String getExternalLogin() {
return externalLogin;
}

@CheckForNull
public Set<String> getUserUuids() {
return userUuids;
@@ -145,6 +154,7 @@ public class UserQuery {
private OffsetDateTime lastConnectionDateTo = null;
private OffsetDateTime sonarLintLastConnectionDateFrom = null;
private OffsetDateTime sonarLintLastConnectionDateTo = null;
private String externalLogin = null;
private Set<String> userUuids = null;

private UserQueryBuilder() {
@@ -185,6 +195,11 @@ public class UserQuery {
return this;
}

public UserQueryBuilder externalLogin(@Nullable String externalLogin) {
this.externalLogin = externalLogin;
return this;
}

public UserQueryBuilder userUuids(@Nullable Set<String> userUuids) {
this.userUuids = userUuids;
return this;
@@ -193,7 +208,7 @@ public class UserQuery {
public UserQuery build() {
return new UserQuery(
searchText, isActive, isManagedSqlClause, lastConnectionDateFrom, lastConnectionDateTo,
sonarLintLastConnectionDateFrom, sonarLintLastConnectionDateTo, userUuids);
sonarLintLastConnectionDateFrom, sonarLintLastConnectionDateTo, externalLogin, userUuids);
}
}
}

+ 3
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml 파일 보기

@@ -169,6 +169,9 @@
<if test="query.sonarLintLastConnectionDateTo != null">
AND (u.last_sonarlint_connection is null or u.last_sonarlint_connection &lt; #{query.sonarLintLastConnectionDateTo, jdbcType=BIGINT})
</if>
<if test="query.externalLogin != null">
AND (u.external_login = #{query.externalLogin, jdbcType=VARCHAR})
</if>
</where>
</sql>


+ 1
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserQueryTest.java 파일 보기

@@ -53,6 +53,7 @@ public class UserQueryTest {
.lastConnectionDateTo(OffsetDateTime.now().plus(1, ChronoUnit.DECADES))
.sonarLintLastConnectionDateFrom(OffsetDateTime.now().plus(2, ChronoUnit.DAYS))
.sonarLintLastConnectionDateTo(OffsetDateTime.now().minus(2, ChronoUnit.DECADES))
.externalLogin("externalLogin")
.build();
}
}

+ 28
- 0
server/sonar-webserver-common/src/it/java/org/sonar/server/common/user/service/UserServiceIT.java 파일 보기

@@ -233,6 +233,34 @@ public class UserServiceIT {
});
}

@Test
public void search_whenFilteringByExternalLoginAndMatchFound_returnsTheCorrectResult() {
prepareUsersWithExternalLogin();

SearchResults<UserInformation> users = userService.findUsers(UsersSearchRequest.builder().setPage(1).setPageSize(50).setExternalLogin("user1").build());

assertThat(users.searchResults())
.extracting(r -> r.userDto().getExternalLogin())
.containsExactly("user1");
}

@Test
public void search_whenFilteringByExternalLoginAndNoMatchFound_returnsNoResult() {
prepareUsersWithExternalLogin();

SearchResults<UserInformation> users = userService.findUsers(UsersSearchRequest.builder().setPage(1).setPageSize(50).setExternalLogin("nomatch").build());

assertThat(users.searchResults())
.extracting(r -> r.userDto().getExternalLogin())
.isEmpty();
}

private void prepareUsersWithExternalLogin() {
db.users().insertUser(user -> user.setExternalLogin("user1"));
db.users().insertUser(user -> user.setExternalLogin("USER1"));
db.users().insertUser(user -> user.setExternalLogin("user1-oldaccount"));
}

@Test
public void return_scm_accounts() {
UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("john1", "john2")));

+ 1
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UserService.java 파일 보기

@@ -92,6 +92,7 @@ public class UserService {
request.getLastConnectionDateTo().ifPresent(builder::lastConnectionDateTo);
request.getSonarLintLastConnectionDateFrom().ifPresent(builder::sonarLintLastConnectionDateFrom);
request.getSonarLintLastConnectionDateTo().ifPresent(builder::sonarLintLastConnectionDateTo);
request.getExternalLogin().ifPresent(builder::externalLogin);

if (managedInstanceService.isInstanceExternallyManaged()) {
String managedInstanceSql = Optional.ofNullable(request.isManaged())

+ 12
- 0
server/sonar-webserver-common/src/main/java/org/sonar/server/common/user/service/UsersSearchRequest.java 파일 보기

@@ -37,6 +37,7 @@ public class UsersSearchRequest {
private final OffsetDateTime lastConnectionDateTo;
private final OffsetDateTime sonarLintLastConnectionDateFrom;
private final OffsetDateTime sonarLintLastConnectionDateTo;
private final String externalLogin;

private UsersSearchRequest(Builder builder) {
this.page = builder.page;
@@ -44,6 +45,7 @@ public class UsersSearchRequest {
this.query = builder.query;
this.deactivated = builder.deactivated;
this.managed = builder.managed;
this.externalLogin = builder.externalLogin;
try {
this.lastConnectionDateFrom = Optional.ofNullable(builder.lastConnectionDateFrom).map(DateUtils::parseOffsetDateTime).orElse(null);
this.lastConnectionDateTo = Optional.ofNullable(builder.lastConnectionDateTo).map(DateUtils::parseOffsetDateTime).orElse(null);
@@ -92,6 +94,10 @@ public class UsersSearchRequest {
return Optional.ofNullable(sonarLintLastConnectionDateTo);
}

public Optional<String> getExternalLogin() {
return Optional.ofNullable(externalLogin);
}

public static Builder builder() {
return new Builder();
}
@@ -106,6 +112,7 @@ public class UsersSearchRequest {
private String lastConnectionDateTo;
private String sonarLintLastConnectionDateFrom;
private String sonarLintLastConnectionDateTo;
private String externalLogin;

private Builder() {
// enforce factory method use
@@ -156,6 +163,11 @@ public class UsersSearchRequest {
return this;
}

public Builder setExternalLogin(@Nullable String externalLogin) {
this.externalLogin = externalLogin;
return this;
}

public UsersSearchRequest build() {
return new UsersSearchRequest(this);
}

+ 53
- 8
server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java 파일 보기

@@ -65,6 +65,7 @@ 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;
import static org.sonar.server.user.ws.SearchAction.EXTERNAL_IDENTITY;
import static org.sonar.test.JsonAssert.assertJson;

public class SearchActionIT {
@@ -505,7 +506,7 @@ public class SearchActionIT {
assertThat(action).isNotNull();
assertThat(action.isPost()).isFalse();
assertThat(action.responseExampleAsString()).isNotEmpty();
assertThat(action.params()).hasSize(9);
assertThat(action.params()).hasSize(10);
}

@Test
@@ -569,13 +570,6 @@ public class SearchActionIT {
.forEach(SearchActionIT::assertForbiddenException);
}

private static void assertForbiddenException(TestRequest testRequest) {
assertThatThrownBy(() -> testRequest.executeProtobuf(SearchWsResponse.class))
.asInstanceOf(InstanceOfAssertFactories.type(ServerException.class))
.extracting(ServerException::httpCode)
.isEqualTo(403);
}

private void assertUserWithFilter(String field, Instant filterValue, String userLogin, boolean isExpectedToBeThere) {
var assertion = assertThat(ws.newRequest()
.setParam("q", "user-%_%-")
@@ -601,4 +595,55 @@ public class SearchActionIT {
);
}

@Test
public void search_whenFilteringOnExternalIdentityAndNotAdmin_shouldThrow() {
userSession.logIn();

TestRequest testRequest = ws.newRequest()
.setParam(EXTERNAL_IDENTITY, "login");

assertForbiddenException(testRequest);
}

private static void assertForbiddenException(TestRequest testRequest) {
assertThatThrownBy(() -> testRequest.executeProtobuf(SearchWsResponse.class))
.asInstanceOf(InstanceOfAssertFactories.type(ServerException.class))
.extracting(ServerException::httpCode)
.isEqualTo(403);
}

@Test
public void search_whenFilteringOnExternalIdentityAndMatch_shouldReturnMatchingUser() {
userSession.logIn().setSystemAdministrator();

prepareUsersWithExternalLogin();

TestRequest testRequest = ws.newRequest()
.setParam(EXTERNAL_IDENTITY, "user1");

assertThat(testRequest.executeProtobuf(SearchWsResponse.class).getUsersList())
.extracting(User::getExternalIdentity)
.containsExactly("user1");
}

@Test
public void search_whenFilteringOnExternalIdentityAndNoMatch_shouldReturnMatchingUser() {
userSession.logIn().setSystemAdministrator();

prepareUsersWithExternalLogin();

TestRequest testRequest = ws.newRequest()
.setParam(EXTERNAL_IDENTITY, "nomatch");

assertThat(testRequest.executeProtobuf(SearchWsResponse.class).getUsersList())
.extracting(User::getExternalIdentity)
.isEmpty();
}

private void prepareUsersWithExternalLogin() {
db.users().insertUser(user -> user.setExternalLogin("user1"));
db.users().insertUser(user -> user.setExternalLogin("USER1"));
db.users().insertUser(user -> user.setExternalLogin("user1-oldaccount"));
}

}

+ 15
- 5
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java 파일 보기

@@ -42,16 +42,17 @@ import static org.sonar.server.common.PaginationInformation.forPageIndex;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class SearchAction implements UsersWsAction {
private static final int MAX_PAGE_SIZE = 500;

private static final String DEACTIVATED_PARAM = "deactivated";
private static final String MANAGED_PARAM = "managed";

private static final int MAX_PAGE_SIZE = 500;
static final String LAST_CONNECTION_DATE_FROM = "lastConnectedAfter";
static final String LAST_CONNECTION_DATE_TO = "lastConnectedBefore";
static final String SONAR_LINT_LAST_CONNECTION_DATE_FROM = "slLastConnectedAfter";
static final String SONAR_LINT_LAST_CONNECTION_DATE_TO = "slLastConnectedBefore";
private final UserSession userSession;
static final String EXTERNAL_IDENTITY = "externalIdentity";

private final UserSession userSession;
private final UserService userService;
private final SearchWsReponseGenerator searchWsReponseGenerator;

@@ -79,6 +80,7 @@ public class SearchAction implements UsersWsAction {
"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.3", "New optional parameters " + EXTERNAL_IDENTITY + " to find a user by its IdP login"),
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. Only available with Administer System permission."),
new Change("10.1", "New optional parameters " + LAST_CONNECTION_DATE_FROM +
@@ -136,7 +138,7 @@ public class SearchAction implements UsersWsAction {
action.createParam(SONAR_LINT_LAST_CONNECTION_DATE_FROM)
.setSince("10.1")
.setDescription("""
Filter the users based on the sonar lint last connection date field
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)
@@ -151,6 +153,12 @@ public class SearchAction implements UsersWsAction {
.setRequired(false)
.setDefaultValue(null)
.setExampleValue(dateExample);
action.createParam(EXTERNAL_IDENTITY)
.setSince("10.3")
.setDescription("""
Find a user by its external identity (ie. its login in the Identity Provider).
This is case sensitive and only available with Administer System permission.
""");
}

@Override
@@ -166,6 +174,7 @@ public class SearchAction implements UsersWsAction {
throwIfParameterValuePresent(request, LAST_CONNECTION_DATE_TO);
throwIfParameterValuePresent(request, SONAR_LINT_LAST_CONNECTION_DATE_FROM);
throwIfParameterValuePresent(request, SONAR_LINT_LAST_CONNECTION_DATE_TO);
throwIfParameterValuePresent(request, EXTERNAL_IDENTITY);
}
}

@@ -176,7 +185,7 @@ public class SearchAction implements UsersWsAction {
return searchWsReponseGenerator.toUsersForResponse(userSearchResults.searchResults(), paging);
}

private UsersSearchRequest toSearchRequest(Request request) {
private static UsersSearchRequest toSearchRequest(Request request) {
int pageSize = request.mandatoryParamAsInt(PAGE_SIZE);
checkArgument(pageSize <= MAX_PAGE_SIZE, "The '%s' parameter must be less than %s", PAGE_SIZE, MAX_PAGE_SIZE);
return UsersSearchRequest.builder()
@@ -187,6 +196,7 @@ public class SearchAction implements UsersWsAction {
.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))
.setExternalLogin(request.param(EXTERNAL_IDENTITY))
.setPage(request.mandatoryParamAsInt(PAGE))
.setPageSize(pageSize)
.build();

Loading…
취소
저장