@@ -625,7 +625,7 @@ export default class IssuesServiceMock { | |||
handleGetUsers = () => { | |||
return this.reply({ | |||
pageRestResponse: mockPaging(), | |||
page: mockPaging(), | |||
users: [mockLoggedInUser() as unknown as RestUser], | |||
}); | |||
}; |
@@ -127,7 +127,7 @@ export default class SecurityHotspotServiceMock { | |||
mockRestUser({ name: 'User Doe', login: 'user.doe' }), | |||
mockRestUser({ name: 'User Foo', login: 'user.foo' }), | |||
], | |||
pageRestResponse: mockPaging(), | |||
page: mockPaging(), | |||
}); | |||
}; | |||
@@ -207,14 +207,14 @@ export default class UsersServiceMock { | |||
}; | |||
handleGetUsers: typeof getUsers<RestUserDetailed> = (data) => { | |||
let pageRestResponse = { | |||
let page = { | |||
pageIndex: 1, | |||
pageSize: 0, | |||
total: 10, | |||
}; | |||
if (data.pageIndex !== undefined && data.pageIndex !== pageRestResponse.pageIndex) { | |||
pageRestResponse = { pageIndex: 2, pageSize: 2, total: 10 }; | |||
if (data.pageIndex !== undefined && data.pageIndex !== page.pageIndex) { | |||
page = { pageIndex: 2, pageSize: 2, total: 10 }; | |||
const users = [ | |||
mockRestUser({ | |||
name: `Local User ${this.users.length + 4}`, | |||
@@ -226,7 +226,7 @@ export default class UsersServiceMock { | |||
}), | |||
]; | |||
return this.reply({ pageRestResponse, users }); | |||
return this.reply({ page, users }); | |||
} | |||
const users = this.getFilteredRestUsers({ | |||
@@ -239,7 +239,7 @@ export default class UsersServiceMock { | |||
}); | |||
return this.reply({ | |||
pageRestResponse: { | |||
page: { | |||
pageIndex: 1, | |||
pageSize: users.length, | |||
total: 10, |
@@ -92,7 +92,7 @@ export function getUsers<T extends RestUserBase>(data: { | |||
sonarLintLastConnectionDateTo?: string; | |||
pageSize?: number; | |||
pageIndex?: number; | |||
}): Promise<{ pageRestResponse: Paging; users: T[] }> { | |||
}): Promise<{ page: Paging; users: T[] }> { | |||
return getJSON('/api/v2/users', data).catch(throwGlobalError); | |||
} | |||
@@ -237,8 +237,8 @@ export const searchAssignees = ( | |||
query: string, | |||
page = 1 | |||
): Promise<{ paging: Paging; results: RestUser[] }> => { | |||
return getUsers<RestUser>({ pageIndex: page, q: query }).then(({ pageRestResponse, users }) => ({ | |||
paging: pageRestResponse, | |||
return getUsers<RestUser>({ pageIndex: page, q: query }).then(({ page, users }) => ({ | |||
paging: page, | |||
results: users, | |||
})); | |||
}; |
@@ -52,7 +52,7 @@ export function useUsersQueries<U extends RestUserBase>( | |||
return results.reduce( | |||
(acc, { data, isLoading }) => ({ | |||
users: acc.users.concat(data?.users ?? []), | |||
total: data?.pageRestResponse.total, | |||
total: data?.page.total, | |||
isLoading: acc.isLoading || isLoading, | |||
}), | |||
{ users: [] as U[], total: 0, isLoading: false } |
@@ -52,15 +52,15 @@ public interface UserController { | |||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) | |||
@ResponseStatus(HttpStatus.OK) | |||
@Operation(summary = "Users search", description = """ | |||
Get a list of users. By default, only active users are returned.<br> | |||
Get a list of users. By default, only active users are returned. | |||
The following fields are only returned when user has Administer System permission or for logged-in in user : | |||
'email' | |||
'externalIdentity' | |||
'externalProvider' | |||
'groups' | |||
'lastConnectionDate' | |||
'sonarLintLastConnectionDate' | |||
'tokensCount'<br> | |||
'email', | |||
'externalIdentity', | |||
'externalProvider', | |||
'groups', | |||
'lastConnectionDate', | |||
'sonarLintLastConnectionDate', | |||
'tokensCount'. | |||
Field 'sonarqubeLastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour. | |||
""") | |||
UsersSearchRestResponse search( |
@@ -19,5 +19,8 @@ | |||
*/ | |||
package org.sonar.server.v2.api.user.model; | |||
import io.swagger.v3.oas.annotations.media.Schema; | |||
@Schema(implementation = RestUserForAdmins.class) | |||
public interface RestUser { | |||
} |
@@ -20,6 +20,10 @@ | |||
package org.sonar.server.v2.api.user.controller; | |||
import com.google.gson.Gson; | |||
import com.google.gson.GsonBuilder; | |||
import com.google.gson.TypeAdapter; | |||
import com.google.gson.stream.JsonReader; | |||
import com.google.gson.stream.JsonWriter; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Optional; | |||
@@ -71,7 +75,7 @@ public class DefaultUserControllerTest { | |||
private final UsersSearchRestResponseGenerator responseGenerator = mock(UsersSearchRestResponseGenerator.class); | |||
private final MockMvc mockMvc = ControllerTester.getMockMvc(new DefaultUserController(userSession, userService, responseGenerator)); | |||
private static final Gson gson = new Gson(); | |||
private static final Gson gson = new GsonBuilder().registerTypeAdapter(RestUser.class, new RestUserDeserializer()).create(); | |||
@Test | |||
public void search_whenNoParameters_shouldUseDefaultAndForwardToUserService() throws Exception { | |||
@@ -114,25 +118,25 @@ public class DefaultUserControllerTest { | |||
@Test | |||
public void search_whenAdminParametersUsedButNotAdmin_shouldFail() throws Exception { | |||
mockMvc.perform(get(USER_ENDPOINT) | |||
.param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100")) | |||
.param("sonarQubeLastConnectionDateFrom", "2020-01-01T00:00:00+0100")) | |||
.andExpectAll( | |||
status().isForbidden(), | |||
content().string("{\"message\":\"parameter sonarQubeLastConnectionDateFrom requires Administer System permission.\"}")); | |||
mockMvc.perform(get(USER_ENDPOINT) | |||
.param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100")) | |||
.param("sonarQubeLastConnectionDateTo", "2020-01-01T00:00:00+0100")) | |||
.andExpectAll( | |||
status().isForbidden(), | |||
content().string("{\"message\":\"parameter sonarQubeLastConnectionDateTo requires Administer System permission.\"}")); | |||
mockMvc.perform(get(USER_ENDPOINT) | |||
.param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100")) | |||
.param("sonarLintLastConnectionDateFrom", "2020-01-01T00:00:00+0100")) | |||
.andExpectAll( | |||
status().isForbidden(), | |||
content().string("{\"message\":\"parameter sonarLintLastConnectionDateFrom requires Administer System permission.\"}")); | |||
mockMvc.perform(get(USER_ENDPOINT) | |||
.param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100")) | |||
.param("sonarLintLastConnectionDateTo", "2020-01-01T00:00:00+0100")) | |||
.andExpectAll( | |||
status().isForbidden(), | |||
content().string("{\"message\":\"parameter sonarLintLastConnectionDateTo requires Administer System permission.\"}")); | |||
@@ -162,6 +166,19 @@ public class DefaultUserControllerTest { | |||
} | |||
static class RestUserDeserializer extends TypeAdapter<RestUser> { | |||
@Override | |||
public void write(JsonWriter out, RestUser value) { | |||
throw new IllegalStateException("not implemented"); | |||
} | |||
@Override | |||
public RestUser read(JsonReader reader) { | |||
return gson.fromJson(reader, RestUserForAdmins.class); | |||
} | |||
} | |||
private UserSearchResult generateUserSearchResult(String id, boolean active, boolean local, boolean managed, int groupsCount, int tokensCount) { | |||
UserDto userDto = new UserDto() | |||
.setLogin("login_" + id) | |||
@@ -311,9 +328,9 @@ public class DefaultUserControllerTest { | |||
userSession.logIn().setNonSystemAdministrator(); | |||
mockMvc.perform( | |||
post(USER_ENDPOINT) | |||
.contentType(MediaType.APPLICATION_JSON_VALUE) | |||
.content(gson.toJson(new UserCreateRestRequest(null, null, "login", "name", null, null)))) | |||
post(USER_ENDPOINT) | |||
.contentType(MediaType.APPLICATION_JSON_VALUE) | |||
.content(gson.toJson(new UserCreateRestRequest(null, null, "login", "name", null, null)))) | |||
.andExpectAll( | |||
status().isForbidden(), | |||
content().json("{\"message\":\"Insufficient privileges\"}")); |