diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java index c4238a5..1d13327 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/controller/external/UserController.java @@ -1,6 +1,5 @@ package com.knecon.fforesight.tenantusermanagement.controller.external; -import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.DELETE_TENANT; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_ALL_USERS; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_USERS; import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_MY_PROFILE; @@ -26,6 +25,7 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.User; +import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles; import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties; import com.knecon.fforesight.tenantusermanagement.service.UserService; @@ -41,9 +41,6 @@ public class UserController implements UserResource, PublicResource { private final UserService userService; private final TenantUserManagementProperties tenantUserManagementProperties; - private static final String KNECON_ADMIN_ROLE = "KNECON_ADMIN"; - private static final String KNECON_SUPPORT_ROLE = "KNECON_SUPPORT"; - @Override @PreAuthorize("hasAuthority('" + READ_USERS + "')") @@ -76,7 +73,7 @@ public class UserController implements UserResource, PublicResource { .filter(user -> { Set filteredRoles = user.getRoles() .stream() - .filter(role -> !role.equals(KNECON_ADMIN_ROLE) && !role.equals(KNECON_SUPPORT_ROLE)) + .filter(ApplicationRoles::isNoKneconRole) .collect(Collectors.toSet()); if (filteredRoles.isEmpty()) { @@ -142,7 +139,7 @@ public class UserController implements UserResource, PublicResource { Set filteredRoles = user.getRoles() .stream() - .filter(role -> !role.equals(KNECON_ADMIN_ROLE) && !role.equals(KNECON_SUPPORT_ROLE)) + .filter(ApplicationRoles::isNoKneconRole) .collect(Collectors.toSet()); if (filteredRoles.isEmpty()) { diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java new file mode 100644 index 0000000..e33faac --- /dev/null +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/permissions/ApplicationRoles.java @@ -0,0 +1,22 @@ +package com.knecon.fforesight.tenantusermanagement.permissions; + +import java.util.Set; + +public final class ApplicationRoles { + + public static final String KNECON_ADMIN_ROLE = "KNECON_ADMIN"; + public static final String KNECON_SUPPORT_ROLE = "KNECON_SUPPORT"; + + private static final Set KNECON_ROLES = Set.of(KNECON_ADMIN_ROLE, KNECON_SUPPORT_ROLE); + + public static boolean isNoKneconRole(String role) { + + return !KNECON_ROLES.contains(role); + } + + public static boolean isKneconRole(String role) { + + return KNECON_ROLES.contains(role); + } + +} diff --git a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java index c83863c..d02c977 100644 --- a/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java +++ b/src/main/java/com/knecon/fforesight/tenantusermanagement/service/UserService.java @@ -39,6 +39,7 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.User; +import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles; import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties; import io.micrometer.common.util.StringUtils; @@ -78,7 +79,7 @@ public class UserService { String username = StringUtils.isEmpty(user.getUsername()) ? user.getEmail() : user.getUsername(); if (!this.getTenantUsersResource().search(username, true).isEmpty() || !this.getTenantUsersResource().searchByEmail(user.getEmail(), true).isEmpty()) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this username or email address already exists"); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Requested username or email address not available"); } if (!EmailValidator.getInstance().isValid(user.getEmail())) { @@ -165,9 +166,13 @@ public class UserService { var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId()); - var userWithNewRoles = setRoles(userId, roles, currentUserRoles); + if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && roles.stream() + .anyMatch(ApplicationRoles::isKneconRole) && currentUserRoles.stream() + .noneMatch(ApplicationRoles::isKneconRole)) { + throw new NotFoundException("User with id: " + userId + " does not exist"); + } - return userWithNewRoles; + return setRoles(userId, roles, currentUserRoles); } @@ -176,7 +181,9 @@ public class UserService { var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles(); newRoles.forEach(role -> { - if (!allRoles.contains(role)) { + + if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role) && currentUserRoles.stream() + .noneMatch(ApplicationRoles::isKneconRole)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role); } }); @@ -236,7 +243,7 @@ public class UserService { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights"); } - if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) { + if (userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self."); } } @@ -412,10 +419,12 @@ public class UserService { return; } - var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId); + var status = validateExecution(KeycloakSecurity.getUserId(), userId); - if (!executionValid) { + if (status.equals(ValidationStatus.FORBIDDEN)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles"); + } else if (status.equals(ValidationStatus.INVALID)) { + return; } var userResource = getUserResource(userId); @@ -434,6 +443,11 @@ public class UserService { var user = this.getUserResource(userId); var userRepresentation = user.toRepresentation(); + if (getRoles(userId).stream() + .anyMatch(ApplicationRoles::isKneconRole)) { + throw new NotFoundException("User with id: " + userId + " does not exist"); + } + if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail() .equals(userRepresentation.getEmail())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to change the email from a federated identity"); @@ -462,10 +476,14 @@ public class UserService { public User activateProfile(String userId, boolean isActive) { - var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId); + if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) { + var status = validateExecution(KeycloakSecurity.getUserId(), userId); - if (!executionValid) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles"); + if (status.equals(ValidationStatus.FORBIDDEN)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles"); + } else if (status.equals(ValidationStatus.INVALID)) { + throw new NotFoundException("User with id: " + userId + " does not exist"); + } } var user = this.getUserResource(userId); @@ -491,10 +509,14 @@ public class UserService { public void resetPassword(String userId, ResetPasswordRequest resetPasswordRequest) { - var executionValid = isExecutionRankValid(KeycloakSecurity.getUserId(), userId); + if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) { + var status = validateExecution(KeycloakSecurity.getUserId(), userId); - if (!executionValid) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles"); + if (status.equals(ValidationStatus.FORBIDDEN)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles"); + } else if (status.equals(ValidationStatus.INVALID)) { + throw new NotFoundException("User with id: " + userId + " does not exist"); + } } try { @@ -542,13 +564,24 @@ public class UserService { } - private boolean isExecutionRankValid(String executingUserId, String targetUserId) { + private enum ValidationStatus { + ALLOWED, + FORBIDDEN, + INVALID + } + + + private ValidationStatus validateExecution(String executingUserId, String targetUserId) { var currentUserResource = getUserResource(executingUserId); var currentRoles = getRoles(currentUserResource.toRepresentation().getId()); - var userRoles = getRoles(targetUserId); + if (currentRoles.stream() + .anyMatch(ApplicationRoles::isKneconRole)) { + return ValidationStatus.INVALID; + } + var roleMapping = tenantUserManagementProperties.getKcRoleMapping(); var maxRank = currentRoles.stream() .map(r -> roleMapping.getRole(r).getRank()) @@ -559,8 +592,11 @@ public class UserService { .max(Integer::compare) .orElse(-1); - return targetRank <= maxRank; - + if (targetRank <= maxRank) { + return ValidationStatus.ALLOWED; + } else { + return ValidationStatus.FORBIDDEN; + } } } diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 40d75a2..288c733 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -30,6 +30,44 @@ fforesight: - 'fforesight-write-smtp-configuration' - 'fforesight-read-identity-provider-config' - 'fforesight-write-identity-provider-config' + - name: KNECON_ADMIN + set-by-default: true + rank: 1000 + permissions: + - 'fforesight-read-general-configuration' + - 'fforesight-write-general-configuration' + - 'fforesight-manage-user-preferences' + - 'fforesight-read-users' + - 'fforesight-read-all-users' + - 'fforesight-write-users' + - 'fforesight-update-my-profile' + - 'fforesight-create-tenant' + - 'fforesight-get-tenants' + - 'fforesight-update-tenant' + - 'fforesight-deployment-info' + - 'fforesight-read-smtp-configuration' + - 'fforesight-write-smtp-configuration' + - 'fforesight-read-identity-provider-config' + - 'fforesight-write-identity-provider-config' + - name: KNECON_SUPPORT + set-by-default: true + rank: 1000 + permissions: + - 'fforesight-read-general-configuration' + - 'fforesight-write-general-configuration' + - 'fforesight-manage-user-preferences' + - 'fforesight-read-users' + - 'fforesight-read-all-users' + - 'fforesight-write-users' + - 'fforesight-update-my-profile' + - 'fforesight-create-tenant' + - 'fforesight-get-tenants' + - 'fforesight-update-tenant' + - 'fforesight-deployment-info' + - 'fforesight-read-smtp-configuration' + - 'fforesight-write-smtp-configuration' + - 'fforesight-read-identity-provider-config' + - 'fforesight-write-identity-provider-config' application-name: "redaction" springdoc: auth-server-url: http://localhost:8080 diff --git a/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java b/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java index 4b4d250..8c3113e 100644 --- a/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java +++ b/src/test/java/com/knecon/fforesight/tenantusermanagement/tests/UserTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -21,6 +22,8 @@ import com.knecon.fforesight.tenantusermanagement.model.CreateUserRequest; import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest; import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest; +import com.knecon.fforesight.tenantusermanagement.model.User; +import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles; import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties; import feign.FeignException; @@ -42,33 +45,41 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { tokenService.setUser("test@fforesight.com", "secret"); var allUsers = userClient.getAllUsers(true); - var testUserFound = allUsers.stream().anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")); + var testUserFound = allUsers.stream() + .anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")); assertThat(allUsers).isNotEmpty(); assertThat(testUserFound).isTrue(); allUsers = userClient.getApplicationSpecificUsers(true); - testUserFound = allUsers.stream().anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")); + testUserFound = allUsers.stream() + .anyMatch(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")); assertThat(allUsers).isNotEmpty(); assertThat(testUserFound).isTrue(); - var testUserId = allUsers.iterator().next().getUserId(); - var testUser = userClient.getUserById(testUserId); + var optionalUser = allUsers.stream().filter(user -> user.getUsername().equalsIgnoreCase("test@fforesight.com")).findFirst(); + assert(optionalUser.isPresent()); + var testUser = userClient.getUserById(optionalUser.get().getUserId()); assertThat(testUser).isNotNull(); testUser = userClient.updateMyProfile(UpdateMyProfileRequest.builder() - .email("test@fforesight.com") - .firstName("updateTestFirstName") - .lastName("updateTestLastName") - .build()); + .email("test@fforesight.com") + .firstName("updateTestFirstName") + .lastName("updateTestLastName") + .build()); assertThat(testUser.getLastName()).isEqualTo("updateTestLastName"); assertThat(testUser.getFirstName()).isEqualTo("updateTestFirstName"); + + Set allButKneconRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles().stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet()); + CreateUserRequest createUserRequest = new CreateUserRequest(); createUserRequest.setEmail("test.new.user@knecon.com"); createUserRequest.setFirstName("Test"); createUserRequest.setLastName("New User"); createUserRequest.setUsername(createUserRequest.getEmail()); - createUserRequest.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest.setRoles(allButKneconRoles); var createdUser = userClient.createUser(createUserRequest); allUsers = userClient.getAllUsers(true); @@ -84,13 +95,14 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { assertThat(createdUser.getRoles()).isEmpty(); createdUser = userClient.updateProfile(createdUser.getUserId(), - UpdateProfileRequest.builder() - .email("test.new.user@knecon.com") - .firstName("update test") - .lastName("update test") - .roles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()) - .build()); - assertThat(createdUser.getRoles()).containsExactly(tenantUserManagementProperties.getKcRoleMapping().getAllRoles().toArray(new String[0])); + UpdateProfileRequest.builder() + .email("test.new.user@knecon.com") + .firstName("update test") + .lastName("update test") + .roles(allButKneconRoles) + .build()); + assertThat(createdUser.getRoles()).containsExactly(allButKneconRoles + .toArray(new String[0])); userClient.deleteUsers(List.of(createdUser.getUserId())); @@ -133,7 +145,10 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { userClient.resetPassword(createdUser2.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!2345").build()); var allUsers = userClient.getAllUsers(true); - var initialSuperUser = allUsers.stream().filter(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")).findAny().get(); + var initialSuperUser = allUsers.stream() + .filter(u -> u.getEmail().equalsIgnoreCase("test@fforesight.com")) + .findAny() + .get(); try { //reset password as less-super-user for super-user userClient.resetPassword(initialSuperUser.getUserId(), ResetPasswordRequest.builder().password("ShouldNotWork@123").build()); @@ -223,6 +238,7 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { TenantContext.clear(); } + @Test @SneakyThrows public void testActivateUserWithSameRole() { @@ -283,13 +299,16 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { public void testCreateUserWithExistingUser() { TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID); + Set allButKneconRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles().stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet()); var createUserRequest = new CreateUserRequest(); createUserRequest.setEmail("existinguser@knecon.com"); createUserRequest.setFirstName("Existing"); createUserRequest.setLastName("User"); createUserRequest.setUsername("ExistingUser"); - createUserRequest.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest.setRoles(allButKneconRoles); userClient.createUser(createUserRequest); var createUserRequest2 = new CreateUserRequest(); @@ -297,46 +316,187 @@ public class UserTest extends AbstractTenantUserManagementIntegrationTest { createUserRequest2.setFirstName("New"); createUserRequest2.setLastName("User"); createUserRequest2.setUsername("NewUser"); - createUserRequest2.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest2.setRoles(allButKneconRoles); FeignException e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest2)); - assertEquals(409, e.status()); + assertEquals(400, e.status()); var createUserRequest3 = new CreateUserRequest(); createUserRequest3.setEmail("newuser@knecon.com"); createUserRequest3.setFirstName("New"); createUserRequest3.setLastName("User"); createUserRequest3.setUsername("ExistingUser"); - createUserRequest3.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest3.setRoles(allButKneconRoles); e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest3)); - assertEquals(409, e.status()); + assertEquals(400, e.status()); var createUserRequest4 = new CreateUserRequest(); createUserRequest4.setEmail("existinguser@knecon.com"); createUserRequest4.setFirstName("New"); createUserRequest4.setLastName("User"); createUserRequest4.setUsername("ExistingUser"); - createUserRequest4.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest4.setRoles(allButKneconRoles); e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest4)); - assertEquals(409, e.status()); + assertEquals(400, e.status()); var createUserRequest5 = new CreateUserRequest(); createUserRequest5.setEmail("anotherexistinguser@knecon.com"); createUserRequest5.setFirstName("Another existing"); createUserRequest5.setLastName("User"); - createUserRequest5.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest5.setRoles(allButKneconRoles); userClient.createUser(createUserRequest5); var createUserRequest6 = new CreateUserRequest(); createUserRequest6.setEmail("anotherexistinguser@knecon.com"); createUserRequest6.setFirstName("Another new"); createUserRequest6.setLastName("User"); - createUserRequest6.setRoles(tenantUserManagementProperties.getKcRoleMapping().getAllRoles()); + createUserRequest6.setRoles(allButKneconRoles); e = assertThrows(FeignException.class, () -> userClient.createUser(createUserRequest6)); - assertEquals(409, e.status()); + assertEquals(400, e.status()); + + } + + + @Test + public void testHiddenKneconRoles() { + + // set context and user + TenantContext.setTenantId(AbstractTenantUserManagementIntegrationTest.TEST_TENANT_ID); + tokenService.setUser("admin@knecon.com", "secret"); + + // different role sets and subsets + var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles(); + Set allButKneconRoles = allRoles.stream() + .filter(ApplicationRoles::isNoKneconRole) + .collect(Collectors.toSet()); + + Set onlyKneconRoles = allRoles.stream() + .filter(ApplicationRoles::isKneconRole) + .collect(Collectors.toSet()); + + var sizeBefore = userClient.getAllUsers(true).size(); + + // create several users with different roles for testing + var createUserRequest = new CreateUserRequest(); + createUserRequest.setEmail("allroles@knecon.com"); + createUserRequest.setFirstName("All"); + createUserRequest.setLastName("Roles"); + createUserRequest.setUsername("AllRoles"); + createUserRequest.setRoles(allRoles); + User user = userClient.createUser(createUserRequest); + + var createUserRequest2 = new CreateUserRequest(); + createUserRequest2.setEmail("nokneconroles@notknecon.com"); + createUserRequest2.setFirstName("No Knecon"); + createUserRequest2.setLastName("Roles"); + createUserRequest2.setUsername("NoKneconRoles"); + createUserRequest2.setRoles(allButKneconRoles); + User noKneconUser = userClient.createUser(createUserRequest2); + + var createUserRequest3 = new CreateUserRequest(); + createUserRequest3.setEmail("onlykneconroles@notknecon.com"); + createUserRequest3.setFirstName("Only Knecon"); + createUserRequest3.setLastName("Roles"); + createUserRequest3.setUsername("OnlyKneconRoles"); + createUserRequest3.setRoles(onlyKneconRoles); + User onlyKneconUser = userClient.createUser(createUserRequest3); + + var allUsers = userClient.getAllUsers(true); + // one role is completely hidden + assertEquals(allUsers.size(), sizeBefore + 2); + + // no knecon roles are visible + assertEquals(allUsers.stream().filter(u -> u.getRoles().stream().anyMatch(ApplicationRoles::isKneconRole)).count(), 0); + + // we can get this user because of other roles being present + userClient.getUserById(noKneconUser.getUserId()); + + // but not this one as he only has knecon roles + var e = assertThrows(FeignException.class, () -> userClient.getUserById(onlyKneconUser.getUserId())); + assertEquals(404, e.status()); + + // switch token to the user without knecon roles + tokenService.setUser("test@fforesight.com", "secret"); + + // create another dummy user with only SUPER_USER + var redUserAdminUserRequest = new CreateUserRequest(); + redUserAdminUserRequest.setEmail("red-user-admin@knecon.com"); + redUserAdminUserRequest.setUsername(redUserAdminUserRequest.getEmail()); + redUserAdminUserRequest.setRoles(Set.of("SUPER_USER")); + var redUserAdmin = userClient.createUser(redUserAdminUserRequest); + + // reset password for authentication + userClient.resetPassword(redUserAdmin.getUserId(), ResetPasswordRequest.builder().password("Secret@secured!23").build()); + + // authenticate with the newly created user + tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23"); + + // we should not be able to set roles of this user at all as it is not visible to us resulting in a 404 + e = assertThrows(FeignException.class, () -> userClient.setRoles(user.getUserId(), onlyKneconRoles)); + assertEquals(404, e.status()); + + // we should not be able to assign ourselves a knecon role as it is not visible to us + e = assertThrows(FeignException.class, () -> userClient.setRoles(redUserAdmin.getUserId(), allRoles)); + assertEquals(400, e.status()); + + // authenticate as knecon admin again + tokenService.setUser("admin@knecon.com", "secret"); + + // this should be possible because we now have knecon roles + userClient.setRoles(onlyKneconUser.getUserId(), allRoles); + assertEquals(userClient.getUserById(onlyKneconUser.getUserId()).getRoles().size(), 2); + + // and this as well + userClient.setRoles(user.getUserId(), allRoles); + + // we can also poll the user + userClient.getUserById(user.getUserId()); + + // back to having no rights + tokenService.setUser("red-user-admin@knecon.com", "Secret@secured!23"); + + // we can not call update profile + e = assertThrows(FeignException.class, () -> userClient.updateProfile(user.getUserId(), new UpdateProfileRequest())); + assertEquals(404, e.status()); + + // or reset password as it is forbidden (higher rights) + e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), new ResetPasswordRequest())); + assertEquals(403, e.status()); + + // now as a knecon admin again + tokenService.setUser("admin@knecon.com", "secret"); + + // we can also not see another knecon account and change their password + e = assertThrows(FeignException.class, () -> userClient.resetPassword(user.getUserId(), new ResetPasswordRequest())); + assertEquals(404, e.status()); + + // or activate the profile + e = assertThrows(FeignException.class, () -> userClient.activateProfile(user.getUserId(), true)); + assertEquals(404, e.status()); + + // we create a new user with all roles + var createUserRequest4 = new CreateUserRequest(); + createUserRequest4.setEmail("allroles2@knecon.com"); + createUserRequest4.setFirstName("All"); + createUserRequest4.setLastName("Roles2"); + createUserRequest4.setUsername("AllRoles2"); + createUserRequest4.setRoles(allRoles); + User user4 = userClient.createUser(createUserRequest4); + + // we attempt to delete it + userClient.deleteUser(user4.getUserId()); + + // and again using the bulk call + userClient.deleteUsers(List.of(user4.getUserId())); + + // user should still be present despite deletes as it as a knecon user + assertEquals(allUsers.size(), sizeBefore + 2); + // and should not have changed + var user5 = userClient.getUserById(user4.getUserId()); + assertEquals(user4, user5); } diff --git a/src/test/java/com/knecon/fforesight/tenantusermanagement/utils/TestTenantService.java b/src/test/java/com/knecon/fforesight/tenantusermanagement/utils/TestTenantService.java index 558ece1..a1a06c6 100644 --- a/src/test/java/com/knecon/fforesight/tenantusermanagement/utils/TestTenantService.java +++ b/src/test/java/com/knecon/fforesight/tenantusermanagement/utils/TestTenantService.java @@ -20,6 +20,7 @@ import com.knecon.fforesight.tenantusermanagement.api.internal.InternalTenantsRe import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest; import com.knecon.fforesight.tenantusermanagement.model.TenantRequest; import com.knecon.fforesight.tenantusermanagement.model.TenantUser; +import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles; import com.knecon.fforesight.tenantusermanagement.testcontainers.MongoDBTestContainer; import com.knecon.fforesight.tenantusermanagement.testcontainers.SpringPostgreSQLTestContainer; @@ -57,7 +58,18 @@ public class TestTenantService { .tenantId(testTenantId) .displayName(testTenantId) .guid(UUID.randomUUID().toString()) - .defaultUsers(List.of(TenantUser.builder().roles(Set.of("SUPER_USER")).username("test@fforesight.com").password("secret").email("test@fforesight.com").build())) + .defaultUsers(List.of(TenantUser.builder() + .roles(Set.of("SUPER_USER")) + .username("test@fforesight.com") + .password("secret") + .email("test@fforesight.com") + .build(), + TenantUser.builder() + .roles(Set.of(ApplicationRoles.KNECON_ADMIN_ROLE, ApplicationRoles.KNECON_SUPPORT_ROLE)) + .username("admin@knecon.com") + .password("secret") + .email("admin@knecon.com") + .build())) .databaseConnection(DatabaseConnection.builder() .driver("postgresql") .host(SpringPostgreSQLTestContainer.getInstance().getHost()) diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index b13dffd..9787d80 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -144,6 +144,44 @@ fforesight: - 'fforesight-write-smtp-configuration' - 'fforesight-read-identity-provider-config' - 'fforesight-write-identity-provider-config' + - name: KNECON_ADMIN + set-by-default: true + rank: 1000 + permissions: + - 'fforesight-read-general-configuration' + - 'fforesight-write-general-configuration' + - 'fforesight-manage-user-preferences' + - 'fforesight-read-users' + - 'fforesight-read-all-users' + - 'fforesight-write-users' + - 'fforesight-update-my-profile' + - 'fforesight-create-tenant' + - 'fforesight-get-tenants' + - 'fforesight-update-tenant' + - 'fforesight-deployment-info' + - 'fforesight-read-smtp-configuration' + - 'fforesight-write-smtp-configuration' + - 'fforesight-read-identity-provider-config' + - 'fforesight-write-identity-provider-config' + - name: KNECON_SUPPORT + set-by-default: true + rank: 1000 + permissions: + - 'fforesight-read-general-configuration' + - 'fforesight-write-general-configuration' + - 'fforesight-manage-user-preferences' + - 'fforesight-read-users' + - 'fforesight-read-all-users' + - 'fforesight-write-users' + - 'fforesight-update-my-profile' + - 'fforesight-create-tenant' + - 'fforesight-get-tenants' + - 'fforesight-update-tenant' + - 'fforesight-deployment-info' + - 'fforesight-read-smtp-configuration' + - 'fforesight-write-smtp-configuration' + - 'fforesight-read-identity-provider-config' + - 'fforesight-write-identity-provider-config' access-token-life-span: 86400 application-name: tenant-user-management application-client-id: tenant-user-management