Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,15 @@ public ResponseEntity<Void> recordConnectionAttempt(@PathVariable("sub") String
@GetMapping(value = "/users/{sub}/profile")
@Operation(summary = "Get the user's profile")
@ApiResponse(responseCode = "200", description = "The user profile")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<UserProfile> getUserProfile(@PathVariable("sub") String sub) {
return ResponseEntity.of(service.getUserProfile(sub));
return ResponseEntity.ok(service.getUserProfile(sub));
}

@GetMapping(value = "/users/{sub}/groups")
@Operation(summary = "Get the user's groups")
@ApiResponse(responseCode = "200", description = "The user groups")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<List<UserGroup>> getUserGroups(@PathVariable("sub") String sub) {
return ResponseEntity.of(service.getUserGroups(sub));
return ResponseEntity.ok(service.getUserGroups(sub));
}

@GetMapping(value = "/users/{sub}/profile/max-cases")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ public UserInfoController(UserInfosService userInfosService) {
@GetMapping(value = "/{sub}/detail", produces = "application/json")
@Operation(summary = "get detailed user information")
@ApiResponse(responseCode = "200", description = "The user exist")
@ApiResponse(responseCode = "404", description = "The user doesn't exist")
public ResponseEntity<UserInfos> getUserDetail(@PathVariable("sub") String sub) {
return ResponseEntity.of(userInfosService.getUserInfo(sub));
return ResponseEntity.ok(userInfosService.getUserInfo(sub));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,24 @@ public record UserProfile(
UUID spreadsheetConfigCollectionId,
UUID networkVisualizationParameterId,
UUID diagramConfigId
) { }
) {
public static final String DEFAULT_PROFILE_NAME = "default profile";

public static UserProfile createDefaultProfile(Integer maxAllowedCases, Integer maxAllowedBuilds) {
return new UserProfile(
null,
DEFAULT_PROFILE_NAME,
null,
null,
null,
null,
null,
null,
maxAllowedCases,
maxAllowedBuilds,
null,
null,
null
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package org.gridsuite.useradmin.server.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
Expand All @@ -30,6 +32,7 @@
@Service
public class DirectoryService {

private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryService.class);
private static final String DIRECTORY_SERVER_API_VERSION = "v1";

private static final String DELIMITER = "/";
Expand Down Expand Up @@ -73,6 +76,12 @@ public Set<UUID> getExistingElements(Set<UUID> elementsUuids, String userId) {

public Integer getCasesCount(String userId) {
String path = UriComponentsBuilder.fromPath(USER_SERVER_ROOT_PATH + "/{userId}/cases/count").buildAndExpand(userId).toUriString();
return restTemplate.getForObject(directoryServerBaseUri + path, Integer.class);
try {
return restTemplate.getForObject(directoryServerBaseUri + path, Integer.class);
} catch (Exception e) {
LOGGER.warn("Failed to retrieve cases count for user {}", userId);
return null;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,31 +150,54 @@ public Optional<UserInfos> getUser(String sub) {
}

@Transactional(readOnly = true)
public Optional<UserProfile> getUserProfile(String sub) {
public UserProfile getUserProfile(String sub) {
return doGetUserProfile(sub);
}

private Optional<UserProfile> doGetUserProfile(String sub) {
private UserProfile doGetUserProfile(String sub) {
// this method is not restricted to Admin because it is called by any user to retrieve its own profile
UserInfosEntity user = userInfosRepository.findBySub(sub).orElseThrow(() -> new UserAdminException(NOT_FOUND));
return user.getProfile() == null ? Optional.empty() : userProfileService.getProfile(user.getProfile().getId());
Optional<UserInfosEntity> userOpt = userInfosRepository.findBySub(sub);

if (userOpt.isEmpty()) {
return createDefaultProfile();
}

UserInfosEntity user = userOpt.get();

if (user.getProfile() == null) {
return createDefaultProfile();
}

return userProfileService.getProfile(user.getProfile().getId())
.orElseGet(this::createDefaultProfile);
}

@Transactional(readOnly = true)
public Optional<List<UserGroup>> getUserGroups(String sub) {
// this method is not restricted to Admin because it is called by any user to retrieve its own profile
UserInfosEntity user = userInfosRepository.findBySub(sub).orElseThrow(() -> new UserAdminException(NOT_FOUND));
return user.getGroups() == null ?
Optional.empty() :
Optional.of(user.getGroups().stream().map(g -> userGroupService.getGroup(g.getId()))
public List<UserGroup> getUserGroups(String sub) {
// this method is not restricted to Admin because it is called by any user to retrieve its own groups
Optional<UserInfosEntity> userOpt = userInfosRepository.findBySub(sub);

if (userOpt.isEmpty()) {
return List.of();
}

UserInfosEntity user = userOpt.get();

if (user.getGroups() == null || user.getGroups().isEmpty()) {
return List.of();
}

return user.getGroups().stream()
.map(g -> userGroupService.getGroup(g.getId()))
.filter(Optional::isPresent)
.map(Optional::get).toList());
.map(Optional::get)
.toList();
}

@Transactional(readOnly = true)
public Integer getUserProfileMaxAllowedCases(String sub) {
return doGetUserProfile(sub)
.map(UserProfile::maxAllowedCases)
UserProfile profile = doGetUserProfile(sub);
return Optional.ofNullable(profile.maxAllowedCases())
.orElse(applicationProps.getDefaultMaxAllowedCases());
}

Expand All @@ -184,8 +207,15 @@ public Integer getCasesAlertThreshold() {

@Transactional(readOnly = true)
public Integer getUserProfileMaxAllowedBuilds(String sub) {
return doGetUserProfile(sub)
.map(UserProfile::maxAllowedBuilds)
UserProfile profile = doGetUserProfile(sub);
return Optional.ofNullable(profile.maxAllowedBuilds())
.orElse(applicationProps.getDefaultMaxAllowedBuilds());
}

private UserProfile createDefaultProfile() {
return UserProfile.createDefaultProfile(
applicationProps.getDefaultMaxAllowedCases(),
applicationProps.getDefaultMaxAllowedBuilds()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.util.Objects;
import java.util.Optional;
import java.util.Set;

@Service
public class UserInfosService {
Expand Down Expand Up @@ -39,15 +40,25 @@ public UserInfos toDtoUserInfo(final UserInfosEntity userInfosEntity, Integer ca
}

@Transactional(readOnly = true)
public Optional<UserInfos> getUserInfo(String sub) {
public UserInfos getUserInfo(String sub) {
Optional<UserInfosEntity> userInfosEntity = getUserInfosEntity(sub);
// get number of cases used
Integer casesUsed = directoryService.getCasesCount(sub);
if (userInfosEntity.isPresent()) {
// get number of cases used
Integer casesUsed = directoryService.getCasesCount(userInfosEntity.get().getSub());

return Optional.of(toDtoUserInfo(userInfosEntity.get(), casesUsed));
return toDtoUserInfo(userInfosEntity.get(), casesUsed);
}
return Optional.empty();
return createDefaultUserInfo(sub, casesUsed);
}

private UserInfos createDefaultUserInfo(String sub, Integer casesUsed) {
return new UserInfos(
sub,
null,
applicationProps.getDefaultMaxAllowedCases(),
casesUsed,
applicationProps.getDefaultMaxAllowedBuilds(),
Set.of()
);
}

private Optional<UserInfosEntity> getUserInfosEntity(String sub) {
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/config/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ useradmin:
cron:
announcement-check: 0 */1 * * * *
announcement-clean: 0 0 2 * * ?
# defaultMaxAllowedCases: 20 # Default allowed cases for a user (set here for testing purposes)
# defaultMaxAllowedBuilds: 10 # Default allowed builds for a user (set here for testing purposes)
# casesAlertThreshold: 90 # Default usage threshold (percentage) when user gets a warning when uploading cases (set here for testing purposes)
defaultMaxAllowedCases: 20 # Default allowed cases for a user
defaultMaxAllowedBuilds: 20 # Default allowed builds for a user
casesAlertThreshold: 90 # Default usage threshold (percentage) when user gets a warning when uploading cases
19 changes: 18 additions & 1 deletion src/test/java/org/gridsuite/useradmin/server/UserAdminTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class UserAdminTest {
@Autowired
private ConnectionRepository connectionRepository;

@Autowired
private UserAdminApplicationProps userAdminApplicationProps;

@AfterEach
void cleanDB() {
userGroupRepository.deleteAll();
Expand Down Expand Up @@ -251,7 +254,21 @@ void testUpdateUserForbidden() throws Exception {

@Test
void testGetUserProfileNotFound() throws Exception {
getUserProfile("BadUser", HttpStatus.NOT_FOUND);
UserProfile profile = getUserProfile("BadUser", HttpStatus.OK);
assertNotNull(profile);
assertEquals(UserProfile.DEFAULT_PROFILE_NAME, profile.name());
assertNull(profile.id());
assertEquals(userAdminApplicationProps.getDefaultMaxAllowedCases(), profile.maxAllowedCases());
assertEquals(userAdminApplicationProps.getDefaultMaxAllowedBuilds(), profile.maxAllowedBuilds());
assertNull(profile.loadFlowParameterId());
assertNull(profile.securityAnalysisParameterId());
assertNull(profile.sensitivityAnalysisParameterId());
assertNull(profile.shortcircuitParameterId());
assertNull(profile.voltageInitParameterId());
assertNull(profile.allLinksValid());
assertNull(profile.spreadsheetConfigCollectionId());
assertNull(profile.networkVisualizationParameterId());
assertNull(profile.diagramConfigId());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private UserInfos getUserInfos(String userName) throws Exception {
new TypeReference<>() { });
}

private Set<UserGroup> getUserGroups(String userName) throws Exception {
private List<UserGroup> getUserGroups(String userName) throws Exception {
return objectMapper.readValue(
mockMvc.perform(get(API_BASE_PATH + "/users/" + userName + "/groups")
.contentType(APPLICATION_JSON))
Expand Down Expand Up @@ -212,7 +212,7 @@ void testGroups() throws Exception {
checkUserGroup(USER_B, null);
checkUserGroup(USER_C, null);

Set<UserGroup> userGroups = getUserGroups(USER_E);
List<UserGroup> userGroups = getUserGroups(USER_E);
assertEquals(Set.of(GROUP_NEW_NAME), userGroups.stream().map(UserGroup::name).collect(Collectors.toSet()));

// delete group : error because of users still referencing it
Expand All @@ -233,4 +233,11 @@ void testGroups() throws Exception {
checkUserGroup(USER_D, null);
checkUserGroup(USER_E, null);
}

@Test
void testGetGroupsForNonExistentUser() throws Exception {
List<UserGroup> groups = getUserGroups("nonExistentUser");
assertNotNull(groups);
assertTrue(groups.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
import java.util.Optional;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

/**
Expand Down Expand Up @@ -62,12 +61,28 @@ void toDtoUserInfoTest() {
UserInfosEntity user = new UserInfosEntity(UUID.randomUUID(), "user_A", profile, null);

when(userInfosRepositoryMock.findBySub("user_A")).thenReturn(Optional.of(user));
Optional<UserInfos> userInfos = userInfosService.getUserInfo("user_A");
assertTrue(userInfos.isPresent());
assertEquals("user_A", userInfos.get().sub());
assertEquals("profile_A", userInfos.get().profileName());
assertEquals(5, userInfos.get().maxAllowedCases());
assertEquals(3, userInfos.get().numberCasesUsed());
assertEquals(6, userInfos.get().maxAllowedBuilds());
UserInfos userInfos = userInfosService.getUserInfo("user_A");
assertNotNull(userInfos);
assertEquals("user_A", userInfos.sub());
assertEquals("profile_A", userInfos.profileName());
assertEquals(5, userInfos.maxAllowedCases());
assertEquals(3, userInfos.numberCasesUsed());
assertEquals(6, userInfos.maxAllowedBuilds());
}

@Test
void getUserInfoForNonExistentUser() {
when(applicationPropsMock.getDefaultMaxAllowedCases()).thenReturn(20);
when(applicationPropsMock.getDefaultMaxAllowedBuilds()).thenReturn(10);
when(userInfosRepositoryMock.findBySub("nonExistent")).thenReturn(Optional.empty());

UserInfos userInfos = userInfosService.getUserInfo("nonExistent");
assertNotNull(userInfos);
assertEquals("nonExistent", userInfos.sub());
assertNull(userInfos.profileName());
assertEquals(20, userInfos.maxAllowedCases());
assertEquals(0, userInfos.numberCasesUsed());
assertEquals(10, userInfos.maxAllowedBuilds());
assertTrue(userInfos.groups().isEmpty());
}
}