diff --git a/application-engine/pom.xml b/application-engine/pom.xml index 59fbb902e4e..90d560be4f4 100644 --- a/application-engine/pom.xml +++ b/application-engine/pom.xml @@ -361,6 +361,12 @@ spring-boot-starter-data-elasticsearch + + + org.springframework.boot + spring-boot-starter-validation + + @@ -483,12 +489,6 @@ guava 33.5.0-jre - - - - org.springframework.boot - spring-boot-starter-validation - tools.jackson.core jackson-core diff --git a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index 9f36d82df2a..5cf8af4b75c 100644 --- a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -36,6 +36,7 @@ import com.netgrif.application.engine.menu.services.interfaces.DashboardManageme import com.netgrif.application.engine.menu.services.interfaces.IMenuItemService import com.netgrif.application.engine.objects.auth.domain.Group import com.netgrif.application.engine.objects.auth.domain.LoggedUser +import com.netgrif.application.engine.objects.auth.domain.QGroup import com.netgrif.application.engine.objects.petrinet.domain.I18nString import com.netgrif.application.engine.objects.petrinet.domain.PetriNet import com.netgrif.application.engine.objects.petrinet.domain.Transition @@ -101,6 +102,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable import com.netgrif.application.engine.objects.utils.Nullable +import org.springframework.data.util.Pair import java.time.ZoneId import java.util.stream.Collectors @@ -3055,4 +3057,61 @@ class ActionDelegate extends DelegateExpando { IStorageService storageService = storageResolverService.resolve(storageField.storageType) return storageService.getPath(aCase.stringId, fileFieldId, fileName) } + + Group findGroupByIdentifier(String identifier) { + return groupService.findByIdentifier(identifier).orElse(null) + } + + Group findGroupById(String groupId) { + return groupService.findById(groupId) + } + + Page findGroups(Closure predicate = {it.identifier.isNotNull()}, Pageable pageable = Pageable.unpaged()) { + QGroup qGroup = new QGroup("group") + return groupService.findByPredicate(predicate(qGroup), pageable) + } + + Group createGroup(String identifier, String title = "", AbstractUser owner = userService.getLoggedOrSystem()) { + return groupService.create(identifier, title, owner) + } + + void deleteGroup(Group group) { + groupService.delete(group) + } + + Group saveGroup(Group group) { + return groupService.save(group) + } + + Group addUserToGroup(String groupId, String userId, String realmId) { + return groupService.addUser(groupId, userId, realmId) + } + + Group removeUserFromGroup(String groupId, String userId, String realmId) { + return groupService.removeUser(groupId, userId, realmId) + } + + Group addAuthorityToGroup(String groupId, String authorityId) { + return groupService.addAuthority(groupId, authorityId) + } + + Group removeAuthorityFromGroup(String groupId, String authorityId) { + return groupService.removeAuthority(groupId, authorityId) + } + + Group addRoleToGroup(String groupId, String roleId) { + return groupService.addRole(groupId, roleId) + } + + Group removeRoleFromGroup(String groupId, String roleId) { + return groupService.removeRole(groupId, roleId) + } + + Pair addSubGroup(String groupId, String subGroupId) { + return groupService.addSubgroup(groupId, subGroupId) + } + + Pair removeSubGroup(String groupId, String subGroupId) { + return groupService.removeSubgroup(groupId, subGroupId) + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java index 4ae6841c4ca..286735383a7 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java @@ -150,7 +150,7 @@ public AbstractUser createNewUser(NewUserRequest newUser) { if (newUser.groups != null && !newUser.groups.isEmpty()) { for (String group : newUser.groups) { - groupService.addUser(user, group); + groupService.addUser(group, user); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java index de08201760a..a36a81e6107 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java @@ -1,9 +1,9 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.interfaces.IUserResourceHelperService; -import com.netgrif.application.engine.auth.web.responsebodies.User; import com.netgrif.application.engine.auth.web.responsebodies.UserResource; import com.netgrif.application.engine.impersonation.service.interfaces.IImpersonationService; import lombok.extern.slf4j.Slf4j; @@ -32,20 +32,20 @@ public UserResource getResource(LoggedUser loggedUser, Locale locale, boolean sm // User result = loggedUser.isImpersonating() ? // getLocalisedUser(user, getImpersonated(loggedUser, small), locale) : // getLocalisedUser(user, locale); - User result = getLocalisedUser(user, locale); - return new UserResource(result, "profile"); + UserDto result = getLocalisedUser(user, locale); + return new UserResource(result); } @Override - public User getLocalisedUser(AbstractUser user, AbstractUser impersonated, Locale locale) { - User localisedUser = getLocalisedUser(user, locale); - User impersonatedUser = userFactory.getUser(impersonated, locale); + public UserDto getLocalisedUser(AbstractUser user, AbstractUser impersonated, Locale locale) { + UserDto localisedUser = getLocalisedUser(user, locale); + UserDto impersonatedUser = userFactory.getUser(impersonated, locale); localisedUser.setImpersonated(impersonatedUser); return localisedUser; } @Override - public User getLocalisedUser(AbstractUser user, Locale locale) { + public UserDto getLocalisedUser(AbstractUser user, Locale locale) { return userFactory.getUser(user, locale); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java deleted file mode 100644 index 81feafc98d7..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.netgrif.application.engine.auth.service.interfaces; - -public interface IAuthorizationService { - boolean hasAuthority(String authority); -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserResourceHelperService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserResourceHelperService.java index f82c5efe437..2ace8f75e66 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserResourceHelperService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserResourceHelperService.java @@ -1,6 +1,6 @@ package com.netgrif.application.engine.auth.service.interfaces; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.auth.web.responsebodies.UserResource; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -10,7 +10,7 @@ public interface IUserResourceHelperService { UserResource getResource(LoggedUser loggedUser, Locale locale, boolean small); - User getLocalisedUser(AbstractUser user, AbstractUser impersonated, Locale locale); + UserDto getLocalisedUser(AbstractUser user, AbstractUser impersonated, Locale locale); - User getLocalisedUser(AbstractUser user, Locale locale); + UserDto getLocalisedUser(AbstractUser user, Locale locale); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java new file mode 100644 index 00000000000..7d2b8bbe650 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java @@ -0,0 +1,483 @@ +package com.netgrif.application.engine.auth.web; + +import com.netgrif.application.engine.adapter.spring.common.web.responsebodies.ResponseMessage; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.auth.service.RealmService; +import com.netgrif.application.engine.auth.service.UserFactory; +import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; +import com.netgrif.application.engine.objects.auth.domain.Group; +import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.Realm; +import com.netgrif.application.engine.objects.dto.request.group.CreateGroupRequestDto; +import com.netgrif.application.engine.objects.dto.request.group.GroupSearchRequestDto; +import com.netgrif.application.engine.objects.dto.request.group.UpdateGroupRequestDto; +import com.netgrif.application.engine.objects.dto.response.group.GroupDto; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@RestController +@RequestMapping("api/groups") +@ConditionalOnProperty( + value = "netgrif.engine.group.web.enabled", + havingValue = "true", + matchIfMissing = true +) +@Tag(name = "Group") +@RequiredArgsConstructor +public class GroupController { + + private final GroupService groupService; + private final UserService userService; + private final ProcessRoleService processRoleService; + private final RealmService realmService; + private final UserFactory userFactory; + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Get page of groups from defined realm", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Retrieve page of groups from defined realm"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping(path = "/{realmId}/all", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getAllGroupsOfRealm(@PathVariable("realmId") String realmId, Pageable pageable, Locale locale) { + Page groups = groupService.findAllFromRealm(realmId, pageable); + return ResponseEntity.ok(transformPageContent(groups.getContent(), pageable, locale)); + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Create new group", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "New group created successfully"), + @ApiResponse(responseCode = "400", description = "Request data invalid"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity createGroup(@Valid @RequestBody CreateGroupRequestDto request) { + if (request == null) { + return ResponseEntity.badRequest().build(); + } + if (!realmExists(request.realmId())) { + String message = "Cannot create group, realm with id [" + request.realmId() + "] does not exist"; + log.error(message); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(message)); + } + AbstractUser user = userService.findById(request.ownerId(), request.realmId()); + if (user == null) { + String message = "User with id [%s] not found in realm [%s]".formatted(request.ownerId(), request.realmId()); + log.error(message); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage(message)); + } + try { + Group group = groupService.create(request.identifier(), request.displayName(), user); + return ResponseEntity.created(URI.create("/api/groups/" + group.getId())).body(ResponseMessage.createSuccessMessage("Group created successfully")); + } catch (IllegalArgumentException e) { + log.error("Failed to create group", e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(e.getMessage())); + } catch (Exception e) { + log.error("Failed to create group", e); + return ResponseEntity.internalServerError().body(ResponseMessage.createErrorMessage("Failed to create group with identifier [%s]".formatted(request.identifier()))); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Delete group defined by id", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "New group deleted successfully"), + @ApiResponse(responseCode = "400", description = "Request data invalid"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @DeleteMapping("/{id}") + public ResponseEntity deleteGroup(@PathVariable("id") String groupId) { + try { + Group group = groupService.findById(groupId); + groupService.delete(group); + return ResponseEntity.ok("Group with id [" + groupId + "] deleted successfully"); + } catch (IllegalArgumentException e) { + String message = "Failed to delete group with id [" + groupId + "]"; + log.error(message, e); + return ResponseEntity.badRequest().body(message); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Get group by id", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Group retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Group with given id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{id}") + public ResponseEntity getGroup(@PathVariable("id") String groupId, Locale locale) { + try { + Group group = groupService.findById(groupId); + return ResponseEntity.ok(GroupDto.fromGroup(group, locale)); + } catch (IllegalArgumentException e) { + log.error("Cannot get group with id [{}]", groupId, e); + return ResponseEntity.badRequest().build(); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Get paged of group members", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Group members retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Group with given id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @GetMapping("/{id}/users") + public ResponseEntity> getMembersOfGroup(@PathVariable("id") String groupId, Pageable pageable, Locale locale) { + try { + Group group = groupService.findById(groupId); + Page groupMembers = userService.findAllByIds(group.getMemberIds(), group.getRealmId(), pageable).map(u -> userFactory.getUser(u, locale)); + return ResponseEntity.ok(groupMembers); + } catch (IllegalArgumentException e) { + log.error("Cannot get members of group with id [{}]", groupId, e); + return ResponseEntity.badRequest().build(); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Assigns multiple users to group", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Users assigned to group successfully"), + @ApiResponse(responseCode = "400", description = "Group with given id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping("/{id}/users/assign") + public ResponseEntity assignUsersToGroup(@PathVariable("id") String groupId, @RequestBody Set userIds) { + try { + groupService.assignUsersToGroup(groupId, userIds); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected users assigned to group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Failed to assign members to group [%s]".formatted(groupId); + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(message)); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Add user to group", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "User added to group successfully"), + @ApiResponse(responseCode = "400", description = "Group with given id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping("/{id}/users/add/{userId}") + public ResponseEntity addUserToGroup(@PathVariable("id") String groupId, @PathVariable("userId") String userId) { + try { + Group group = groupService.findById(groupId); + AbstractUser user = userService.findById(userId, group.getRealmId()); + groupService.addUser(group, user); + return ResponseEntity.ok("Added user [" + userId + "] to group [" + groupId + "]"); + } catch (IllegalArgumentException e) { + String message = "Failed to add member [%s] to group [%s]".formatted(userId, groupId); + log.error(message, e); + return ResponseEntity.badRequest().body(message); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Remove user from group", + description = "Caller must have the ADMIN role", + security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "User removed from group successfully"), + @ApiResponse(responseCode = "400", description = "Group or user with given id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping("/{id}/users/remove/{userId}") + public ResponseEntity removeUserFromGroup(@PathVariable("id") String groupId, @PathVariable("userId") String userId) { + try { + Group group = groupService.findById(groupId); + AbstractUser user = userService.findById(userId, group.getRealmId()); + groupService.removeUser(group, user); + return ResponseEntity.ok("User [" + userId + "] removed from group [" + groupId + "]"); + } catch (IllegalArgumentException e) { + String message = "Failed to remove member [%s] from group [%s]".formatted(userId, groupId); + log.error(message, e); + return ResponseEntity.badRequest().body(message); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Assign roles to the group", description = "Assigns roles based on request body to group based on roleIds", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected roles assigned successfully"), + @ApiResponse(responseCode = "400", description = "Requested roles or group with defined id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/roles/assign", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity assignRolesToGroup(@PathVariable("id") String groupId, @RequestBody Set roleIds) { + try { + Group group = groupService.findById(groupId); + processRoleService.assignRolesToGroup(group, roleIds.stream().map(ProcessResourceId::new).collect(Collectors.toSet())); + log.info("Process roles {} assigned to group with id [{}]", roleIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected roles assigned to group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Assigning roles to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(message)); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Adds roles to the group", description = "Adds roles based on request body to group based on roleIds", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected roles added successfully"), + @ApiResponse(responseCode = "400", description = "Requested roles or group with defined id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/roles/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity addRolesToGroup(@PathVariable("id") String groupId, @RequestBody Set roleIds) { + try { + Group group = groupService.findById(groupId); + List processRoleList = roleIds.stream().map(roleId -> { + ProcessRole processRole = processRoleService.findById(roleId); + if (processRole == null) { + throw new IllegalArgumentException("Process role with id [" + roleId + "] does not exist"); + } + return processRole; + }).toList(); + + for (ProcessRole processRole : processRoleList) { + group = groupService.addRole(group, processRole); + } + + log.info("Process roles {} added to group with id [{}]", roleIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected roles added to group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Adding roles to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(message)); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Revokes roles to the group", description = "Revokes roles based on request body from group based on id", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected roles revoked successfully"), + @ApiResponse(responseCode = "400", description = "Requested roles or group with defined id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/roles/revoke", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity revokeRolesFromGroup(@PathVariable("id") String groupId, @RequestBody Set roleIds) { + try { + roleIds.forEach(roleId -> groupService.removeRole(groupId, roleId)); + log.info("Process roles {} revoked from group with id [{}]", roleIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected roles revoked from group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Revoking roles from group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Revoking roles from group " + groupId + " has failed!")); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Adds authority to the group", description = "Adds authority based on request body to group based on id", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected authorities added successfully"), + @ApiResponse(responseCode = "400", description = "Requested authorities or group with defined id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/authorities/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity addAuthorityToGroup(@PathVariable("id") String groupId, @RequestBody Set authorityIds) { + try { + authorityIds.forEach(authorityId -> groupService.addAuthority(groupId, authorityId)); + log.info("Authorities {} added to group with id [{}]", authorityIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected authorities added to group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Adding authorities to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Adding authorities to group " + groupId + " has failed!")); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Revokes authority from the group", description = "Revokes authority based on request body from group based on roleIds", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected authorities revoked successfully"), + @ApiResponse(responseCode = "400", description = "Requested authorities or group with defined id does not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/authorities/revoke", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity revokeAuthorityFromGroup(@PathVariable("id") String groupId, @RequestBody Set authorityIds) { + try { + authorityIds.forEach(authorityId -> groupService.removeAuthority(groupId, authorityId)); + log.info("Authorities {} revoked from group with id [{}]", authorityIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected authorities revoked from group " + groupId)); + } catch (IllegalArgumentException e) { + String message = "Revoking authorities to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Revoking authorities to group " + groupId + " has failed!")); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Groups retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Invalid group data"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @Operation(summary = "Generic group search", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> search(GroupSearchRequestDto query, Pageable pageable, Locale locale) { + List groups = groupService.search(query, pageable).getContent(); + return ResponseEntity.ok(transformPageContent(groups, pageable, locale)); + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Updates group", description = "Updates group according to incoming parameters", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Group updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid group parameters"), + @ApiResponse(responseCode = "404", description = "Group not found"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity updateGroup(@RequestBody UpdateGroupRequestDto groupUpdate) { + try { + Group group = groupService.findById(groupUpdate.id()); + if (groupUpdate.identifier() != null && groupService.findByIdentifier(groupUpdate.identifier()).isEmpty()) { + group.setIdentifier(groupUpdate.identifier()); + } + if (groupUpdate.displayName() != null) { + group.setDisplayName(groupUpdate.displayName()); + } + groupService.save(group); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Group with id [%s] updated successfully".formatted(groupUpdate.id()))); + } catch (IllegalArgumentException e) { + log.error("Group with id [{}] does not exist", groupUpdate.id(), e); + return ResponseEntity.notFound().build(); + } catch (Exception e) { + String message = "Failed to update group with id [%s]".formatted(groupUpdate.id()); + log.error(message, e); + return ResponseEntity.internalServerError().body(ResponseMessage.createErrorMessage(message)); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Assigns subgroups to group", description = "Removes existing subgroups and assigns new ones to group based on path param and request body", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected subgroups was successfully assigned to group"), + @ApiResponse(responseCode = "400", description = "Requested group or groups with defined id do not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/groups/assign", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity assignSubgroupsToGroup(@PathVariable("id") String groupId, @RequestBody Set subgroupIds) { + try { + groupService.assignSubgroups(groupId, subgroupIds); + log.info("Subgroups {} assigned to group with id [{}]", subgroupIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected subgroups was successfully assigned to group")); + } catch (IllegalArgumentException e) { + String message = "Adding subgroups to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Adding subgroups to group " + groupId + " has failed!")); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Adds subgroups to group", description = "Add subgroups to group based on path param and request body", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected subgroups was successfully added to group"), + @ApiResponse(responseCode = "400", description = "Requested group or groups with defined id do not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/groups/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity addSubgroupsToGroup(@PathVariable("id") String groupId, @RequestBody Set subgroupIds) { + try { + subgroupIds.forEach(subgroupId -> groupService.addSubgroup(groupId, subgroupId)); + log.info("Subgroups {} added to group with id [{}]", subgroupIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected subgroups was successfully added to group")); + } catch (IllegalArgumentException e) { + String message = "Adding subgroups to group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Adding subgroups to group " + groupId + " has failed!")); + } + } + + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") + @Operation(summary = "Removes subgroups from group", description = "Removes subgroups from group based on path param and request body", security = {@SecurityRequirement(name = "X-Auth-Token")}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Selected subgroups was successfully removed from group"), + @ApiResponse(responseCode = "400", description = "Requested group or groups with defined id do not exist"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @PatchMapping(value = "/{id}/groups/remove", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity removeSubgroupsFromGroup(@PathVariable("id") String groupId, @RequestBody Set subgroupIds) { + try { + subgroupIds.forEach(subgroupId -> groupService.removeSubgroup(groupId, subgroupId)); + log.info("Subgroups {} removed from group with id [{}]", subgroupIds, groupId); + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected subgroups was successfully removed from group")); + } catch (IllegalArgumentException e) { + String message = "Removing subgroups from group [" + groupId + "] has failed!"; + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Removing subgroups from group " + groupId + " has failed!")); + } + } + + private Page transformPageContent(List groups, Pageable pageable, Locale locale) { + return new PageImpl<>(groups.stream().map(group -> GroupDto.fromGroup(group, locale)).toList(), pageable, groups.size()); + } + + private boolean realmExists(String realmId) { + if (realmId == null) { + return false; + } + Optional realm = realmService.getRealmById(realmId); + return realm.isPresent(); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java index 6d472275a12..8d1213dac48 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java @@ -5,7 +5,7 @@ import com.netgrif.application.engine.auth.web.requestbodies.PreferencesRequest; import com.netgrif.application.engine.auth.web.requestbodies.UserSearchRequestBody; import com.netgrif.application.engine.auth.web.responsebodies.PreferencesResource; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.preferences.Preferences; @@ -53,7 +53,7 @@ public class PublicUserController { @ApiResponse(responseCode = "500", description = "Internal server error") }) @GetMapping(value = "/me", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getLoggedUser(Authentication auth) { + public ResponseEntity getLoggedUser(Authentication auth) { LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); AbstractUser user; try { @@ -67,7 +67,7 @@ public ResponseEntity getLoggedUser(Authentication auth) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } - return ResponseEntity.ok(User.createUser(user)); + return ResponseEntity.ok(UserDto.createUser(user)); } @ApiResponses(value = { @@ -77,7 +77,7 @@ public ResponseEntity getLoggedUser(Authentication auth) { }) @Operation(summary = "Generic user search", security = {@SecurityRequirement(name = "X-Auth-Token")}) @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> search(@RequestBody UserSearchRequestBody query, Pageable pageable, Authentication auth) { + public ResponseEntity> search(@RequestBody UserSearchRequestBody query, Pageable pageable, Authentication auth) { List roles = query.getRoles() == null ? null : query.getRoles().stream().map(ProcessResourceId::new).toList(); List negativeRoles = query.getNegativeRoles() == null ? null : query.getNegativeRoles().stream().map(ProcessResourceId::new).toList(); Page users = userService.searchAllCoMembers(query.getFulltext(), @@ -127,12 +127,12 @@ public ResponseEntity savePreferences(@RequestBody PreferencesRequest pr } } - private Page changeToResponse(Page users, Pageable pageable) { + private Page changeToResponse(Page users, Pageable pageable) { return new PageImpl<>(changeType(users.getContent()), pageable, users.getTotalElements()); } - public List changeType(List users) { - return users.stream().map(User::createUser).toList(); + public List changeType(List users) { + return users.stream().map(UserDto::createUser).toList(); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java index 1fd5f887d09..9475116922e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java @@ -7,7 +7,7 @@ import com.netgrif.application.engine.auth.web.requestbodies.UserCreateRequest; import com.netgrif.application.engine.auth.web.requestbodies.UserSearchRequestBody; import com.netgrif.application.engine.auth.web.responsebodies.PreferencesResource; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -54,7 +54,7 @@ public class UserController { private final RealmService realmService; private final UserFactory userFactory; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Create a new user", description = "Creates a new user in the realm specified by id.") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "User successfully created"), @@ -63,7 +63,7 @@ public class UserController { @ApiResponse(responseCode = "500", description = "Internal server error") }) @PostMapping("/{realmId}") - public ResponseEntity createUser(@PathVariable String realmId, @RequestBody UserCreateRequest request, Locale locale) { + public ResponseEntity createUser(@PathVariable String realmId, @RequestBody UserCreateRequest request, Locale locale) { try { if (!realmExists(realmId)) { log.error("Realm with id [{}] not found", realmId); @@ -96,7 +96,7 @@ public ResponseEntity createUser(@PathVariable String realmId, @RequestBod @ApiResponse(responseCode = "500", description = "Internal server error") }) @GetMapping("/{realmId}/all") - public ResponseEntity> getAllUsers(@PathVariable String realmId, Pageable pageable, Locale locale) { + public ResponseEntity> getAllUsers(@PathVariable String realmId, Pageable pageable, Locale locale) { if (!realmExists(realmId)) { log.error("Realm with id [{}] not found", realmId); return ResponseEntity.badRequest().build(); @@ -112,7 +112,7 @@ public ResponseEntity> getAllUsers(@PathVariable String realmId, Page @ApiResponse(responseCode = "500", description = "Internal server error") }) @GetMapping(value = "/me", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getLoggedUser(Authentication auth, Locale locale) { + public ResponseEntity getLoggedUser(Authentication auth, Locale locale) { LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); AbstractUser user; try { @@ -136,7 +136,7 @@ public ResponseEntity getLoggedUser(Authentication auth, Locale locale) { }) @Operation(summary = "Generic user search", security = {@SecurityRequirement(name = "X-Auth-Token")}) @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> search(@RequestBody UserSearchRequestBody query, Pageable pageable, Authentication auth, Locale locale) { + public ResponseEntity> search(@RequestBody UserSearchRequestBody query, Pageable pageable, Authentication auth, Locale locale) { List roles = query.getRoles() == null ? null : query.getRoles().stream().map(ProcessResourceId::new).toList(); List negativeRoles = query.getNegativeRoles() == null ? null : query.getNegativeRoles().stream().map(ProcessResourceId::new).toList(); Page users = userService.searchAllCoMembers(query.getFulltext(), @@ -154,7 +154,7 @@ public ResponseEntity> search(@RequestBody UserSearchRequestBody quer @ApiResponse(responseCode = "500", description = "Internal server error") }) @GetMapping(value = "/{realmId}/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getUser(@PathVariable("realmId") String realmId, @PathVariable("id") String userId, Locale locale) { + public ResponseEntity getUser(@PathVariable("realmId") String realmId, @PathVariable("id") String userId, Locale locale) { LoggedUser actualUser = userService.getLoggedUserFromContext(); // TODO: impersonation // LoggedUser loggedUser = actualUser.getSelfOrImpersonated(); @@ -172,44 +172,7 @@ public ResponseEntity getUser(@PathVariable("realmId") String realmId, @Pa return ResponseEntity.ok(userFactory.getUser(user, locale)); } -// todo step 2, only used in test on frontend -// @Operation(summary = "Update user", security = {@SecurityRequirement(name = "X-Auth-Token")}) -// @PostMapping(value = "/update", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) -// public ResponseEntity updateUser(@RequestBody UpdateUserRequest updates, Authentication auth, Locale locale) { - - /// / todo should this be kept? not relevant anymore? -// if (!serverAuthProperties.isEnableProfileEdit()) { -// return null; -// } -// LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); -// String actorId = updates.getStringId(); -// IUser user; -// try { -// user = userService.findById(actorId, updatedUser.getRealmId()); -// } catch (IllegalArgumentException e) { -// log.error("Could not find user with id [{}]", actorId, e); -// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); -// } -// user = userService.update(user, updates.getUpdatedUser()); -// securityContextService.saveToken(actorId); -// if (Objects.equals(loggedUser.getId(), actorId)) { -// loggedUser.setFirstName(user.getFirstName()); -// loggedUser.setLastName(user.getLastName()); -// securityContextService.reloadSecurityContext(loggedUser); -// } -// log.info("Updating user " + user.getEmail() + " with data " + updatedUser); -// return ResponseEntity.ok(User.createUser(user)); -// } - -// todo not used on front, is it needed? -// @Operation(summary = "Get all users with specified roles", security = {@SecurityRequirement(name = "X-Auth-Token")}) -// @PostMapping(value = "/role", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) -// public ResponseEntity> getAllWithRole(@RequestBody Set roleIds, Pageable pageable, Locale locale) { -// Set roleResourceIds = roleIds == null ? null : roleIds.stream().map(ProcessResourceId::new).collect(Collectors.toSet()); -// Page page = userService.findAllActiveByProcessRoles(roleResourceIds, pageable); -// return ResponseEntity.ok(); -// } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Assign roles to the user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @PutMapping(value = "/{realmId}/{id}/roles", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ApiResponses(value = { @@ -231,29 +194,7 @@ public ResponseEntity assignRolesToUser(@PathVariable("realmId" } } -// -// @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") -// @Operation(summary = "Assign negative roles to the user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) -// @PutMapping(value = "/{realmId}/{id}/negativeRole", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) -// @ApiResponses(value = { -// @ApiResponse(responseCode = "200", description = "Selected negative roles assigned successfully"), -// @ApiResponse(responseCode = "400", description = "Requested roles or user with defined id does not exist"), -// @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), -// @ApiResponse(responseCode = "500", description = "Internal server error") -// }) -// public ResponseEntity assignNegativeRolesToUser(@PathVariable("realmId") String realmId, @PathVariable("id") String actorId, @RequestBody Set roleIds, Authentication auth) { -// try { -// AbstractUser user = userService.findById(actorId, realmId); -// processRoleService.assignNegativeRolesToUser(user, roleIds.stream().map(ProcessResourceId::new).collect(Collectors.toSet()), (LoggedUser) auth.getPrincipal()); -// log.info("Negative process roles {} assigned to user [{}]", roleIds, actorId); -// return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected negative roles assigned to user " + actorId)); -// } catch (IllegalArgumentException e) { -// log.error("Assigning negative roles to user with id [{}] has failed!", actorId, e); -// return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Assigning negative roles to user " + actorId + " has failed!")); -// } -// } -// - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Get all authorities of the system", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @@ -267,7 +208,7 @@ public ResponseEntity> getAllAuthorities() { return ResponseEntity.ok(authorityService.findAll(Pageable.unpaged()).stream().toList()); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Assign authority to the user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @@ -327,11 +268,11 @@ public ResponseEntity savePreferences(@RequestBody PreferencesR } } - private Page changeToResponse(Page users, Pageable pageable, Locale locale) { + private Page changeToResponse(Page users, Pageable pageable, Locale locale) { return new PageImpl<>(changeType(users.getContent(), locale), pageable, users.getTotalElements()); } - public List changeType(List users, Locale locale) { + public List changeType(List users, Locale locale) { return users.stream().map(u -> userFactory.getUser(u, locale)).toList(); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java index da82aaab178..872afa5130b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java @@ -5,9 +5,9 @@ import java.util.ArrayList; -public class UserResource extends EntityModel { +public class UserResource extends EntityModel { - public UserResource(User content, String selfRel) { + public UserResource(UserDto content) { super(content, new ArrayList<>()); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResourceAssembler.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResourceAssembler.java index 50e130aca4a..5653520cbe6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResourceAssembler.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResourceAssembler.java @@ -13,18 +13,14 @@ public class UserResourceAssembler implements RepresentationModelAssembler searchBody, Auth } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Reindex all or stale cases with bulk index", description = "Reindex all or stale cases (specified by IndexParams.indexAll param) with bulk index. Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/impersonation/web/ImpersonationController.java b/application-engine/src/main/java/com/netgrif/application/engine/impersonation/web/ImpersonationController.java index 2f2b5b189f5..2e9354614d0 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/impersonation/web/ImpersonationController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/impersonation/web/ImpersonationController.java @@ -57,9 +57,9 @@ public class ImpersonationController { @Autowired protected ObjectFactory userResourceAssemblerProvider; - protected UserResourceAssembler getUserResourceAssembler(Locale locale, boolean small, String selfRel) { + protected UserResourceAssembler getUserResourceAssembler(Locale locale) { UserResourceAssembler result = userResourceAssemblerProvider.getObject(); - result.initialize(locale, small, selfRel); + result.initialize(locale); return result; } @@ -70,7 +70,7 @@ public PagedModel getImpersonationUserOptions(@RequestBody SearchR Page page = impersonationAuthorizationService.getConfiguredImpersonationUsers(request.getQuery(), loggedUser, pageable); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(ImpersonationController.class) .getImpersonationUserOptions(request, pageable, assembler, auth, locale)).withRel("all"); - PagedModel resources = assembler.toModel(page, getUserResourceAssembler(locale, false, "all"), selfLink); + PagedModel resources = assembler.toModel(page, getUserResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, AbstractUser.class, selfLink.getRel().toString()); return resources; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java b/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java deleted file mode 100644 index 5fca05b1095..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.netgrif.application.engine.orgstructure.web; - -import com.netgrif.application.engine.auth.service.GroupService; -import com.netgrif.application.engine.orgstructure.web.responsebodies.Group; -import com.netgrif.application.engine.orgstructure.web.responsebodies.GroupsResource; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.domain.Pageable; -import org.springframework.hateoas.MediaTypes; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@RestController -@RequestMapping("/api/group") -@ConditionalOnProperty( - value = "netgrif.engine.security.web.group-enabled", - havingValue = "true", - matchIfMissing = true -) -@Tag(name = "Group") -public class GroupController { - - private final GroupService service; - - public GroupController(GroupService service) { - this.service = service; - } - - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") - @Operation(summary = "Get all groups in the system", - description = "Caller must have the ADMIN role", - security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/all", produces = MediaTypes.HAL_JSON_VALUE) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - }) - public GroupsResource getAllGroups() { - List groups = service.findAll(Pageable.unpaged()).getContent(); - Set groupResponse = groups.stream() - .map(g -> new Group(g.getStringId(), g.getDisplayName())) - .collect(Collectors.toCollection(HashSet::new)); - return new GroupsResource(groupResponse); - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/GroupsResource.java b/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/GroupsResource.java deleted file mode 100644 index a6039cd6bde..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/GroupsResource.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.netgrif.application.engine.orgstructure.web.responsebodies; - -import com.netgrif.application.engine.orgstructure.web.GroupController; -import org.springframework.hateoas.CollectionModel; -import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; - - -public class GroupsResource extends CollectionModel { - - public GroupsResource(Iterable content) { - super(content); - buildLinks(); - } - - private void buildLinks() { - add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(GroupController.class) - .getAllGroups()).withSelfRel()); - } -} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/LdapGroupResponseBody.java b/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/LdapGroupResponseBody.java deleted file mode 100644 index cad3b96532d..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/LdapGroupResponseBody.java +++ /dev/null @@ -1,22 +0,0 @@ -//package com.netgrif.application.engine.orgstructure.web.responsebodies; -// -//import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; -//import lombok.AllArgsConstructor; -//import lombok.Data; -//import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -// -//import java.util.Set; -// -//@Data -//@AllArgsConstructor -//@ConditionalOnExpression("${nae.ldap.enabled:false}") -//public class LdapGroupResponseBody { -// -// private String dn; -// -// private String cn; -// -// private String description; -// -// private Set processRoles; -//} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java index d1d07bcdc22..eb5252ebb72 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java @@ -410,37 +410,8 @@ public void deleteRolesOfNet(PetriNet net, LoggedUser loggedUser) { .collect(Collectors.toList()); Set deletedRoleStringIds = deletedRoleIds.stream().map(ProcessResourceId::toString).collect(Collectors.toSet()); - Pageable realmPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); - Pageable usersPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); - - Page realms; - do { - realms = realmService.getSmallRealm(realmPageable); - - realms.forEach(realm -> { - Page users; - users = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), realm.getName(), usersPageable); - while (users.hasContent()) { - for (AbstractUser user : users) { - log.info("[{}]: Removing deleted roles of Petri net {} version {} from user {} with id {}", - net.getStringId(), net.getIdentifier(), net.getVersion().toString(), user.getFullName(), user.getStringId()); - - if (user.getProcessRoles().isEmpty()) { - continue; - } - - Set newRoles = user.getProcessRoles().stream() - .filter(role -> !deletedRoleStringIds.contains(role.getStringId())) - .map(ProcessRole::get_id) - .collect(Collectors.toSet()); - this.assignRolesToUser(user, newRoles, loggedUser); - } - users = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), realm.getName(), usersPageable); - } - }); - - realmPageable = realmPageable.next(); - } while (realms.hasNext()); + deleteRolesOfNetFromUser(net, deletedRoleIds, deletedRoleStringIds, loggedUser); + deleteRolesOfNetFromGroup(net, deletedRoleIds, deletedRoleStringIds); log.info("[{}]: Deleting all roles of Petri net {} version {}", net.getStringId(), net.getIdentifier(), net.getVersion().toString()); @@ -516,4 +487,57 @@ private ObjectId extractObjectId(String caseId) { return new ObjectId(objectIdPart); } + + private void deleteRolesOfNetFromUser(PetriNet net, List deletedRoleIds, Set deletedRoleStringIds, LoggedUser loggedUser) { + Pageable realmPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Pageable usersPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page realms; + do { + realms = realmService.getSmallRealm(realmPageable); + + realms.forEach(realm -> { + Page users; + users = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), realm.getName(), usersPageable); + while (users.hasContent()) { + for (AbstractUser user : users) { + log.info("[{}]: Removing deleted roles of Petri net {} version {} from user {} with id {}", + net.getStringId(), net.getIdentifier(), net.getVersion().toString(), user.getFullName(), user.getStringId()); + + if (user.getProcessRoles().isEmpty()) { + continue; + } + + Set newRoles = user.getProcessRoles().stream() + .filter(role -> !deletedRoleStringIds.contains(role.getStringId())) + .map(ProcessRole::get_id) + .collect(Collectors.toSet()); + this.assignRolesToUser(user, newRoles, loggedUser); + } + users = this.userService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), realm.getName(), usersPageable); + } + }); + + realmPageable = realmPageable.next(); + } while (realms.hasNext()); + } + + private void deleteRolesOfNetFromGroup(PetriNet net, List deletedRoleIds, Set deletedRoleStringIds) { + Pageable groupPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page groups = groupService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), groupPageable); + while (groups.hasContent()) { + for (Group group : groups) { + log.info("[{}]: Removing deleted roles of Petri net {} version {} from group {} with id {}", + net.getStringId(), net.getIdentifier(), net.getVersion().toString(), group.getFullName(), group.getStringId()); + if (group.getProcessRoles().isEmpty()) { + continue; + } + Set newRoles = group.getProcessRoles().stream() + .filter(role -> !deletedRoleStringIds.contains(role.getStringId())) + .map(ProcessRole::get_id) + .collect(Collectors.toSet()); + assignRolesToGroup(group, newRoles); + } + groups = groupService.findAllByProcessRoles(new HashSet<>(deletedRoleIds), groupPageable); + } + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java index d4b062650c2..29497470846 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java @@ -95,7 +95,7 @@ public static String decodeUrl(String s1) { } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Import new process", description = "Caller must have the ADMIN role. Imports an entirely new process or a new version of an existing process.", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -182,7 +182,7 @@ public TransactionsResource getTransactions(@PathVariable("netId") String netId, return new TransactionsResource(net.getTransactions().values(), netId, locale); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Download process model", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/{netId}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public FileSystemResource getNetFile(@PathVariable("netId") String netId, @RequestParam(value = "title", required = false) String title, Authentication auth, HttpServletResponse response) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java index 77cac9d06fd..7d10c6f9f8d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java @@ -34,7 +34,7 @@ public class ProcessRoleController { private final ProcessRoleService processRoleService; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Delete global role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @Parameter(name = "id", description = "Id of the global role to be deleted", required = true, example = "GcdIZcAPUc6jh7i2-68d683f80dc9384aa6791a64") diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java index a404fc17d94..71212a1b1b9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java @@ -3,6 +3,7 @@ import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.objects.auth.domain.Group; import com.netgrif.application.engine.startup.ApplicationEngineStartupRunner; import com.netgrif.application.engine.startup.annotation.RunnerOrder; import lombok.RequiredArgsConstructor; @@ -11,6 +12,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; +import java.util.Optional; + @Slf4j @Component @RunnerOrder(110) @@ -24,13 +27,13 @@ public class GroupRunner implements ApplicationEngineStartupRunner { @Override public void run(ApplicationArguments args) throws Exception { - createDefaultGroup(); + if (groupProperties.isSystemEnabled()) { + createDefaultGroup(); + } } protected void createDefaultGroup() { - if (groupProperties.isSystemEnabled()) { - groupService.create(userService.getLoggedOrSystem()); - } + groupService.getDefaultSystemGroup(); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java index bb449182e42..91ae07fdb56 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java @@ -108,7 +108,7 @@ public void setAllToSuperUser() { } public void setAllGroups() { - groupService.findAll(Pageable.unpaged()).forEach(g -> groupService.addUser(getSuperUser(), g)); + groupService.findAll(Pageable.unpaged()).forEach(g -> groupService.addUser(g, getSuperUser())); } public void setAllProcessRoles() { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java index 31ac9674498..df93aad66d6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java @@ -259,6 +259,11 @@ protected Map performImport(FilterImportExportList filterList) t QTask.task.transitionId.eq(IMPORT_FILTER_TRANSITION) .and(QTask.task.caseId.eq(filterCase.get().getStringId())) ); + + if (importedFilterTask == null) { + return; + } + importedFilterTaskIds.put(filter.getCaseId(), importedFilterTask.getStringId()); // TODO: delete after fixed issue: https://netgrif.atlassian.net/jira/servicedesk/projects/NGSD/issues/ @@ -429,4 +434,3 @@ private static void validateFilterXML(InputStream xml) throws IllegalFilterFileE } } } - diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java index e9ed10582a2..16e6b7106fd 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java @@ -146,43 +146,61 @@ public List importMenu(List menuItemCases, FileFieldValue ffv, Str List importedEntryAndFilterCaseIds = new ArrayList<>(); MenuAndFilters menuAndFilters = loadFromXML(ffv); - //Firstly, remove existing preference_filter_item cases having the same menu identifier as any menu - // which is being currently imported. - List menuItemIdsToReplace = menuItemCases.stream().filter(caze -> menuAndFilters.getMenuList().getMenus().stream() - .anyMatch(menu -> Objects.equals(menu.getMenuIdentifier(), caze.getDataSet().get(MENU_IDENTIFIER).getValue()))) - .map(Case::getStringId).collect(Collectors.toList()); - - //Change remove_option button value to trigger its SET action - if (!menuItemIdsToReplace.isEmpty()) menuItemIdsToReplace.forEach(id -> { - Case caseToRemove = workflowService.findOne(id); - Map> caseToRemoveData = new HashMap<>(); - Map removeBtnData = new HashMap<>(); - removeBtnData.put("type", "button"); - Object removeOption = caseToRemove.getFieldValue("remove_option"); - int nextRemoveOption = 1; - if (removeOption instanceof Number number) { - nextRemoveOption = number.intValue() + 1; - } else if (removeOption instanceof String value && value.matches("-?\\d+")) { - nextRemoveOption = Integer.parseInt(value) + 1; - } - removeBtnData.put("value", String.valueOf(nextRemoveOption)); - caseToRemoveData.put("remove_option", removeBtnData); - - QTask qTask = new QTask("task"); - Task task = taskService.searchOne(qTask.transitionId.eq("view").and(qTask.caseId.eq(caseToRemove.getStringId()))); - dataService.setData(task, ImportHelper.populateDataset(caseToRemoveData)); - }); - //Import filters Map importedFilterTaskIds = filterImportExportService.importFilters(menuAndFilters.getFilterList()); //Import each menu individually menuAndFilters.getMenuList().getMenus().forEach(menu -> { resultMessage.append("\nIMPORTING MENU \"").append(menu.getMenuIdentifier()).append("\":\n"); - menu.getMenuEntries().forEach(menuItem -> { - String entryAndFilterCaseId = createMenuItemCase(resultMessage, menuItem, menu.getMenuIdentifier(), parentId, importedFilterTaskIds.get(menuItem.getFilterCaseId())); - if (!entryAndFilterCaseId.equals("")) importedEntryAndFilterCaseIds.add(entryAndFilterCaseId); - }); + + boolean allFiltersPresent = true; + if (menu.getMenuEntries() != null) { + for (MenuEntry menuItem : menu.getMenuEntries()) { + String filterTaskId = importedFilterTaskIds.get(menuItem.getFilterCaseId()); + if (filterTaskId == null || filterTaskId.isBlank()) { + log.warn("Menu entry \"{}\" references filter case with original ID '{}', which was not imported. Menu entry was skipped.", + menuItem.getEntryName(), menuItem.getFilterCaseId()); + resultMessage.append("\nMenu entry \"").append(menuItem.getEntryName()) + .append("\": Filter not found! Menu entry was skipped.\n"); + allFiltersPresent = false; + } + } + } + + if (allFiltersPresent) { + //Remove existing preference_filter_item cases having the same menu identifier as the menu being imported. + List menuItemIdsToReplace = menuItemCases.stream().filter(caze -> + Objects.equals(menu.getMenuIdentifier(), caze.getDataSet().get(MENU_IDENTIFIER).getValue())) + .map(Case::getStringId).collect(Collectors.toList()); + + //Change remove_option button value to trigger its SET action + if (!menuItemIdsToReplace.isEmpty()) menuItemIdsToReplace.forEach(id -> { + Case caseToRemove = workflowService.findOne(id); + Map> caseToRemoveData = new HashMap<>(); + Map removeBtnData = new HashMap<>(); + removeBtnData.put("type", "button"); + Object removeOption = caseToRemove.getFieldValue("remove_option"); + int nextRemoveOption = 1; + if (removeOption instanceof Number number) { + nextRemoveOption = number.intValue() + 1; + } else if (removeOption instanceof String value && value.matches("-?\\d+")) { + nextRemoveOption = Integer.parseInt(value) + 1; + } + removeBtnData.put("value", String.valueOf(nextRemoveOption)); + caseToRemoveData.put("remove_option", removeBtnData); + + QTask qTask = new QTask("task"); + Task task = taskService.searchOne(qTask.transitionId.eq("view").and(qTask.caseId.eq(caseToRemove.getStringId()))); + dataService.setData(task, ImportHelper.populateDataset(caseToRemoveData)); + }); + + if (menu.getMenuEntries() != null) { + menu.getMenuEntries().forEach(menuItem -> { + String entryAndFilterCaseId = createMenuItemCase(resultMessage, menuItem, menu.getMenuIdentifier(), parentId, importedFilterTaskIds.get(menuItem.getFilterCaseId())); + if (!entryAndFilterCaseId.isEmpty()) importedEntryAndFilterCaseIds.add(entryAndFilterCaseId); + }); + } + } QTask qTask = new QTask("task"); Task task = taskService.searchOne(qTask.transitionId.eq(GROUP_NAV_TASK).and(qTask.caseId.eq(parentId))); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index 9d4615b06e4..77e3d6a5054 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java @@ -188,7 +188,7 @@ public PagedModel findAllByAuthor(@PathVariable("id") String autho return resources; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')") @Operation(summary = "Reload tasks of case", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy index 97aedd3daf0..79c5bfa0329 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy @@ -98,8 +98,12 @@ class ActionDelegateTest { void importFiltersTest(){ prepareFilterImportFile() - List actionDelegateList = actionDelegate.importFilters() - assert actionDelegateList.size() == 2 + actionDelegate.importFilters() + List filters = workflowService.search( + QCase.case$.processIdentifier.eq(FilterRunner.FILTER_PETRI_NET_IDENTIFIER), + Pageable.ofSize(4) + ).content + assert filters.size() == 4 } private void prepareFilterImportFile() { diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy index 560eae5f304..7488a4ddeb8 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy @@ -99,7 +99,7 @@ class AssignActionTest { auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Test", lastName: "Integration", email: USER_EMAIL, password: USER_PASSWORD, state: UserState.ACTIVE), + importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: USER_PASSWORD, state: UserState.ACTIVE), [auths.get("user"), auths.get("admin")] as Authority[], // [org] as Group[], [] as ProcessRole[]) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignRemoveTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignRemoveTest.groovy index 5797a309360..505705ab2ae 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignRemoveTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignRemoveTest.groovy @@ -76,7 +76,7 @@ class AssignRemoveTest { assert netOptional.getNet() != null; def net = netOptional.getNet() def userAuthorities = importHelper.createAuthorities(["user": Authority.user]) - def testUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + def testUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [userAuthorities.get("user")] as Authority[], [] as ProcessRole[]) def loggedUser = ActorTransformer.toLoggedUser(testUser) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/LoginAttemptsTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/LoginAttemptsTest.groovy index 86a256ac0bc..ab8a4fdc353 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/LoginAttemptsTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/LoginAttemptsTest.groovy @@ -1,6 +1,7 @@ package com.netgrif.application.engine.auth import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties import com.netgrif.application.engine.objects.auth.domain.Authority @@ -62,7 +63,7 @@ class LoginAttemptsTest { .build() auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: USER_PASSWORD, state: UserState.ACTIVE), + importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: USER_PASSWORD, state: UserState.ACTIVE), [auths.get("user"), auths.get("admin")] as Authority[], [] as ProcessRole[]) } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/TaskAuthorizationServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/TaskAuthorizationServiceTest.groovy index 644852ad01f..38eff5d9eb9 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/TaskAuthorizationServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/TaskAuthorizationServiceTest.groovy @@ -1,6 +1,7 @@ package com.netgrif.application.engine.auth import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.auth.service.GroupService import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.importer.service.Importer @@ -181,7 +182,7 @@ class TaskAuthorizationServiceTest { this.netWithUserRefs = netWithUserRefs.getNet() def auths = importHelper.createAuthorities(["user": Authority.user]) - testUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + testUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], // [org] as Group[], [] as ProcessRole[] diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy index 6313dc671bd..6a9b90fd267 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/UserServiceTest.groovy @@ -116,10 +116,10 @@ class UserServiceTest { User otherGroupMember = createUser("other-group-member@netgrif.com") def defaultSystemGroup = groupService.getDefaultSystemGroup() - groupService.removeUser(requester, defaultSystemGroup) - groupService.removeUser(sharedGroupMember, defaultSystemGroup) - groupService.removeUser(otherGroupMember, defaultSystemGroup) - groupService.addUser(sharedGroupMember, groupService.getDefaultUserGroup(requester)) + groupService.removeUser(defaultSystemGroup, requester) + groupService.removeUser(defaultSystemGroup, sharedGroupMember) + groupService.removeUser(defaultSystemGroup, otherGroupMember) + groupService.addUser(groupService.getDefaultUserGroup(requester), sharedGroupMember) sharedGroupMember = (User) userService.addRole(sharedGroupMember, dummyRole.get_id()) otherGroupMember = (User) userService.addRole(otherGroupMember, dummyRole.get_id()) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy index 11d44504a7b..f0c9214e1e7 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy @@ -148,11 +148,10 @@ class WorkflowAuthorizationServiceTest { this.netWithUserRefs = netWithUserRefs.getNet() def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - testUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), - [auths.get("user")]as Authority[], + testUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")]as Authority[], // [org] as Group[], [] as ProcessRole[]) - AbstractUser adminUser = importHelper.createUser(new User(firstName: "Admin", lastName: "User", email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), + AbstractUser adminUser = importHelper.createUser(new User(firstName: "Admin", lastName: "User", username: ADMIN_EMAIL, email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("admin")] as Authority[], [] as ProcessRole[]) userAuth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(testUser), "password", testUser.authoritySet as List) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/elastic/ElasticSearchTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/elastic/ElasticSearchTest.groovy index 9ad69cb4b58..f96f3cde973 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/elastic/ElasticSearchTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/elastic/ElasticSearchTest.groovy @@ -2,6 +2,7 @@ package com.netgrif.application.engine.elastic import com.netgrif.application.engine.ApplicationEngine import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.elastic.domain.ElasticCaseRepository import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseMappingService import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService @@ -119,7 +120,7 @@ class ElasticSearchTest { // def org = importHelper.createGroup("Test") def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) // def processRoles = importHelper.getProcessRoles(net.get()) - def testUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Test", lastName: "Integration", email: USER_EMAIL, password: USER_PASSW, state: UserState.ACTIVE), + def testUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: USER_PASSW, state: UserState.ACTIVE), [auths.get("user")] as Authority[], [net.roles.values().find { it.importId == "process_role" }] as ProcessRole[]) auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(testUser), USER_PASSW, testUser.authoritySet as List) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/EncryptionTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/EncryptionTest.groovy index 784bffe713a..3b4ab135fe9 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/EncryptionTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/EncryptionTest.groovy @@ -1,5 +1,6 @@ package com.netgrif.application.engine.insurance +import com.netgrif.application.engine.TestHelper import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.importer.service.Importer @@ -40,6 +41,9 @@ class EncryptionTest { @Autowired private Importer importer + @Autowired + private TestHelper helper + @Autowired private AuthorityService authorityService @@ -54,6 +58,7 @@ class EncryptionTest { @Test void testEncryption() { + helper.truncateDbs() String id = createCase() Case useCase = loadCase(id) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/mvc/InsuranceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/mvc/InsuranceTest.groovy index 1c1cc1c1c02..b8cb3fe6b31 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/mvc/InsuranceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/insurance/mvc/InsuranceTest.groovy @@ -2,6 +2,7 @@ package com.netgrif.application.engine.insurance.mvc import com.netgrif.application.engine.ApplicationEngine import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService import com.netgrif.application.engine.auth.service.UserService @@ -139,10 +140,10 @@ class InsuranceTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) def processRoles = ImportHelper.getProcessRolesByImportId(net.getNet(), ["agent": "1", "company": "2"]) - def testUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + def testUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("user"), auths.get("admin")] as Authority[], [processRoles.get("agent"), processRoles.get("company")] as ProcessRole[]) - auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(testUser), "password", testUser.authoritySet as List) + auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(testUser), "password", (Set) testUser.authoritySet) auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())); mapper = net.getNet().dataSet.collectEntries { [(it.value.importId as int): (it.key)] } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy index 29c357877bd..b2fb1519240 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy @@ -212,7 +212,7 @@ class MenuImportExportTest { private User createDummyUser() { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - return importHelper.createUser(new User(firstName: "Dummy", lastName: "User", email: DUMMY_USER_MAIL, username: DUMMY_USER_MAIL, password: DUMMY_USER_PASSWORD, state: UserState.ACTIVE), + return importHelper.createUser(new User(firstName: "Dummy", lastName: "User", username: DUMMY_USER_MAIL, email: DUMMY_USER_MAIL, password: DUMMY_USER_PASSWORD, state: UserState.ACTIVE), [auths.get("user")] as Authority[], [] as ProcessRole[]) } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy index ca02b6e69ec..3c8e0ca93a2 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy @@ -1,21 +1,39 @@ package com.netgrif.application.engine.orgstructure.groups import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl +import com.netgrif.application.engine.adapter.spring.auth.domain.User +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService +import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.auth.service.GroupService import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.objects.auth.constants.UserConstants +import com.netgrif.application.engine.objects.auth.domain.AbstractUser +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer import com.netgrif.application.engine.objects.auth.domain.Authority import com.netgrif.application.engine.objects.auth.domain.Group import com.netgrif.application.engine.objects.auth.domain.QGroup import com.netgrif.application.engine.objects.auth.domain.enums.UserState +import com.netgrif.application.engine.objects.petrinet.domain.PetriNet +import com.netgrif.application.engine.objects.petrinet.domain.VersionType import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole +import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.petrinetoutcomes.ImportPetriNetEventOutcome +import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate +import com.netgrif.application.engine.petrinet.params.ImportPetriNetParams +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.startup.runner.GroupRunner import com.netgrif.application.engine.utils.FullPageRequest +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.data.util.Pair +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension @@ -37,65 +55,434 @@ class GroupServiceTest { UserService userService @Autowired - private ImportHelper importHelper + ProcessRoleService processRoleService + + @Autowired + ImportHelper importHelper + + @Autowired + IPetriNetService petriNetService @Autowired TestHelper testHelper - @Test - void groupTest() { + @Autowired + AuthorityService authorityService + + @Autowired + ActionDelegate actionDelegate + + AbstractUser dummy, customer + + @BeforeEach + void init() { testHelper.truncateDbs() def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Dummy", lastName: "User", email: DUMMY_USER_MAIL, username: DUMMY_USER_MAIL, password: "password", state: UserState.ACTIVE), + dummy = importHelper.createUser(new User(firstName: "Dummy", lastName: "User", email: DUMMY_USER_MAIL, username: DUMMY_USER_MAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [] as ProcessRole[]) - importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Customer", lastName: "User", email: CUSTOMER_USER_MAIL, username: CUSTOMER_USER_MAIL, password: "password", state: UserState.ACTIVE), + customer = importHelper.createUser(new User(firstName: "Customer", lastName: "User", email: CUSTOMER_USER_MAIL, username: CUSTOMER_USER_MAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [] as ProcessRole[]) - Group customGroup = createGroup() - if (customGroup == null) { - throw new NullPointerException() + } + + private void setSecurityContext(AbstractUser user) { + def loggedUser = ActorTransformer.toLoggedUser(user) + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loggedUser, null, loggedUser.authoritySet as Set)) + } + + // ==================== GroupService Tests ==================== + + @Test + void createGroup() { + groupService.create("CUSTOM_GROUP_1", "CUSTOM_GROUP_1", userService.findUserByUsername(DUMMY_USER_MAIL, null).get()) + Optional groupOpt = groupService.findByIdentifier("CUSTOM_GROUP_1") + assert groupOpt.isPresent() + + } + + @Test + void findGroup() { + QGroup qGroup = new QGroup("group") + Page groupPage = groupService.findByPredicate(qGroup.ownerUsername.eq(DUMMY_USER_MAIL), Pageable.ofSize(1)) + assert !groupPage.isEmpty() + } + + @Test + void addAndRemoveUser() { + QGroup qGroup = new QGroup("group") + Group group = groupService.findByPredicate(qGroup.identifier.eq(DUMMY_USER_MAIL), new FullPageRequest()).getContent().get(0) + group = groupService.addUser(group, userService.findUserByUsername(CUSTOMER_USER_MAIL, null).get()) + assert group.getMemberIds().size() == 2 + group = groupService.removeUser(group, userService.findUserByUsername(CUSTOMER_USER_MAIL, null).get()) + assert group.getMemberIds().size() == 1 + } + + @Test + void addAndRemoveRole() { + FileInputStream file = new FileInputStream("src/test/resources/simple_role.xml"); + ImportPetriNetEventOutcome netWithRoleOutcome + new FileInputStream("src/test/resources/simple_role.xml").withCloseable { xml -> + netWithRoleOutcome = petriNetService.importPetriNet(ImportPetriNetParams.with() + .xmlFile(xml) + .releaseType(VersionType.MAJOR) + .author(userService.getSystem()) + .build()) } + file.close() + assert netWithRoleOutcome != null + ProcessRole role = netWithRoleOutcome.getNet().getRoles().values().find { it.importId == "simple_role"} + Group group = groupService.create("addAndRemoveRole", "Add and remove role test group", dummy) + group = groupService.addRole(group.getStringId(), role.getStringId()) + assert group.getProcessRoles().any {it.getStringId() == role.getStringId()} + group = groupService.removeRole(group.getStringId(), role.getStringId()) + assert !group.getProcessRoles().any() {it.getStringId() == role.getStringId()} + } + + @Test + void addAndRemoveSubgroup() { + Group group = groupService.findByIdentifier(dummy.getUsername()).orElse(null) + assert group != null + Group subGroup = groupService.create("addAndRemoveSubgroup", "Add and remove role test group", dummy) + + Pair groupPair = groupService.addSubgroup(group.getStringId(), subGroup.getStringId()) + assert groupPair.getFirst().getSubgroupIds().contains(subGroup.getStringId()) + assert groupPair.getSecond().getGroupIds().contains(group.getStringId()) + + groupPair = groupService.removeSubgroup(group.getStringId(), subGroup.getStringId()) + assert !groupPair.getFirst().getSubgroupIds().contains(subGroup.getStringId()) + assert !groupPair.getSecond().getGroupIds().contains(group.getStringId()) + } - List allGroups = findAllGroups() + @Test + void deleteGroup() { + Group group = groupService.create("deleteGroup", "Delete group test", dummy) + assert groupService.findByIdentifier("deleteGroup").isPresent() + groupService.delete(group) + assert !groupService.findByIdentifier("deleteGroup").isPresent() + } + + @Test + void deleteGroupRemovesMembersAndSubgroups() { + Group parentGroup = groupService.create("parentForDelete", "Parent group", dummy) + Group childGroup = groupService.create("childForDelete", "Child group", dummy) + Pair parentChildGroupPair = groupService.addSubgroup(parentGroup.getStringId(), childGroup.getStringId()) + parentGroup = parentChildGroupPair.getFirst() + childGroup = parentChildGroupPair.getSecond() + groupService.addUser(parentGroup, customer) + + groupService.delete(groupService.findById(parentGroup.getStringId())) + + assert !groupService.findByIdentifier("parentForDelete").isPresent() + Group updatedChild = groupService.findById(childGroup.getStringId()) + assert !updatedChild.getGroupIds().contains(parentGroup.getStringId()) + AbstractUser updatedCustomer = userService.findUserByUsername(CUSTOMER_USER_MAIL, null).get() + assert !updatedCustomer.getGroupIds().contains(parentGroup.getStringId()) + } + + @Test + void saveGroup() { + Group group = groupService.create("saveGroupTest", "Original title", dummy) + group.setDisplayName("Updated title") + Group saved = groupService.save(group) + assert saved.getDisplayName() == "Updated title" + assert saved.getModifiedAt() != null + } + + @Test + void findById() { + Group group = groupService.create("findByIdTest", "Find by id test", dummy) + Group found = groupService.findById(group.getStringId()) + assert found != null + assert found.getIdentifier() == "findByIdTest" + } + + @Test + void findByIdNotFound() { + try { + groupService.findById("nonexistent_id") + assert false : "Should have thrown exception" + } catch (IllegalArgumentException e) { + assert e.getMessage().contains("does not exist") + } + } + + @Test + void findByIdentifier() { + groupService.create("findByIdentifierTest", "Find by identifier test", dummy) + Optional found = groupService.findByIdentifier("findByIdentifierTest") + assert found.isPresent() + assert found.get().getDisplayName() == "Find by identifier test" + } + + @Test + void findByIdentifierNotFound() { + Optional found = groupService.findByIdentifier("nonexistent_identifier") + assert !found.isPresent() + } + + @Test + void findAll() { + Page allGroups = groupService.findAll(new FullPageRequest()) assert !allGroups.isEmpty() + } - List byPredicate = findGroup() - assert !byPredicate.isEmpty() + @Test + void findAllByIds() { + Group group1 = groupService.create("findAllByIds1", "Group 1", dummy) + Group group2 = groupService.create("findAllByIds2", "Group 2", dummy) + Page found = groupService.findAllByIds([group1.getStringId(), group2.getStringId()], new FullPageRequest()) + assert found.getContent().size() == 2 + } - Group addedUserGroup = addUser() - assert !addedUserGroup.getMemberIds().isEmpty() + @Test + void getDefaultSystemGroup() { + Group defaultGroup = groupService.getDefaultSystemGroup() + assert defaultGroup != null + assert defaultGroup.getIdentifier() != null + } - Group removedUserGroup = removeUser() - assert !removedUserGroup.getMemberIds().isEmpty() + @Test + void getDefaultUserGroup() { + Group defaultUserGroup = groupService.getDefaultUserGroup(dummy) + assert defaultUserGroup != null + assert defaultUserGroup.getIdentifier() == dummy.getUsername() } - Group createGroup() { - return groupService.create("CUSTOM_GROUP_1", "CUSTOM_GROUP_1", userService.findUserByUsername(DUMMY_USER_MAIL, null).get()) + @Test + void addAndRemoveAuthority() { + Group group = groupService.create("authorityTest", "Authority test group", dummy) + Authority authority = authorityService.getOrCreate(Authority.user) + group = groupService.addAuthority(group, authority) + assert group.getAuthorityIds().contains(authority.getStringId()) + group = groupService.removeAuthority(group, authority) + assert !group.getAuthorityIds().contains(authority.getStringId()) } - List findGroup() { - QGroup qGroup = new QGroup("group") - return groupService.findByPredicate(qGroup.ownerUsername.eq(DUMMY_USER_MAIL), new FullPageRequest()).getContent() + @Test + void addAndRemoveAuthorityById() { + Group group = groupService.create("authorityByIdTest", "Authority by id test group", dummy) + Authority authority = authorityService.getOrCreate(Authority.admin) + group = groupService.addAuthority(group.getStringId(), authority.getStringId()) + assert group.getAuthorityIds().contains(authority.getStringId()) + group = groupService.removeAuthority(group.getStringId(), authority.getStringId()) + assert !group.getAuthorityIds().contains(authority.getStringId()) } - List findAllGroups() { - return groupService.findAll() as List + @Test + void assignUsersToGroup() { + Group group = groupService.create("assignUsersTest", "Assign users test group", dummy) + Set userIds = [customer.getStringId()] as Set + group = groupService.assignUsersToGroup(group.getStringId(), userIds) + assert group.getMemberIds().contains(customer.getStringId()) } - Group addUser() { - QGroup qGroup = new QGroup("group") - Group group = groupService.findByPredicate(qGroup.identifier.eq("CUSTOM_GROUP_1"), new FullPageRequest()).getContent().get(0) - groupService.addUser(userService.findUserByUsername(CUSTOMER_USER_MAIL, null).get(), group) - groupService.addUser(userService.findUserByUsername(UserConstants.SYSTEM_USER_USERNAME, null).get(), group) - return group + @Test + void assignSubgroups() { + Group parentGroup = groupService.create("assignSubgroupsParent", "Parent", dummy) + Group child1 = groupService.create("assignSubgroupsChild1", "Child 1", dummy) + Group child2 = groupService.create("assignSubgroupsChild2", "Child 2", dummy) + Set childIds = [child1.getStringId(), child2.getStringId()] as Set + groupService.assignSubgroups(parentGroup.getStringId(), childIds) + Group updated = groupService.findById(parentGroup.getStringId()) + assert updated.getSubgroupIds().contains(child1.getStringId()) + assert updated.getSubgroupIds().contains(child2.getStringId()) } - Group removeUser() { - QGroup qGroup = new QGroup("group") - Group group = groupService.findByPredicate(qGroup.identifier.eq("CUSTOM_GROUP_1"), new FullPageRequest()).getContent().get(0) - groupService.removeUser(userService.findUserByUsername(CUSTOMER_USER_MAIL, null).get(), group) - return group + @Test + void getGroupParentGroupsAndSubgroups() { + Group parentGroup = groupService.create("parentGroupNav", "Parent", dummy) + Group childGroup = groupService.create("childGroupNav", "Child", dummy) + groupService.addSubgroup(parentGroup.getStringId(), childGroup.getStringId()) + + List subgroups = groupService.getGroupSubgroupsById(parentGroup.getStringId()) + assert subgroups.any { it.getStringId() == childGroup.getStringId() } + + List parents = groupService.getGroupParentGroupsById(childGroup.getStringId()) + assert parents.any { it.getStringId() == parentGroup.getStringId() } + } + + @Test + void getGroupOwnerEmail() { + Group group = groupService.create("ownerEmailTest", "Owner email test", dummy) + String ownerEmail = groupService.getGroupOwnerEmail(group.getStringId()) + assert ownerEmail == DUMMY_USER_MAIL + } + + @Test + void addSubgroupToItselfThrowsException() { + Group group = groupService.create("selfSubgroup", "Self subgroup test", dummy) + try { + groupService.addSubgroup(group.getStringId(), group.getStringId()) + assert false : "Should have thrown exception" + } catch (IllegalArgumentException e) { + assert e.getMessage().contains("itself") + } + } + + @Test + void addUserByIdAndRealmId() { + Group group = groupService.create("addUserByIdTest", "Add user by id test", dummy) + group = groupService.addUser(group.getStringId(), customer.getStringId(), customer.getRealmId()) + assert group.getMemberIds().contains(customer.getStringId()) + group = groupService.removeUser(group.getStringId(), customer.getStringId(), customer.getRealmId()) + assert !group.getMemberIds().contains(customer.getStringId()) } + + @Test + void assignAuthorities() { + Group group = groupService.create("assignAuthoritiesTest", "Assign authorities test", dummy) + Authority userAuth = authorityService.getOrCreate(Authority.user) + Authority adminAuth = authorityService.getOrCreate(Authority.admin) + Set authorityIds = [userAuth.getStringId(), adminAuth.getStringId()] as Set + group = groupService.assignAuthorities(group.getStringId(), authorityIds) + assert group.getAuthorityIds().contains(userAuth.getStringId()) + assert group.getAuthorityIds().contains(adminAuth.getStringId()) + } + + // ==================== ActionDelegate Group Method Tests ==================== + + @Test + void actionDelegateCreateAndDeleteGroup() { + setSecurityContext(dummy) + try { + Group group = actionDelegate.createGroup("adCreateGroup", "AD Create Group", dummy) + assert group != null + assert group.getIdentifier() == "adCreateGroup" + + actionDelegate.deleteGroup(group) + assert !groupService.findByIdentifier("adCreateGroup").isPresent() + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateFindGroupByIdentifier() { + setSecurityContext(dummy) + try { + groupService.create("adFindByIdentifier", "AD Find test", dummy) + Group found = actionDelegate.findGroupByIdentifier("adFindByIdentifier") + assert found != null + assert found.getIdentifier() == "adFindByIdentifier" + + Group notFound = actionDelegate.findGroupByIdentifier("nonexistent") + assert notFound == null + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateFindGroupById() { + setSecurityContext(dummy) + try { + Group group = groupService.create("adFindById", "AD Find by id test", dummy) + Group found = actionDelegate.findGroupById(group.getStringId()) + assert found != null + assert found.getIdentifier() == "adFindById" + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateFindGroups() { + setSecurityContext(dummy) + try { + groupService.create("adFindGroups1", "AD Find groups 1", dummy) + groupService.create("adFindGroups2", "AD Find groups 2", dummy) + Page groups = actionDelegate.findGroups() + assert groups.getTotalElements() >= 2 + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateSaveGroup() { + setSecurityContext(dummy) + try { + Group group = groupService.create("adSaveGroup", "Original", dummy) + group.setDisplayName("Modified") + Group saved = actionDelegate.saveGroup(group) + assert saved.getDisplayName() == "Modified" + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateAddAndRemoveUserFromGroup() { + setSecurityContext(dummy) + try { + Group group = groupService.create("adUserGroup", "AD User group", dummy) + Group updated = actionDelegate.addUserToGroup(group.getStringId(), customer.getStringId(), customer.getRealmId()) + assert updated.getMemberIds().contains(customer.getStringId()) + + updated = actionDelegate.removeUserFromGroup(group.getStringId(), customer.getStringId(), customer.getRealmId()) + assert !updated.getMemberIds().contains(customer.getStringId()) + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateAddAndRemoveAuthorityFromGroup() { + setSecurityContext(dummy) + try { + Group group = groupService.create("adAuthorityGroup", "AD Authority group", dummy) + Authority authority = authorityService.getOrCreate(Authority.user) + Group updated = actionDelegate.addAuthorityToGroup(group.getStringId(), authority.getStringId()) + assert updated.getAuthorityIds().contains(authority.getStringId()) + + updated = actionDelegate.removeAuthorityFromGroup(group.getStringId(), authority.getStringId()) + assert !updated.getAuthorityIds().contains(authority.getStringId()) + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateAddAndRemoveRoleFromGroup() { + setSecurityContext(dummy) + try { + ImportPetriNetEventOutcome netOutcome = petriNetService.importPetriNet(ImportPetriNetParams.with() + .xmlFile(new FileInputStream("src/test/resources/simple_role.xml")) + .releaseType(VersionType.MAJOR) + .author(userService.getSystem()) + .build()) + ProcessRole role = netOutcome.getNet().getRoles().values().find { it.importId == "simple_role" } + Group group = groupService.create("adRoleGroup", "AD Role group", dummy) + + Group updated = actionDelegate.addRoleToGroup(group.getStringId(), role.getStringId()) + assert updated.getProcessRoles().any { it.getStringId() == role.getStringId() } + + updated = actionDelegate.removeRoleFromGroup(group.getStringId(), role.getStringId()) + assert !updated.getProcessRoles().any { it.getStringId() == role.getStringId() } + } finally { + SecurityContextHolder.clearContext() + } + } + + @Test + void actionDelegateAddAndRemoveSubGroup() { + setSecurityContext(dummy) + try { + Group parentGroup = groupService.create("adSubGroupParent", "AD Parent", dummy) + Group childGroup = groupService.create("adSubGroupChild", "AD Child", dummy) + + Pair result = actionDelegate.addSubGroup(parentGroup.getStringId(), childGroup.getStringId()) + assert result.getFirst().getSubgroupIds().contains(childGroup.getStringId()) + assert result.getSecond().getGroupIds().contains(parentGroup.getStringId()) + + result = actionDelegate.removeSubGroup(parentGroup.getStringId(), childGroup.getStringId()) + assert !result.getFirst().getSubgroupIds().contains(childGroup.getStringId()) + assert !result.getSecond().getGroupIds().contains(parentGroup.getStringId()) + } finally { + SecurityContextHolder.clearContext() + } + } + } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/ElasticSearchViewPermissionTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/ElasticSearchViewPermissionTest.groovy index 85faa76a3f7..5da0c91b22b 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/ElasticSearchViewPermissionTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/ElasticSearchViewPermissionTest.groovy @@ -105,7 +105,7 @@ class ElasticSearchViewPermissionTest { userAuthority = authorityService.getOrCreate(Authority.user) - testUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + testUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Role", lastName: "User", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [userAuthority] as Authority[], [] as ProcessRole[]) } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/QueryDSLViewPermissionTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/QueryDSLViewPermissionTest.groovy index babbe92faa7..4c7b0590e33 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/QueryDSLViewPermissionTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/permissions/QueryDSLViewPermissionTest.groovy @@ -1,6 +1,7 @@ package com.netgrif.application.engine.permissions import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.auth.service.GroupService import com.netgrif.application.engine.auth.service.UserService @@ -98,7 +99,7 @@ class QueryDSLViewPermissionTest { userAuthority = authorityService.getOrCreate(Authority.user) - testUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + testUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [userAuthority] as Authority[], [] as ProcessRole[]) } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy index 81a7a63cf0a..e850b4dc045 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy @@ -134,10 +134,7 @@ class FileFieldTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - def adminUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Admin", lastName: "User", username: UserConstants.ADMIN_USER_USERNAME, email: UserConstants.ADMIN_USER_EMAIL, password: "password", state: UserState.ACTIVE), - [auths.get("admin")] as Authority[], -// [] as Group[], - [] as ProcessRole[]) + def adminUser = userService.findUserByUsername(UserConstants.ADMIN_USER_USERNAME, null).get() auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(adminUser), "password", [auths.get("admin")] as List) auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy index b49f1e0486a..936ed60c18b 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy @@ -134,10 +134,7 @@ class FileListFieldTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - def adminUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Admin", lastName: "User", username: UserConstants.ADMIN_USER_USERNAME, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), - [auths.get("admin")] as Authority[], -// [] as Group[], - [] as ProcessRole[]) + def adminUser = userService.findUserByUsername(UserConstants.ADMIN_USER_USERNAME, null).get() auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(adminUser), "password", [auths.get("admin")] as List) auth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleTest.groovy index 4b971b991e9..90c2d30d336 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleTest.groovy @@ -97,7 +97,7 @@ class ProcessRoleTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) def processRoles = userProcessRoleRepository.findAllByProcessId(this.netId, Pageable.unpaged()).content - AbstractUser viewUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", email: USER_EMAIL_VIEW, password: "password", state: UserState.ACTIVE), + AbstractUser viewUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL_VIEW, email: USER_EMAIL_VIEW, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [processRoles.find { it.getStringId() == net.getNet().roles.values().find { @@ -105,11 +105,11 @@ class ProcessRoleTest { }.stringId }] as ProcessRole[]) - AbstractUser performUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", email: USER_EMAIL_PERFORM, password: "password", state: UserState.ACTIVE), + AbstractUser performUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL_PERFORM, email: USER_EMAIL_PERFORM, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [processRoles.find { it.getStringId() == net.getNet().roles.values().find { it.name.defaultValue == "Perform" }.stringId }] as ProcessRole[]) - AbstractUser bothUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", email: USER_EMAIL_BOTH, password: "password", state: UserState.ACTIVE), + AbstractUser bothUser = importHelper.createUser(new User(firstName: "Test", lastName: "Integration", username: USER_EMAIL_BOTH, email: USER_EMAIL_BOTH, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [processRoles.find { it.getStringId() == net.getNet().roles.values().find { it.name.defaultValue == "View" }.stringId }, processRoles.find { it.getStringId() == net.getNet().roles.values().find { it.name.defaultValue == "Perform" }.stringId }] as ProcessRole[]) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy index 2d2fa7d1f91..dfb2a68d55e 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy @@ -60,7 +60,7 @@ class CachePetriNetServiceTest { void setup() { testHelper.truncateDbs() def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Customer", lastName: "User", email: CUSTOMER_USER_MAIL, password: "password", state: UserState.ACTIVE), + importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Customer", lastName: "User", username: CUSTOMER_USER_MAIL, email: CUSTOMER_USER_MAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], [] as ProcessRole[]) } diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy index d0b1d59ed1b..5bd87b4d7a4 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy @@ -2,6 +2,7 @@ package com.netgrif.application.engine.petrinet.web import com.netgrif.application.engine.TestHelper import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.ipc.TaskApiTest import com.netgrif.application.engine.objects.auth.domain.ActorTransformer @@ -33,6 +34,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders import org.springframework.web.context.WebApplicationContext import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status @@ -98,7 +100,7 @@ class PetriNetControllerTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - def simpleUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + def simpleUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", username: USER_EMAIL, email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], // [] as Group[], [] as ProcessRole[]) @@ -106,7 +108,7 @@ class PetriNetControllerTest { userAuth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(simpleUser), "password", [auths.get("user")] as List) userAuth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) - def adminUser = importHelper.createUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User(firstName: "Admin", lastName: "User", email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), + def adminUser = importHelper.createUser(new User(firstName: "Admin", lastName: "User", username: ADMIN_EMAIL, email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("admin")] as Authority[], // [] as Group[], [] as ProcessRole[]) diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/service/ActorMongoEventListenerTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/ActorMongoEventListenerTest.java new file mode 100644 index 00000000000..0db1df22e9e --- /dev/null +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/ActorMongoEventListenerTest.java @@ -0,0 +1,114 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.adapter.spring.auth.domain.User; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.objects.auth.domain.AbstractActor; +import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith({SpringExtension.class, MockitoExtension.class}) +@ActiveProfiles({"test"}) +@SpringBootTest +public class ActorMongoEventListenerTest { + + @Mock + private ProcessRoleService processRoleService; + + @Mock + private AuthorityService authorityService; + + private ActorMongoEventListener listener; + + static class TestAuthority extends Authority { + private final ObjectId id = new ObjectId(); + + public TestAuthority(String name) { + super(name); + } + + @Override + public ObjectId get_id() { + return id; + } + + @Override + public String getStringId() { + return id.toString(); + } + } + + static class TestProcessRole extends ProcessRole { + public TestProcessRole(String id) { + super(id); + } + } + + @BeforeEach + void setUp() { + listener = new ActorMongoEventListener(processRoleService, authorityService); + } + + @Test + void testOnAfterConvertWithEmptyRolesAndAuthorities() { + User user = new User(); + Document document = new Document(); + + AbstractActor result = listener.onAfterConvert(user, document, "actors"); + + assertSame(user, result); + assertTrue(result.getProcessRoles().isEmpty()); + assertTrue(result.getAuthoritySet().isEmpty()); + } + + @Test + void testOnAfterConvertPopulatesProcessRolesAndAuthorities() { + User user = new User(); + String role1Id = new ProcessResourceId().getFullId(); + String role2Id = new ProcessResourceId().getFullId(); + user.setProcessRoleIds(Set.of(role1Id, role2Id)); + + String auth1Id = new ObjectId().toString(); + String auth2Id = new ObjectId().toString(); + user.setAuthorityIds(Set.of(auth1Id, auth2Id)); + + TestProcessRole role1 = new TestProcessRole(role1Id); + when(processRoleService.findById(role1Id)).thenReturn(role1); + when(processRoleService.findById(role2Id)).thenReturn(null); + + TestAuthority auth1 = new TestAuthority("USER"); + when(authorityService.getOne(auth1Id)).thenReturn(auth1); + when(authorityService.getOne(auth2Id)).thenReturn(null); + + Document doc = new Document(); + AbstractActor result = listener.onAfterConvert(user, doc, "users"); + + assertSame(user, result); + assertEquals(1, result.getProcessRoles().size()); + assertTrue(result.getProcessRoles().contains(role1)); + + assertEquals(1, result.getAuthoritySet().size()); + assertTrue(result.getAuthoritySet().contains(auth1)); + + verify(processRoleService).findById(role1Id); + verify(processRoleService).findById(role2Id); + verify(authorityService).getOne(auth1Id); + verify(authorityService).getOne(auth2Id); + } +} diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactoryTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactoryTest.java new file mode 100644 index 00000000000..7325f2c341a --- /dev/null +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactoryTest.java @@ -0,0 +1,207 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.adapter.spring.auth.domain.Group; +import com.netgrif.application.engine.adapter.spring.auth.domain.User; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.objects.auth.domain.LoggedUser; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith({SpringExtension.class, MockitoExtension.class}) +@ActiveProfiles({"test"}) +@SpringBootTest +class DefaultLoggedUserFactoryTest { + + @Mock + private GroupService groupService; + + @Mock + private ProcessRoleService processRoleService; + + @Mock + private AuthorityService authorityService; + + private DefaultLoggedUserFactory factory; + + static class TestAuthority extends Authority { + private final ObjectId id; + + public TestAuthority(String name) { + super(name); + this.id = new ObjectId(); + } + + public TestAuthority(String id, String name) { + super(name); + this.id = new ObjectId(id); + } + + @Override + public ObjectId get_id() { + return id; + } + + @Override + public String getStringId() { + return id.toString(); + } + } + + static class TestProcessRole extends ProcessRole { + public TestProcessRole(String id) { + super(id); + } + } + + @BeforeEach + void setUp() { + factory = new DefaultLoggedUserFactory(groupService, processRoleService, authorityService); + } + + @Test + void testCreateReturnsLoggedUser() { + LoggedUser loggedUser = factory.create(); + assertNotNull(loggedUser); + } + + @Test + void testResolveProcessRolesDirectAndNestedWithCycles() { + User user = new User(); + String userRoleId = new ProcessResourceId().getFullId(); + user.setProcessRoleIds(Set.of(userRoleId)); + + String group1Id = new ObjectId().toString(); + String group2Id = new ObjectId().toString(); + user.setGroupIds(Set.of(group1Id)); + + Group group1 = new Group(new ObjectId(group1Id)); + group1.setRealmId("realm"); + group1.setIdentifier(group1Id); + String group1RoleId = new ProcessResourceId().getFullId(); + group1.setProcessRoleIds(Set.of(group1RoleId)); + group1.setGroupIds(Set.of(group2Id)); + + Group group2 = new Group(new ObjectId(group2Id)); + group2.setRealmId("realm"); + group2.setIdentifier(group2Id); + String group2RoleId = new ProcessResourceId().getFullId(); + group2.setProcessRoleIds(Set.of(group2RoleId)); + // Cycle: group2 references group1 + group2.setGroupIds(Set.of(group1Id)); + + when(groupService.findById(group1Id)).thenReturn(group1); + when(groupService.findById(group2Id)).thenReturn(group2); + + TestProcessRole userRole = new TestProcessRole(userRoleId); + TestProcessRole group1Role = new TestProcessRole(group1RoleId); + when(processRoleService.findById(userRoleId)).thenReturn(userRole); + when(processRoleService.findById(group1RoleId)).thenReturn(group1Role); + when(processRoleService.findById(group2RoleId)).thenReturn(null); // tests null branch + + factory.resolveProcessRoles(user); + + assertTrue(user.getProcessRoleIds().contains(userRoleId)); + assertTrue(user.getProcessRoleIds().contains(group1RoleId)); + assertTrue(user.getProcessRoleIds().contains(group2RoleId)); + + assertEquals(2, user.getProcessRoles().size()); + assertTrue(user.getProcessRoles().contains(userRole)); + assertTrue(user.getProcessRoles().contains(group1Role)); + } + + @Test + void testResolveProcessRolesEmptyGroups() { + User user = new User(); + String userRoleId = new ProcessResourceId().getFullId(); + user.setProcessRoleIds(Set.of(userRoleId)); + TestProcessRole userRole = new TestProcessRole(userRoleId); + when(processRoleService.findById(userRoleId)).thenReturn(userRole); + + factory.resolveProcessRoles(user); + + assertEquals(1, user.getProcessRoles().size()); + assertTrue(user.getProcessRoles().contains(userRole)); + verifyNoInteractions(groupService); + } + + @Test + void testResolveAuthoritiesDirectAndNestedWithCycles() { + User user = new User(); + String userAuthId = new ObjectId().toString(); + user.setAuthorityIds(Set.of(userAuthId)); + + String group1Id = new ObjectId().toString(); + String group2Id = new ObjectId().toString(); + user.setGroupIds(Set.of(group1Id)); + + Group group1 = new Group(new ObjectId(group1Id)); + group1.setRealmId("realm"); + group1.setIdentifier(group1Id); + String group1AuthId = new ObjectId().toString(); + group1.setAuthorityIds(Set.of(group1AuthId)); + group1.setGroupIds(Set.of(group2Id)); + + Group group2 = new Group(new ObjectId(group2Id)); + group2.setRealmId("realm"); + group2.setIdentifier(group2Id); + String group2AuthId = new ObjectId().toString(); + group2.setAuthorityIds(Set.of(group2AuthId)); + // Cycle: group2 references group1 + group2.setGroupIds(Set.of(group1Id)); + + when(groupService.findById(group1Id)).thenReturn(group1); + when(groupService.findById(group2Id)).thenReturn(group2); + + TestAuthority userAuth = new TestAuthority(userAuthId, "ROLE_USER"); + TestAuthority group1Auth = new TestAuthority(group1AuthId, "ROLE_GROUP1"); + TestAuthority group2Auth = new TestAuthority(group2AuthId, "ROLE_GROUP2"); + + when(authorityService.getOne(userAuthId)).thenReturn(userAuth); + when(authorityService.getOne(group1AuthId)).thenReturn(group1Auth); + when(authorityService.getOne(group2AuthId)).thenReturn(group2Auth); + + factory.resolveAuthorities(user); + + assertTrue(user.getAuthorityIds().contains(userAuthId)); + assertTrue(user.getAuthorityIds().contains(group1AuthId)); + assertTrue(user.getAuthorityIds().contains(group2AuthId)); + + assertEquals(3, user.getAuthoritySet().size()); + assertTrue(user.getAuthoritySet().contains(userAuth)); + assertTrue(user.getAuthoritySet().contains(group1Auth)); + assertTrue(user.getAuthoritySet().contains(group2Auth)); + } + + @Test + void testResolveAuthoritiesEmptyGroups() { + User user = new User(); + String userAuthId = new ObjectId().toString(); + user.setAuthorityIds(Set.of(userAuthId)); + + TestAuthority userAuth = new TestAuthority(userAuthId, "ROLE_USER"); + when(authorityService.getOne(userAuthId)).thenReturn(userAuth); + + factory.resolveAuthorities(user); + + assertEquals(1, user.getAuthoritySet().size()); + assertTrue(user.getAuthoritySet().contains(userAuth)); + verifyNoInteractions(groupService); + } +} diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/service/GroupServiceImplTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/GroupServiceImplTest.java new file mode 100644 index 00000000000..2f8ef0d83d9 --- /dev/null +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/GroupServiceImplTest.java @@ -0,0 +1,667 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.adapter.spring.auth.domain.Group; +import com.netgrif.application.engine.adapter.spring.auth.domain.User; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; +import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; +import com.netgrif.application.engine.auth.provider.CollectionNameProvider; +import com.netgrif.application.engine.auth.repository.GroupRepository; +import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.objects.common.ResourceNotFoundException; +import com.netgrif.application.engine.objects.dto.request.group.GroupSearchRequestDto; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import com.querydsl.core.types.Predicate; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.util.Pair; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith({SpringExtension.class, MockitoExtension.class}) +@ActiveProfiles({"test"}) +@SpringBootTest +class GroupServiceImplTest { + + @Mock + private CollectionNameProvider collectionNameProvider; + + @Mock + private UserService userService; + + @Mock + private GroupRepository groupRepository; + + @Mock + private AuthorityService authorityService; + + @Mock + private GroupConfigurationProperties groupConfigurationProperties; + + @Mock + private PaginationProperties paginationProperties; + + @Mock + private MongoTemplate mongoTemplate; + + @Mock + private ProcessRoleService processRoleService; + + @Autowired + private TestHelper testHelper; + + private GroupServiceImpl service; + + static class TestAuthority extends Authority { + private final ObjectId id; + + public TestAuthority(String name) { + super(name); + this.id = new ObjectId(); + } + + public TestAuthority(String id, String name) { + super(name); + this.id = new ObjectId(id); + } + + @Override + public ObjectId get_id() { + return id; + } + + @Override + public String getStringId() { + return id.toString(); + } + } + + static class TestProcessRole extends ProcessRole { + public TestProcessRole(String id) { + super(id); + } + } + + @BeforeEach + void setUp() { + testHelper.truncateDbs(); + service = new GroupServiceImpl(); + service.setCollectionNameProvider(collectionNameProvider); + service.setUserService(userService); + service.setGroupRepository(groupRepository); + service.setAuthorityService(authorityService); + service.setGroupConfigurationProperties(groupConfigurationProperties); + service.setPaginationProperties(paginationProperties); + service.setMongoTemplate(mongoTemplate); + service.setProcessRoleService(processRoleService); + } + + @Test + void testGetters() { + assertSame(collectionNameProvider, service.getCollectionNameProvider()); + assertSame(userService, service.getUserService()); + assertSame(groupRepository, service.getGroupRepository()); + assertSame(authorityService, service.getAuthorityService()); + assertSame(groupConfigurationProperties, service.getGroupConfigurationProperties()); + assertSame(paginationProperties, service.getPaginationProperties()); + assertSame(mongoTemplate, service.getMongoTemplate()); + assertSame(processRoleService, service.getProcessRoleService()); + } + + @Test + void testSave() { + Group group = new Group(new ObjectId()); + group.setIdentifier("grp1"); + + when(groupRepository.existsById(group.getStringId())).thenReturn(false); + when(groupRepository.save(group)).thenReturn(group); + + com.netgrif.application.engine.objects.auth.domain.Group saved = service.save(group); + assertSame(group, saved); + assertNotNull(group.getModifiedAt()); + + when(groupRepository.existsById(group.getStringId())).thenReturn(true); + saved = service.save(group); + assertSame(group, saved); + } + + @Test + void testDeleteThrowsWhenGroupDoesNotExist() { + Group group = new Group(new ObjectId()); + when(groupRepository.existsById(group.getStringId())).thenReturn(false); + + assertThrows(IllegalArgumentException.class, () -> service.delete(group)); + } + + @Test + void testDeleteCascadesMembersSubgroupsAndParentGroups() { + Group group = new Group(new ObjectId()); + group.setRealmId("realm-1"); + group.setIdentifier("main-grp"); + + String memberId = new ObjectId().toString(); + group.addMemberId(memberId); + + String subgroupId = new ObjectId().toString(); + group.addSubGroupId(subgroupId); + + String parentGroupId = new ObjectId().toString(); + group.addGroupId(parentGroupId); + + when(groupRepository.existsById(group.getStringId())).thenReturn(true); + when(paginationProperties.getBackendPageSize()).thenReturn(10); + + User member = new User(); + member.addGroupId(group.getStringId()); + Page memberPage = new PageImpl<>(List.of(member), PageRequest.of(0, 10), 1); + when(userService.findAllByIds(eq(group.getMemberIds()), eq("realm-1"), any(Pageable.class))).thenReturn(memberPage); + + Group subgroup = new Group(new ObjectId(subgroupId)); + subgroup.addGroupId(group.getStringId()); + when(groupRepository.findAllByIdIn(eq(group.getSubgroupIds()), eq(Pageable.unpaged()))).thenReturn(new PageImpl<>(List.of(subgroup))); + + Group parentGroup = new Group(new ObjectId(parentGroupId)); + parentGroup.addSubGroupId(group.getStringId()); + when(groupRepository.findAllByIdIn(eq(group.getGroupIds()), eq(Pageable.unpaged()))).thenReturn(new PageImpl<>(List.of(parentGroup))); + + service.delete(group); + + verify(userService).saveUser(member); + assertFalse(member.getGroupIds().contains(group.getStringId())); + + verify(groupRepository).save(subgroup); + assertFalse(subgroup.getGroupIds().contains(group.getStringId())); + + verify(groupRepository).save(parentGroup); + assertFalse(parentGroup.getSubgroupIds().contains(group.getStringId())); + + verify(groupRepository).delete(group); + } + + @Test + void testDeleteWithEmptyRelations() { + Group group = new Group(new ObjectId()); + when(groupRepository.existsById(group.getStringId())).thenReturn(true); + + service.delete(group); + + verify(groupRepository).delete(group); + verifyNoInteractions(userService); + } + + @Test + void testRemoveAllByRealmId() { + when(paginationProperties.getBackendPageSize()).thenReturn(10); + Group group = new Group(new ObjectId()); + when(groupRepository.existsById(group.getStringId())).thenReturn(true); + + Page page1 = new PageImpl<>(List.of(group)); + Page page2 = new PageImpl<>(List.of()); + when(groupRepository.findAllByRealmId("realm-1", PageRequest.of(0, 10))) + .thenReturn(page1) + .thenReturn(page2); + + service.removeAllByRealmId("realm-1"); + + verify(groupRepository).delete(group); + } + + @Test + void testRemoveAllByRealmIdIn() { + when(paginationProperties.getBackendPageSize()).thenReturn(10); + Group group = new Group(new ObjectId()); + when(groupRepository.existsById(group.getStringId())).thenReturn(true); + + // When realmIds is null or empty, delegates to removeAllGroups() + Page pageAll1 = new PageImpl<>(List.of(group)); + Page pageAll2 = new PageImpl<>(List.of()); + when(groupRepository.findAll(PageRequest.of(0, 10))) + .thenReturn(pageAll1) + .thenReturn(pageAll2); + + service.removeAllByRealmIdIn(null); + verify(groupRepository).delete(group); + + // When realmIds has items + Page pageIn1 = new PageImpl<>(List.of(group)); + Page pageIn2 = new PageImpl<>(List.of()); + when(groupRepository.findAllByRealmIdIn(eq(Set.of("realm-1")), eq(PageRequest.of(0, 10)))) + .thenReturn(pageIn1) + .thenReturn(pageIn2); + + service.removeAllByRealmIdIn(Set.of("realm-1")); + verify(groupRepository, times(2)).delete(group); + } + + @Test + void testFindById() { + Group group = new Group(new ObjectId()); + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + + com.netgrif.application.engine.objects.auth.domain.Group found = service.findById(group.getStringId()); + assertSame(group, found); + + when(groupRepository.findById("unknown")).thenReturn(Optional.empty()); + assertThrows(IllegalArgumentException.class, () -> service.findById("unknown")); + } + + @Test + void testFindByIdentifier() { + Group group = new Group(new ObjectId()); + when(groupRepository.findByIdentifier("ident")).thenReturn(Optional.of(group)); + + Optional result = service.findByIdentifier("ident"); + assertTrue(result.isPresent()); + assertSame(group, result.get()); + } + + @Test + void testGetDefaultSystemGroup() { + when(groupConfigurationProperties.getDefaultGroupIdentifier()).thenReturn("DEFAULT_SYSTEM_GRP"); + when(groupConfigurationProperties.getDefaultGroupTitle()).thenReturn("Default System Group"); + + User systemUser = new User(); + systemUser.setUsername("system"); + systemUser.setRealmId("default"); + when(userService.getSystem()).thenReturn(systemUser); + + when(groupRepository.existsByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(false, false, true); + when(groupRepository.save(any(Group.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + com.netgrif.application.engine.objects.auth.domain.Group group = service.getDefaultSystemGroup(); + assertNotNull(group); + assertEquals("DEFAULT_SYSTEM_GRP", group.getIdentifier()); + + // When already exists and cached + assertSame(group, service.getDefaultSystemGroup()); + + // When exists in repo but defaultSystemGroup is not yet cached in field + GroupServiceImpl freshService = new GroupServiceImpl(); + freshService.setGroupRepository(groupRepository); + freshService.setGroupConfigurationProperties(groupConfigurationProperties); + + Group existingGroup = new Group("DEFAULT_SYSTEM_GRP", "default"); + when(groupRepository.existsByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(true); + when(groupRepository.findByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(Optional.of(existingGroup)); + + com.netgrif.application.engine.objects.auth.domain.Group resolved = freshService.getDefaultSystemGroup(); + assertSame(existingGroup, resolved); + + // When existsByIdentifier is true but findByIdentifier returns empty + GroupServiceImpl errorService = new GroupServiceImpl(); + errorService.setGroupRepository(groupRepository); + errorService.setGroupConfigurationProperties(groupConfigurationProperties); + when(groupRepository.findByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(Optional.empty()); + + assertThrows(IllegalStateException.class, errorService::getDefaultSystemGroup); + } + + @Test + void testCreateWithGroupOwner() { + User owner = new User(); + owner.setUsername("owner1"); + owner.setRealmId("realm-1"); + + User systemUser = new User(); + systemUser.setUsername("system"); + when(userService.getSystem()).thenReturn(systemUser); + + when(groupRepository.findByOwnerId(owner.getStringId(), Pageable.ofSize(1))) + .thenReturn(new PageImpl<>(List.of(new Group("grp1", "realm-1")))); + + assertThrows(IllegalArgumentException.class, () -> service.create(owner)); + + // When no existing groups for owner + when(groupRepository.findByOwnerId(owner.getStringId(), Pageable.ofSize(1))) + .thenReturn(new PageImpl<>(List.of())); + when(groupRepository.existsByIdentifier(owner.getUsername())).thenReturn(false); + when(groupRepository.save(any(Group.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + com.netgrif.application.engine.objects.auth.domain.Group created = service.create(owner); + assertNotNull(created); + assertEquals("owner1", created.getIdentifier()); + verify(userService).saveUser(owner, "realm-1"); + } + + @Test + void testCreateWithIdentifierTitleAndOwner() { + User owner = new User(); + owner.setUsername("owner1"); + owner.setRealmId("realm-1"); + + assertThrows(IllegalArgumentException.class, () -> service.create(null, "Title", owner)); + assertThrows(IllegalArgumentException.class, () -> service.create(" ", "Title", owner)); + + when(groupRepository.existsByIdentifier("IDENT")).thenReturn(true); + assertThrows(IllegalArgumentException.class, () -> service.create("IDENT", "Title", owner)); + + when(groupRepository.existsByIdentifier("IDENT")).thenReturn(false); + when(groupRepository.save(any(Group.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + com.netgrif.application.engine.objects.auth.domain.Group created = service.create("IDENT", "Title", owner); + assertEquals("IDENT", created.getIdentifier()); + assertEquals("Title", created.getDisplayName()); + assertEquals(owner.getStringId(), created.getOwnerId()); + assertTrue(created.getMemberIds().contains(owner.getStringId())); + assertTrue(owner.getGroupIds().contains(created.getStringId())); + } + + @Test + void testGetDefaultUserGroup() { + User user = new User(); + user.setUsername("john"); + + when(paginationProperties.getBackendPageSize()).thenReturn(10); + + Group matchGroup = new Group("john", "realm-1"); + Group otherGroup = new Group("other", "realm-1"); + + Page page = new PageImpl<>(List.of(otherGroup, matchGroup)); + when(groupRepository.findByOwnerId(user.getStringId(), PageRequest.of(0, 10))).thenReturn(page); + + com.netgrif.application.engine.objects.auth.domain.Group result = service.getDefaultUserGroup(user); + assertSame(matchGroup, result); + + // When not found + Page emptyPage = new PageImpl<>(List.of(otherGroup)); + when(groupRepository.findByOwnerId(user.getStringId(), PageRequest.of(0, 10))).thenReturn(emptyPage); + + assertThrows(ResourceNotFoundException.class, () -> service.getDefaultUserGroup(user)); + } + + @Test + void testAddUserToDefaultSystemGroup() { + when(groupConfigurationProperties.getDefaultGroupIdentifier()).thenReturn("DEFAULT_SYSTEM_GRP"); + Group defaultGroup = new Group("DEFAULT_SYSTEM_GRP", "default"); + when(groupRepository.existsByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(true); + when(groupRepository.findByIdentifier("DEFAULT_SYSTEM_GRP")).thenReturn(Optional.of(defaultGroup)); + when(groupRepository.save(any(Group.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + User user = new User(); + user.setRealmId("default"); + + service.addUserToDefaultSystemGroup(user); + + assertTrue(defaultGroup.getMemberIds().contains(user.getStringId())); + assertTrue(user.getGroupIds().contains(defaultGroup.getStringId())); + verify(userService).saveUser(user, "default"); + } + + @Test + void testAssignUsersToGroup() { + Group group = new Group(new ObjectId()); + group.setRealmId("realm-1"); + String currentUserId = new ObjectId().toString(); + group.addMemberId(currentUserId); + + String newUserId = new ObjectId().toString(); + + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + when(groupRepository.save(group)).thenReturn(group); + + User currentUser = new User(new ObjectId(currentUserId)); + currentUser.setRealmId("realm-1"); + currentUser.addGroupId(group.getStringId()); + + User newUser = new User(new ObjectId(newUserId)); + newUser.setRealmId("realm-1"); + + when(userService.findById(currentUserId, "realm-1")).thenReturn(currentUser); + when(userService.findById(newUserId, "realm-1")).thenReturn(newUser); + + com.netgrif.application.engine.objects.auth.domain.Group updated = service.assignUsersToGroup(group.getStringId(), Set.of(newUserId)); + + assertFalse(updated.getMemberIds().contains(currentUserId)); + assertTrue(updated.getMemberIds().contains(newUserId)); + + // Test with null userIds + service.assignUsersToGroup(group.getStringId(), null); + } + + @Test + void testAddUserAndRemoveUserOverloads() { + Group group = new Group(new ObjectId()); + group.setRealmId("realm-1"); + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + when(groupRepository.save(group)).thenReturn(group); + + User user = new User(); + user.setRealmId("realm-1"); + when(userService.findById(user.getStringId(), "realm-1")).thenReturn(user); + + // addUser overloads + service.addUser(group.getStringId(), user.getStringId(), "realm-1"); + assertTrue(group.getMemberIds().contains(user.getStringId())); + + service.addUser(group, user.getStringId(), "realm-1"); + service.addUser(group.getStringId(), user); + service.addUser(group, user); + + // removeUser overloads + service.removeUser(group.getStringId(), user.getStringId(), "realm-1"); + service.removeUser(group.getStringId(), user); + service.removeUser(group, user); + assertFalse(group.getMemberIds().contains(user.getStringId())); + } + + @Test + void testFindDelegates() { + Pageable pageable = PageRequest.of(0, 10); + Predicate predicate = mock(Predicate.class); + Query query = new Query(); + + Page page = new PageImpl<>(List.of()); + + when(groupRepository.findAll(predicate, pageable)).thenReturn(page); + assertSame(page, service.findByPredicate(predicate, pageable)); + + when(groupRepository.findAll(query, mongoTemplate, pageable)).thenReturn(page); + assertSame(page, service.findByQuery(query, pageable)); + + when(groupRepository.findAllByIdIn(Set.of("g1"), pageable)).thenReturn(page); + assertSame(page, service.findAllByIds(Set.of("g1"), pageable)); + + when(groupRepository.findAll(pageable)).thenReturn(page); + assertSame(page, service.findAll(pageable)); + + when(groupRepository.findAllByRealmId("r1", pageable)).thenReturn(page); + assertSame(page, service.findAllFromRealm("r1", pageable)); + + when(groupRepository.findAllByRealmIdIn(Set.of("r1"), pageable)).thenReturn(page); + assertSame(page, service.findAllFromRealmIn(Set.of("r1"), pageable)); + + ProcessResourceId roleId = new ProcessResourceId(); + when(groupRepository.findAllByProcessRoles__idIn(Set.of(roleId), pageable)).thenReturn(page); + assertSame(page, service.findAllByProcessRoles(Set.of(roleId), pageable)); + } + + @Test + void testAuthorityOperations() { + Group group = new Group(new ObjectId()); + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + when(groupRepository.save(group)).thenReturn(group); + + String currentAuthId = new ObjectId().toString(); + TestAuthority currentAuth = new TestAuthority(currentAuthId, "CURRENT"); + group.addAuthority(currentAuth); + + String newAuthId = new ObjectId().toString(); + TestAuthority newAuth = new TestAuthority(newAuthId, "NEW"); + + when(authorityService.getOne(currentAuthId)).thenReturn(currentAuth); + when(authorityService.getOne(newAuthId)).thenReturn(newAuth); + + // assignAuthorities null branch + assertSame(group, service.assignAuthorities(group.getStringId(), null)); + + // assignAuthorities diff + com.netgrif.application.engine.objects.auth.domain.Group updated = service.assignAuthorities(group.getStringId(), Set.of(newAuthId)); + assertTrue(updated.getAuthorityIds().contains(newAuthId)); + assertFalse(updated.getAuthorityIds().contains(currentAuthId)); + + // addAuthority / removeAuthority overloads + service.addAuthority(group.getStringId(), currentAuthId); + assertTrue(group.getAuthorityIds().contains(currentAuthId)); + + service.removeAuthority(group.getStringId(), currentAuthId); + assertFalse(group.getAuthorityIds().contains(currentAuthId)); + } + + @Test + void testSubgroupOperations() { + Group parent = new Group(new ObjectId()); + parent.setRealmId("realm-1"); + Group child = new Group(new ObjectId()); + child.setRealmId("realm-1"); + + when(groupRepository.findById(parent.getStringId())).thenReturn(Optional.of(parent)); + when(groupRepository.findById(child.getStringId())).thenReturn(Optional.of(child)); + when(groupRepository.save(parent)).thenReturn(parent); + when(groupRepository.save(child)).thenReturn(child); + + // Self addition throws + assertThrows(IllegalArgumentException.class, () -> service.addSubgroup(parent.getStringId(), parent.getStringId())); + assertThrows(IllegalArgumentException.class, () -> service.addSubgroup(parent, parent.getStringId())); + assertThrows(IllegalArgumentException.class, () -> service.addSubgroup(parent.getStringId(), parent)); + assertThrows(IllegalArgumentException.class, () -> service.addSubgroup(parent, parent)); + + // Realm mismatch throws + Group diffRealmChild = new Group(new ObjectId()); + diffRealmChild.setRealmId("realm-2"); + assertThrows(IllegalArgumentException.class, () -> service.addSubgroup(parent, diffRealmChild)); + + // Successful addSubgroup overloads + Pair pair = + service.addSubgroup(parent.getStringId(), child.getStringId()); + assertNotNull(pair); + assertTrue(parent.getSubgroupIds().contains(child.getStringId())); + assertTrue(child.getGroupIds().contains(parent.getStringId())); + + service.addSubgroup(parent, child.getStringId()); + service.addSubgroup(parent.getStringId(), child); + + // assignSubgroups + assertSame(parent, service.assignSubgroups(parent.getStringId(), null)); + String newChildId = new ObjectId().toString(); + Group newChild = new Group(new ObjectId(newChildId)); + newChild.setRealmId("realm-1"); + when(groupRepository.findById(newChildId)).thenReturn(Optional.of(newChild)); + + service.assignSubgroups(parent.getStringId(), Set.of(newChildId)); + assertTrue(parent.getSubgroupIds().contains(newChildId)); + assertFalse(parent.getSubgroupIds().contains(child.getStringId())); + + // removeSubgroup self removal throws + assertThrows(IllegalArgumentException.class, () -> service.removeSubgroup(parent.getStringId(), parent.getStringId())); + assertThrows(IllegalArgumentException.class, () -> service.removeSubgroup(parent, parent.getStringId())); + assertThrows(IllegalArgumentException.class, () -> service.removeSubgroup(parent.getStringId(), parent)); + assertThrows(IllegalArgumentException.class, () -> service.removeSubgroup(parent, parent)); + + // Successful removeSubgroup overloads + service.removeSubgroup(parent.getStringId(), newChildId); + assertFalse(parent.getSubgroupIds().contains(newChildId)); + + parent.addSubGroupId(newChildId); + newChild.addGroupId(parent.getStringId()); + service.removeSubgroup(parent, newChildId); + + parent.addSubGroupId(newChildId); + newChild.addGroupId(parent.getStringId()); + service.removeSubgroup(parent.getStringId(), newChild); + } + + @Test + void testHierarchyAndEmailQueries() { + Group group = new Group(new ObjectId()); + group.setRealmId("realm-1"); + group.setOwnerId(new ObjectId().toString()); + + String parentId = new ObjectId().toString(); + group.addGroupId(parentId); + Group parent = new Group(new ObjectId(parentId)); + + String childId = new ObjectId().toString(); + group.addSubGroupId(childId); + Group child = new Group(new ObjectId(childId)); + + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + when(groupRepository.findAllByIdIn(eq(Set.of(parentId)), eq(Pageable.unpaged()))).thenReturn(new PageImpl<>(List.of(parent))); + when(groupRepository.findAllByIdIn(eq(Set.of(childId)), eq(Pageable.unpaged()))).thenReturn(new PageImpl<>(List.of(child))); + + assertEquals(List.of(parent), service.getGroupParentGroupsById(group.getStringId())); + assertEquals(List.of(child), service.getGroupSubgroupsById(group.getStringId())); + + // empty relations + Group isolated = new Group(new ObjectId()); + assertTrue(service.getGroupParentGroups(isolated).isEmpty()); + assertTrue(service.getGroupSubgroups(isolated).isEmpty()); + + // emails + User owner = new User(); + owner.setEmail("owner@example.com"); + when(userService.findById(group.getOwnerId(), "realm-1")).thenReturn(owner); + + assertEquals("owner@example.com", service.getGroupOwnerEmail(group.getStringId())); + when(groupRepository.findAllByIdIn(eq(Set.of(group.getStringId())), eq(Pageable.unpaged()))).thenReturn(new PageImpl<>(List.of(group))); + assertEquals(List.of("owner@example.com"), service.getGroupsOwnerEmails(Set.of(group.getStringId()))); + } + + @Test + void testProcessRoleOperations() { + Group group = new Group(new ObjectId()); + when(groupRepository.findById(group.getStringId())).thenReturn(Optional.of(group)); + when(groupRepository.save(group)).thenReturn(group); + + String roleCompositeId = new ProcessResourceId().getFullId(); + TestProcessRole role = new TestProcessRole(roleCompositeId); + when(processRoleService.findById(any(ProcessResourceId.class))).thenReturn(role); + + service.addRole(group.getStringId(), roleCompositeId); + assertTrue(group.getProcessRoleIds().contains(roleCompositeId)); + + service.removeRole(group.getStringId(), roleCompositeId); + assertFalse(group.getProcessRoleIds().contains(roleCompositeId)); + } + + @Test + void testSearch() { + GroupSearchRequestDto searchDto = new GroupSearchRequestDto(); + searchDto.setIds(Set.of("g1")); + searchDto.setFullText("test"); + searchDto.setRealmId("realm-1"); + + Pageable pageable = PageRequest.of(0, 10); + when(mongoTemplate.count(any(Query.class), eq(com.netgrif.application.engine.objects.auth.domain.Group.class))).thenReturn(1L); + when(mongoTemplate.find(any(Query.class), eq(com.netgrif.application.engine.objects.auth.domain.Group.class))).thenReturn(List.of(new Group("g1", "realm-1"))); + + Page result = service.search(searchDto, pageable); + assertEquals(1, result.getTotalElements()); + + // empty searchDto + Page emptyResult = service.search(null, pageable); + assertNotNull(emptyResult); + } +} + diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/service/RegistrationServiceUtilityTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/RegistrationServiceUtilityTest.java index 533cf4958f4..afdef1093b0 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/auth/service/RegistrationServiceUtilityTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/service/RegistrationServiceUtilityTest.java @@ -164,7 +164,7 @@ void createNewUserInitializesInactiveUserWithRolesAndGroups() { verify(userService).addDefaultAuthorities(any(AbstractUser.class)); verify(userService).addRole(any(AbstractUser.class), eq("role-a")); verify(userService).addRole(any(AbstractUser.class), eq(defaultRole.getStringId())); - verify(groupService).addUser(saved, "group-a"); + verify(groupService).addUser("group-a", saved); } @Test diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/web/AuthenticationControllerTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/AuthenticationControllerTest.java index 91646774334..36a86995a60 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/auth/web/AuthenticationControllerTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/AuthenticationControllerTest.java @@ -7,6 +7,7 @@ import com.netgrif.application.engine.auth.web.requestbodies.ChangePasswordRequest; import com.netgrif.application.engine.auth.web.requestbodies.NewUserRequest; import com.netgrif.application.engine.auth.web.requestbodies.RegistrationRequest; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; import com.netgrif.application.engine.mail.interfaces.IMailAttemptService; import com.netgrif.application.engine.mail.interfaces.IMailService; @@ -312,8 +313,8 @@ void changePasswordUpdatesSelfAndReloadsSecurityContext() { @Test void loginUsesSecurityContextAuthentication() { - com.netgrif.application.engine.auth.web.responsebodies.User responseUser = - org.mockito.Mockito.mock(com.netgrif.application.engine.auth.web.responsebodies.User.class); + UserDto responseUser = + org.mockito.Mockito.mock(UserDto.class); when(loggedUser.getStringId()).thenReturn("self"); when(userService.findById("self", null)).thenReturn(user); when(userFactory.getUser(user, Locale.ENGLISH)).thenReturn(responseUser); diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/web/GroupControllerTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/GroupControllerTest.java new file mode 100644 index 00000000000..dc54840b628 --- /dev/null +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/GroupControllerTest.java @@ -0,0 +1,442 @@ +package com.netgrif.application.engine.auth.web; + +import com.netgrif.application.engine.adapter.spring.common.web.responsebodies.ResponseMessage; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.auth.service.RealmService; +import com.netgrif.application.engine.auth.service.UserFactory; +import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; +import com.netgrif.application.engine.objects.dto.request.group.CreateGroupRequestDto; +import com.netgrif.application.engine.objects.dto.request.group.GroupSearchRequestDto; +import com.netgrif.application.engine.objects.dto.request.group.UpdateGroupRequestDto; +import com.netgrif.application.engine.objects.dto.response.group.GroupDto; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class GroupControllerTest { + + @Mock + private GroupService groupService; + + @Mock + private UserService userService; + + @Mock + private ProcessRoleService processRoleService; + + @Mock + private RealmService realmService; + + @Mock + private UserFactory userFactory; + + private GroupController controller; + + static class DomainGroup extends com.netgrif.application.engine.adapter.spring.auth.domain.Group { + public DomainGroup(String id, String identifier, String displayName, String realmId) { + super(); + this.id = new ObjectId(id); + this.setIdentifier(identifier); + this.setDisplayName(displayName); + this.realmId = realmId; + } + } + + static class DomainUser extends com.netgrif.application.engine.adapter.spring.auth.domain.User { + public DomainUser(String id, String username, String realmId) { + super(); + this.id = new ObjectId(id); + this.username = username; + this.realmId = realmId; + } + } + + static class TestProcessRole extends ProcessRole { + public TestProcessRole(String id) { + super(id); + } + } + + @BeforeEach + void setUp() { + controller = new GroupController(groupService, userService, processRoleService, realmService, userFactory); + } + + @Test + void testGetAllGroupsOfRealm() { + String realmId = "realm-1"; + Pageable pageable = PageRequest.of(0, 10); + DomainGroup group = new DomainGroup(new ObjectId().toString(), "grp1", "Group 1", realmId); + when(groupService.findAllFromRealm(realmId, pageable)).thenReturn(new PageImpl<>(List.of(group), pageable, 1)); + + ResponseEntity> response = controller.getAllGroupsOfRealm(realmId, pageable, Locale.ENGLISH); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().getTotalElements()); + assertEquals("grp1", response.getBody().getContent().get(0).identifier()); + } + + @Test + void testCreateGroup() { + // null request + ResponseEntity nullResponse = controller.createGroup(null); + assertEquals(HttpStatus.BAD_REQUEST, nullResponse.getStatusCode()); + + String ownerId = new ObjectId().toString(); + CreateGroupRequestDto request = new CreateGroupRequestDto("Display Name", "realm-1", "ident-1", ownerId); + + // Realm does not exist + when(realmService.getRealmById("realm-1")).thenReturn(Optional.empty()); + ResponseEntity noRealmResponse = controller.createGroup(request); + assertEquals(HttpStatus.BAD_REQUEST, noRealmResponse.getStatusCode()); + assertTrue(noRealmResponse.getBody().getError().contains("does not exist")); + + // Realm with null id branch + CreateGroupRequestDto nullRealmRequest = new CreateGroupRequestDto("Display Name", null, "ident-1", ownerId); + ResponseEntity nullRealmResponse = controller.createGroup(nullRealmRequest); + assertEquals(HttpStatus.BAD_REQUEST, nullRealmResponse.getStatusCode()); + + // Realm exists, but user not found + when(realmService.getRealmById("realm-1")).thenReturn(Optional.of(new com.netgrif.application.engine.adapter.spring.auth.domain.Realm("realm-1"))); + when(userService.findById(ownerId, "realm-1")).thenReturn(null); + ResponseEntity noUserResponse = controller.createGroup(request); + assertEquals(HttpStatus.NOT_FOUND, noUserResponse.getStatusCode()); + + // User found, but service throws IllegalArgumentException + DomainUser owner = new DomainUser(ownerId, "john", "realm-1"); + when(userService.findById(ownerId, "realm-1")).thenReturn(owner); + doThrow(new IllegalArgumentException("Already exists")).when(groupService).create("ident-1", "Display Name", owner); + ResponseEntity errorResponse = controller.createGroup(request); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + + // Service throws general Exception + doThrow(new RuntimeException("DB error")).when(groupService).create("ident-1", "Display Name", owner); + ResponseEntity serverErrorResponse = controller.createGroup(request); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, serverErrorResponse.getStatusCode()); + + // Success + DomainGroup createdGroup = new DomainGroup(new ObjectId().toString(), "ident-1", "Display Name", "realm-1"); + doReturn(createdGroup).when(groupService).create("ident-1", "Display Name", owner); + ResponseEntity successResponse = controller.createGroup(request); + assertEquals(HttpStatus.CREATED, successResponse.getStatusCode()); + assertNotNull(successResponse.getHeaders().getLocation()); + assertTrue(successResponse.getHeaders().getLocation().toString().contains(createdGroup.getStringId())); + } + + @Test + void testDeleteGroup() { + String groupId = new ObjectId().toString(); + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + + when(groupService.findById(groupId)).thenReturn(group); + doNothing().when(groupService).delete(group); + + ResponseEntity response = controller.deleteGroup(groupId); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getBody().contains("deleted successfully")); + + // Throws IllegalArgumentException + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.deleteGroup("unknown"); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testGetGroup() { + String groupId = new ObjectId().toString(); + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + + when(groupService.findById(groupId)).thenReturn(group); + ResponseEntity response = controller.getGroup(groupId, Locale.ENGLISH); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("ident", response.getBody().identifier()); + + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.getGroup("unknown", Locale.ENGLISH); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testGetMembersOfGroup() { + String groupId = new ObjectId().toString(); + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + String memberId = new ObjectId().toString(); + group.addMemberId(memberId); + + Pageable pageable = PageRequest.of(0, 10); + when(groupService.findById(groupId)).thenReturn(group); + + DomainUser member = new DomainUser(memberId, "member", "realm-1"); + when(userService.findAllByIds(group.getMemberIds(), "realm-1", pageable)).thenReturn(new PageImpl<>(List.of(member))); + UserDto userDto = new UserDto(member); + when(userFactory.getUser(member, Locale.ENGLISH)).thenReturn(userDto); + + ResponseEntity> response = controller.getMembersOfGroup(groupId, pageable, Locale.ENGLISH); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().getTotalElements()); + + // Group not found + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity> errorResponse = controller.getMembersOfGroup("unknown", pageable, Locale.ENGLISH); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAssignUsersToGroup() { + String groupId = new ObjectId().toString(); + Set userIds = Set.of(new ObjectId().toString()); + + ResponseEntity response = controller.assignUsersToGroup(groupId, userIds); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).assignUsersToGroup(groupId, userIds); + + doThrow(new IllegalArgumentException("Invalid user")).when(groupService).assignUsersToGroup("bad-id", userIds); + ResponseEntity errorResponse = controller.assignUsersToGroup("bad-id", userIds); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAddUserToGroup() { + String groupId = new ObjectId().toString(); + String userId = new ObjectId().toString(); + + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + DomainUser user = new DomainUser(userId, "user1", "realm-1"); + + when(groupService.findById(groupId)).thenReturn(group); + when(userService.findById(userId, "realm-1")).thenReturn(user); + + ResponseEntity response = controller.addUserToGroup(groupId, userId); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).addUser(group, user); + + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.addUserToGroup("unknown", userId); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testRemoveUserFromGroup() { + String groupId = new ObjectId().toString(); + String userId = new ObjectId().toString(); + + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + DomainUser user = new DomainUser(userId, "user1", "realm-1"); + + when(groupService.findById(groupId)).thenReturn(group); + when(userService.findById(userId, "realm-1")).thenReturn(user); + + ResponseEntity response = controller.removeUserFromGroup(groupId, userId); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).removeUser(group, user); + + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.removeUserFromGroup("unknown", userId); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAssignRolesToGroup() { + String groupId = new ObjectId().toString(); + String roleId = new ProcessResourceId().getFullId(); + Set roleIds = Set.of(roleId); + + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + when(groupService.findById(groupId)).thenReturn(group); + + ResponseEntity response = controller.assignRolesToGroup(groupId, roleIds); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(processRoleService).assignRolesToGroup(eq(group), any()); + + when(groupService.findById("bad-id")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.assignRolesToGroup("bad-id", roleIds); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAddRolesToGroup() { + String groupId = new ObjectId().toString(); + String roleId = new ProcessResourceId().getFullId(); + Set roleIds = Set.of(roleId); + + DomainGroup group = new DomainGroup(groupId, "ident", "Group", "realm-1"); + when(groupService.findById(groupId)).thenReturn(group); + + TestProcessRole processRole = new TestProcessRole(roleId); + when(processRoleService.findById(roleId)).thenReturn(processRole); + + ResponseEntity response = controller.addRolesToGroup(groupId, roleIds); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).addRole(group, processRole); + + // Role not found branch + when(processRoleService.findById("missing-role")).thenReturn(null); + ResponseEntity missingRoleResponse = controller.addRolesToGroup(groupId, Set.of("missing-role")); + assertEquals(HttpStatus.BAD_REQUEST, errorResponseStatusCode(missingRoleResponse)); + + // Group not found branch + when(groupService.findById("unknown")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity errorResponse = controller.addRolesToGroup("unknown", roleIds); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + private HttpStatus errorResponseStatusCode(ResponseEntity response) { + return (HttpStatus) response.getStatusCode(); + } + + @Test + void testRevokeRolesFromGroup() { + String groupId = new ObjectId().toString(); + String roleId = new ProcessResourceId().getFullId(); + + ResponseEntity response = controller.revokeRolesFromGroup(groupId, Set.of(roleId)); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).removeRole(groupId, roleId); + + doThrow(new IllegalArgumentException("Error")).when(groupService).removeRole("bad", roleId); + ResponseEntity errorResponse = controller.revokeRolesFromGroup("bad", Set.of(roleId)); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAddAuthorityToGroup() { + String groupId = new ObjectId().toString(); + String authId = new ObjectId().toString(); + + ResponseEntity response = controller.addAuthorityToGroup(groupId, Set.of(authId)); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).addAuthority(groupId, authId); + + doThrow(new IllegalArgumentException("Error")).when(groupService).addAuthority("bad", authId); + ResponseEntity errorResponse = controller.addAuthorityToGroup("bad", Set.of(authId)); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testRevokeAuthorityFromGroup() { + String groupId = new ObjectId().toString(); + String authId = new ObjectId().toString(); + + ResponseEntity response = controller.revokeAuthorityFromGroup(groupId, Set.of(authId)); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).removeAuthority(groupId, authId); + + doThrow(new IllegalArgumentException("Error")).when(groupService).removeAuthority("bad", authId); + ResponseEntity errorResponse = controller.revokeAuthorityFromGroup("bad", Set.of(authId)); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testSearch() { + GroupSearchRequestDto query = new GroupSearchRequestDto(); + Pageable pageable = PageRequest.of(0, 10); + DomainGroup group = new DomainGroup(new ObjectId().toString(), "ident", "Group", "realm-1"); + + when(groupService.search(query, pageable)).thenReturn(new PageImpl<>(List.of(group))); + + ResponseEntity> response = controller.search(query, pageable, Locale.ENGLISH); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().getTotalElements()); + } + + @Test + void testUpdateGroup() { + String groupId = new ObjectId().toString(); + DomainGroup group = new DomainGroup(groupId, "old-ident", "Old Name", "realm-1"); + + when(groupService.findById(groupId)).thenReturn(group); + when(groupService.findByIdentifier("new-ident")).thenReturn(Optional.empty()); + + UpdateGroupRequestDto updateDto = new UpdateGroupRequestDto(groupId, "new-ident", "New Name"); + ResponseEntity response = controller.updateGroup(updateDto); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("new-ident", group.getIdentifier()); + assertEquals("New Name", group.getDisplayName()); + verify(groupService).save(group); + + // When identifier is already taken by another group + when(groupService.findByIdentifier("taken-ident")).thenReturn(Optional.of(new DomainGroup(new ObjectId().toString(), "taken-ident", "Other", "realm-1"))); + UpdateGroupRequestDto takenDto = new UpdateGroupRequestDto(groupId, "taken-ident", null); + controller.updateGroup(takenDto); + assertEquals("new-ident", group.getIdentifier()); // identifier not modified + + // When group not found (IllegalArgumentException) + when(groupService.findById("missing")).thenThrow(new IllegalArgumentException("Not found")); + ResponseEntity notFoundResponse = controller.updateGroup(new UpdateGroupRequestDto("missing", "id", "name")); + assertEquals(HttpStatus.NOT_FOUND, notFoundResponse.getStatusCode()); + + // When unexpected Exception occurs + when(groupService.findById("error")).thenReturn(group); + when(groupService.save(group)).thenThrow(new RuntimeException("Save failed")); + ResponseEntity errorResponse = controller.updateGroup(new UpdateGroupRequestDto("error", null, "name")); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, errorResponse.getStatusCode()); + } + + @Test + void testAssignSubgroupsToGroup() { + String groupId = new ObjectId().toString(); + Set subgroupIds = Set.of(new ObjectId().toString()); + + ResponseEntity response = controller.assignSubgroupsToGroup(groupId, subgroupIds); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).assignSubgroups(groupId, subgroupIds); + + doThrow(new IllegalArgumentException("Error")).when(groupService).assignSubgroups("bad", subgroupIds); + ResponseEntity errorResponse = controller.assignSubgroupsToGroup("bad", subgroupIds); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testAddSubgroupsToGroup() { + String groupId = new ObjectId().toString(); + String subgroupId = new ObjectId().toString(); + + ResponseEntity response = controller.addSubgroupsToGroup(groupId, Set.of(subgroupId)); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).addSubgroup(groupId, subgroupId); + + doThrow(new IllegalArgumentException("Error")).when(groupService).addSubgroup("bad", subgroupId); + ResponseEntity errorResponse = controller.addSubgroupsToGroup("bad", Set.of(subgroupId)); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } + + @Test + void testRemoveSubgroupsFromGroup() { + String groupId = new ObjectId().toString(); + String subgroupId = new ObjectId().toString(); + + ResponseEntity response = controller.removeSubgroupsFromGroup(groupId, Set.of(subgroupId)); + assertEquals(HttpStatus.OK, response.getStatusCode()); + verify(groupService).removeSubgroup(groupId, subgroupId); + + doThrow(new IllegalArgumentException("Error")).when(groupService).removeSubgroup("bad", subgroupId); + ResponseEntity errorResponse = controller.removeSubgroupsFromGroup("bad", Set.of(subgroupId)); + assertEquals(HttpStatus.BAD_REQUEST, errorResponse.getStatusCode()); + } +} diff --git a/application-engine/src/test/java/com/netgrif/application/engine/auth/web/UserControllerTest.java b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/UserControllerTest.java index adaeb2d6ed5..7246e219d2d 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/auth/web/UserControllerTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/auth/web/UserControllerTest.java @@ -6,7 +6,7 @@ import com.netgrif.application.engine.auth.web.requestbodies.UserCreateRequest; import com.netgrif.application.engine.auth.web.requestbodies.UserSearchRequestBody; import com.netgrif.application.engine.auth.web.responsebodies.PreferencesResource; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -103,14 +103,14 @@ void savePreferencesStoresPreferencesForLoggedUser() { @Test void createUserReturnsCreatedUserWhenRealmExistsAndUsernameIsFree() { AbstractUser domainUser = domainUser("john"); - User responseUser = new User(domainUser); + UserDto responseUser = new UserDto(domainUser); UserCreateRequest request = createRequest("john"); when(realmService.getRealmById("realm")).thenReturn(Optional.of(realm("realm"))); when(userService.findUserByUsername("john", "realm")).thenReturn(Optional.empty()); when(userService.createUser("john", "john@example.com", "John", "User", "secret", "realm")).thenReturn(domainUser); when(userFactory.getUser(domainUser, Locale.ENGLISH)).thenReturn(responseUser); - ResponseEntity response = controller.createUser("realm", request, Locale.ENGLISH); + ResponseEntity response = controller.createUser("realm", request, Locale.ENGLISH); assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertSame(responseUser, response.getBody()); @@ -133,13 +133,13 @@ void createUserReportsBadRealmConflictAndUnexpectedFailure() { @Test void getAllUsersTransformsUsersWhenRealmExists() { AbstractUser domainUser = domainUser("john"); - User responseUser = new User(domainUser); + UserDto responseUser = new UserDto(domainUser); Pageable pageable = PageRequest.of(0, 10); when(realmService.getRealmById("realm")).thenReturn(Optional.of(realm("realm"))); when(userService.findAllUsers("realm", pageable)).thenReturn(new PageImpl<>(List.of(domainUser), pageable, 1)); when(userFactory.getUser(domainUser, Locale.ENGLISH)).thenReturn(responseUser); - ResponseEntity> response = controller.getAllUsers("realm", pageable, Locale.ENGLISH); + ResponseEntity> response = controller.getAllUsers("realm", pageable, Locale.ENGLISH); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(1, response.getBody().getTotalElements()); @@ -151,14 +151,14 @@ void getAllUsersTransformsUsersWhenRealmExists() { @Test void getLoggedUserHandlesSuccessMissingAndBadIds() { AbstractUser domainUser = domainUser("john"); - User responseUser = new User(domainUser); + UserDto responseUser = new UserDto(domainUser); when(authentication.getPrincipal()).thenReturn(loggedUser); when(loggedUser.getStringId()).thenReturn("john-id"); when(loggedUser.getRealmId()).thenReturn("realm"); when(userService.findById("john-id", "realm")).thenReturn(domainUser); when(userFactory.getUser(domainUser, Locale.ENGLISH)).thenReturn(responseUser); - ResponseEntity ok = controller.getLoggedUser(authentication, Locale.ENGLISH); + ResponseEntity ok = controller.getLoggedUser(authentication, Locale.ENGLISH); assertEquals(HttpStatus.OK, ok.getStatusCode()); assertSame(responseUser, ok.getBody()); @@ -176,7 +176,7 @@ void getLoggedUserHandlesSuccessMissingAndBadIds() { @Test void searchMapsRoleStringsToProcessResourceIds() { AbstractUser domainUser = domainUser("john"); - User responseUser = new User(domainUser); + UserDto responseUser = new UserDto(domainUser); Pageable pageable = PageRequest.of(0, 10); String roleId = new ProcessResourceId().toString(); String negativeRoleId = new ProcessResourceId().toString(); @@ -189,7 +189,7 @@ void searchMapsRoleStringsToProcessResourceIds() { .thenReturn(new PageImpl<>(List.of(domainUser), pageable, 1)); when(userFactory.getUser(domainUser, Locale.ENGLISH)).thenReturn(responseUser); - ResponseEntity> response = controller.search(query, pageable, authentication, Locale.ENGLISH); + ResponseEntity> response = controller.search(query, pageable, authentication, Locale.ENGLISH); assertEquals(HttpStatus.OK, response.getStatusCode()); assertSame(responseUser, response.getBody().getContent().getFirst()); @@ -198,7 +198,7 @@ void searchMapsRoleStringsToProcessResourceIds() { @Test void getUserAllowsAdminOrSelfAndRejectsForeignOrInvalidIds() { AbstractUser domainUser = domainUser("john"); - User responseUser = new User(domainUser); + UserDto responseUser = new UserDto(domainUser); when(userService.getLoggedUserFromContext()).thenReturn(loggedUser); when(loggedUser.isAdmin()).thenReturn(false); when(loggedUser.getId()).thenReturn(new ObjectId("64b000000000000000000001")); @@ -209,7 +209,7 @@ void getUserAllowsAdminOrSelfAndRejectsForeignOrInvalidIds() { when(loggedUser.isAdmin()).thenReturn(true); when(userService.findById("64b000000000000000000001", "realm")).thenReturn(domainUser); when(userFactory.getUser(domainUser, Locale.ENGLISH)).thenReturn(responseUser); - ResponseEntity admin = controller.getUser("realm", "64b000000000000000000001", Locale.ENGLISH); + ResponseEntity admin = controller.getUser("realm", "64b000000000000000000001", Locale.ENGLISH); assertEquals(HttpStatus.OK, admin.getStatusCode()); assertSame(responseUser, admin.getBody()); diff --git a/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/MenuImportExportServiceTest.java b/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/MenuImportExportServiceTest.java index fcb252c5a7e..8b623ed6da0 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/MenuImportExportServiceTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/MenuImportExportServiceTest.java @@ -8,7 +8,14 @@ import com.netgrif.application.engine.objects.petrinet.domain.dataset.*; import com.netgrif.application.engine.objects.workflow.domain.Case; import com.netgrif.application.engine.objects.workflow.domain.DataField; +import com.netgrif.application.engine.objects.workflow.domain.Task; +import com.netgrif.application.engine.objects.workflow.domain.menu.Menu; import com.netgrif.application.engine.objects.workflow.domain.menu.MenuAndFilters; +import com.netgrif.application.engine.objects.workflow.domain.menu.MenuEntry; +import com.netgrif.application.engine.workflow.service.interfaces.IDataService; +import com.netgrif.application.engine.workflow.service.interfaces.IFilterImportExportService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.test.util.ReflectionTestUtils; @@ -22,8 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; class MenuImportExportServiceTest { @@ -102,6 +109,107 @@ void createXmlWritesMenuFileThroughResolvedStorage() throws Exception { assertTrue(xml.contains("menusWithFilters")); } + @Test + void importMenuSkipsReplacingMenuWhenFilterMappingIsMissing() throws Exception { + IFilterImportExportService filterImportExportService = mock(IFilterImportExportService.class); + IWorkflowService workflowService = mock(IWorkflowService.class); + ITaskService taskService = mock(ITaskService.class); + IDataService dataService = mock(IDataService.class); + + MenuImportExportService service = spy(new MenuImportExportService()); + ReflectionTestUtils.setField(service, "filterImportExportService", filterImportExportService); + ReflectionTestUtils.setField(service, "workflowService", workflowService); + ReflectionTestUtils.setField(service, "taskService", taskService); + ReflectionTestUtils.setField(service, "dataService", dataService); + + MenuAndFilters menuAndFilters = new MenuAndFilters(); + Menu menu = new Menu(); + menu.setMenuIdentifier("nav-menu"); + MenuEntry entry1 = new MenuEntry(); + entry1.setEntryName("Entry 1"); + entry1.setFilterCaseId("filter-case-1"); + MenuEntry entry2 = new MenuEntry(); + entry2.setEntryName("Entry 2"); + entry2.setFilterCaseId("filter-case-2"); + menu.setMenuEntries(List.of(entry1, entry2)); + menuAndFilters.getMenuList().setMenus(List.of(menu)); + + FileFieldValue ffv = new FileFieldValue(); + doReturn(menuAndFilters).when(service).loadFromXML(ffv); + + when(filterImportExportService.importFilters(any())).thenReturn(Map.of("filter-case-1", "task-1")); + + Case existingCase = mock(Case.class); + when(existingCase.getStringId()).thenReturn("existing-case-id"); + when(existingCase.getDataSet()).thenReturn(Map.of("menu_identifier", new DataField("nav-menu"))); + + Task groupNavTask = mock(Task.class); + when(taskService.searchOne(any())).thenReturn(groupNavTask); + Task importedFilterTask = mock(Task.class); + when(importedFilterTask.getCaseId()).thenReturn("filter-case-id"); + when(taskService.findOne("task-1")).thenReturn(importedFilterTask); + Case filterCase = mock(Case.class); + when(workflowService.findOne("filter-case-id")).thenReturn(filterCase); + + List result = service.importMenu(List.of(existingCase), ffv, "parent-group-id"); + + assertTrue(result.isEmpty()); + verify(workflowService, never()).findOne("existing-case-id"); + verify(service, never()).createMenuItemCase(any(), any(), any(), any(), any()); + } + + @Test + void importMenuReplacesMenuWhenAllFilterMappingsArePresent() throws Exception { + IFilterImportExportService filterImportExportService = mock(IFilterImportExportService.class); + IWorkflowService workflowService = mock(IWorkflowService.class); + ITaskService taskService = mock(ITaskService.class); + IDataService dataService = mock(IDataService.class); + + MenuImportExportService service = spy(new MenuImportExportService()); + ReflectionTestUtils.setField(service, "filterImportExportService", filterImportExportService); + ReflectionTestUtils.setField(service, "workflowService", workflowService); + ReflectionTestUtils.setField(service, "taskService", taskService); + ReflectionTestUtils.setField(service, "dataService", dataService); + + MenuAndFilters menuAndFilters = new MenuAndFilters(); + Menu menu = new Menu(); + menu.setMenuIdentifier("nav-menu"); + MenuEntry entry1 = new MenuEntry(); + entry1.setEntryName("Entry 1"); + entry1.setFilterCaseId("filter-case-1"); + menu.setMenuEntries(List.of(entry1)); + menuAndFilters.getMenuList().setMenus(List.of(menu)); + + FileFieldValue ffv = new FileFieldValue(); + doReturn(menuAndFilters).when(service).loadFromXML(ffv); + + when(filterImportExportService.importFilters(any())).thenReturn(Map.of("filter-case-1", "task-1")); + + Case existingCase = mock(Case.class); + when(existingCase.getStringId()).thenReturn("existing-case-id"); + when(existingCase.getDataSet()).thenReturn(Map.of("menu_identifier", new DataField("nav-menu"))); + when(existingCase.getFieldValue("remove_option")).thenReturn(0); + when(workflowService.findOne("existing-case-id")).thenReturn(existingCase); + + Task groupNavTask = mock(Task.class); + Task removeViewTask = mock(Task.class); + when(taskService.searchOne(any())).thenReturn(removeViewTask).thenReturn(groupNavTask); + + Task importedFilterTask = mock(Task.class); + when(importedFilterTask.getCaseId()).thenReturn("filter-case-id"); + when(taskService.findOne("task-1")).thenReturn(importedFilterTask); + Case filterCase = mock(Case.class); + when(workflowService.findOne("filter-case-id")).thenReturn(filterCase); + + doReturn("new-case-id,filter-case-id,true").when(service).createMenuItemCase(any(), eq(entry1), eq("nav-menu"), eq("parent-group-id"), eq("task-1")); + + List result = service.importMenu(List.of(existingCase), ffv, "parent-group-id"); + + assertEquals(List.of("new-case-id,filter-case-id,true"), result); + verify(workflowService).findOne("existing-case-id"); + verify(service).createMenuItemCase(any(), eq(entry1), eq("nav-menu"), eq("parent-group-id"), eq("task-1")); + } + private Case menuCase(String id, String defaultName) { Case useCase = mock(Case.class); when(useCase.getStringId()).thenReturn(id); diff --git a/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java b/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java index 6635e9cc7b7..92b533f242a 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/workflow/service/TaskServiceTest.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.workflow.service; import com.netgrif.application.engine.MockService; +import com.netgrif.application.engine.TestHelper; import com.netgrif.application.engine.auth.service.AuthorityService; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.importer.service.throwable.MissingIconKeyException; @@ -88,6 +89,7 @@ public void setUp() throws Exception { taskRepository.deleteAll(); realmRunner.run(null); userRunner.run(null); + superCreator.run(null); petriNetService.importPetriNet(ImportPetriNetParams.with() .xmlFile(new FileInputStream("src/test/resources/prikladFM.xml")) @@ -122,6 +124,7 @@ public void resetArcTest() throws TransitionNotExecutableException, MissingPetri user.setPassword("password"); user.setLastName("surname"); user.setEmail("email@email.com"); + user.setUsername("email@email.com"); user.setState(UserState.ACTIVE); user = (User) userService.saveUser(user, null); diff --git a/application-engine/src/test/java/com/netgrif/application/engine/workflow/web/VariableArcsTest.java b/application-engine/src/test/java/com/netgrif/application/engine/workflow/web/VariableArcsTest.java index 750af39c7c3..d754f2cd295 100644 --- a/application-engine/src/test/java/com/netgrif/application/engine/workflow/web/VariableArcsTest.java +++ b/application-engine/src/test/java/com/netgrif/application/engine/workflow/web/VariableArcsTest.java @@ -133,6 +133,7 @@ public void before() throws Exception { user.setCredential("password", passwordCredential); user.setState(UserState.ACTIVE); user.setEmail("VariableArcsTest@test.com"); + user.setUsername("VariableArcsTest@test.com"); testUser = importHelper.createUser(user, new Authority[]{authorityService.getOrCreate(Authority.user)}, new ProcessRole[]{}); diff --git a/application-engine/src/test/resources/simple_role.xml b/application-engine/src/test/resources/simple_role.xml new file mode 100644 index 00000000000..eb843ee34f0 --- /dev/null +++ b/application-engine/src/test/resources/simple_role.xml @@ -0,0 +1,14 @@ + + simple_role + 1.0.0 + SMR + Simple Role + device_hub + true + true + false + + simple_role + Simple role + + \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java index 543ccebec1c..fc53777641f 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java @@ -388,4 +388,32 @@ public void removeGroupId(String groupId) { public boolean isAdmin() { return this.authoritySet.stream().anyMatch(it -> it.getName().equals(Authority.admin)); } + + public Set getGroupIds() { + if (groupIds == null) { + groupIds = new HashSet<>(); + } + return groupIds; + } + + public Map> getAttributes() { + if (attributes == null) { + attributes = new HashMap<>(); + } + return attributes; + } + + public Set getAuthorityIds() { + if (authorityIds == null) { + authorityIds = new HashSet<>(); + } + return authorityIds; + } + + public Set getProcessRoleIds() { + if (processRoleIds == null) { + processRoleIds = new HashSet<>(); + } + return processRoleIds; + } } \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java index 374d6473b55..4b5ca073265 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.objects.auth.domain; import java.time.Duration; +import java.util.Set; /** * Transformer class responsible for converting between different user/actor representations @@ -27,6 +28,14 @@ public interface LoggedUserFactory { * @return newly created LoggedUser instance */ LoggedUser create(); + + default void resolveProcessRoles(AbstractActor user) {}; + + default void resolveProcessRolesRecursively(AbstractActor actor, Set processRoleIds, Set visitedGroupIds) {}; + + default void resolveAuthorities(AbstractActor user) {}; + + default void resolveAuthoritiesRecursively(AbstractActor actor, Set authorityIds, Set visitedGroupIds) {}; } /** @@ -62,6 +71,8 @@ public static LoggedUser toLoggedUser(AbstractUser user) { loggedUser.setProcessRoles(user.getProcessRoles()); loggedUser.setAttributes(user.getAttributes()); loggedUser.setGroupIds(user.getGroupIds()); + factory.resolveProcessRoles(loggedUser); + factory.resolveAuthorities(loggedUser); return loggedUser; } diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java index 7c7ba44f1ed..707287100a9 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java @@ -1,5 +1,7 @@ package com.netgrif.application.engine.objects.auth.domain; +import com.netgrif.application.engine.objects.annotations.EnsureCollection; +import com.netgrif.application.engine.objects.annotations.Indexed; import com.querydsl.core.annotations.QueryEntity; import lombok.Getter; import lombok.Setter; @@ -16,30 +18,35 @@ */ @Getter @QueryEntity +@EnsureCollection public abstract class Group extends AbstractActor implements Serializable { /** * The unique identifier of the group. */ @Setter + @Indexed private String identifier; /** * The display name of the group shown in the user interface. */ @Setter + @Indexed private String displayName; /** * The unique identifier of the group owner. */ @Setter + @Indexed private String ownerId; /** * The username of the group owner. */ @Setter + @Indexed private String ownerUsername; /** @@ -169,4 +176,18 @@ public void removeSubgroupId(String groupId) { public void setSubgroupIds(Set subgroupIds) { this.subgroupIds = subgroupIds == null ? new HashSet<>() : new HashSet<>(subgroupIds); } + + public Set getMemberIds() { + if (memberIds == null) { + memberIds = new HashSet<>(); + } + return memberIds; + } + + public Set getSubgroupIds() { + if (subgroupIds == null) { + subgroupIds = new HashSet<>(); + } + return subgroupIds; + } } \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/GroupSearchDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/GroupSearchDto.java deleted file mode 100644 index a8560fabf19..00000000000 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/GroupSearchDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.netgrif.application.engine.objects.auth.dto; - -import lombok.Data; - -@Data -public class GroupSearchDto { - private String fullText; - private String realmId; -} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/CreateGroupRequestDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/CreateGroupRequestDto.java new file mode 100644 index 00000000000..de899412cd5 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/CreateGroupRequestDto.java @@ -0,0 +1,14 @@ +package com.netgrif.application.engine.objects.dto.request.group; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +import java.io.Serializable; + +public record CreateGroupRequestDto( + @NotBlank(message = "Title is mandatory") String displayName, + @NotBlank(message = "Realm ID is mandatory") String realmId, + @NotBlank(message = "Identifier is mandatory") String identifier, + @Pattern(regexp = "^[a-fA-F0-9]{24}$") String ownerId) implements Serializable { + +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/GroupSearchRequestDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/GroupSearchRequestDto.java new file mode 100644 index 00000000000..aedb8dd2335 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/GroupSearchRequestDto.java @@ -0,0 +1,21 @@ +package com.netgrif.application.engine.objects.dto.request.group; + +import com.netgrif.application.engine.objects.auth.domain.Group; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Set; + +/** + * DTO for {@link Group} search request + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class GroupSearchRequestDto { + private Set ids; + private String realmId; + private String fullText; +} \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/UpdateGroupRequestDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/UpdateGroupRequestDto.java new file mode 100644 index 00000000000..f599304ebda --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/UpdateGroupRequestDto.java @@ -0,0 +1,9 @@ +package com.netgrif.application.engine.objects.dto.request.group; + +import jakarta.validation.constraints.NotBlank; + +public record UpdateGroupRequestDto( + @NotBlank(message = "Group ID cannot be null") String id, + String identifier, + String displayName) { +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/authority/AuthorityDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/authority/AuthorityDto.java new file mode 100644 index 00000000000..c958583db00 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/authority/AuthorityDto.java @@ -0,0 +1,17 @@ +package com.netgrif.application.engine.objects.dto.response.authority; + +import com.netgrif.application.engine.objects.auth.domain.Authority; +import org.bson.types.ObjectId; + +import java.io.Serializable; + +/** + * DTO for {@link Authority} + */ +public record AuthorityDto(String id, String name, String stringId) implements Serializable { + + public static AuthorityDto fromAuthority(Authority authority) { + return new AuthorityDto(authority.getStringId(), authority.getName(), authority.getStringId()); + } + +} \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java new file mode 100644 index 00000000000..1c03f028141 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java @@ -0,0 +1,31 @@ +package com.netgrif.application.engine.objects.dto.response.group; + +import com.netgrif.application.engine.objects.auth.domain.Group; +import com.netgrif.application.engine.objects.dto.response.authority.AuthorityDto; +import com.netgrif.application.engine.objects.dto.response.petrinet.ProcessRoleDto; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +public record GroupDto(String id, + String displayName, + String identifier, + String ownerUsername, + Set authoritySet, + Set processRoles, + Set groupIds, + Set subGroupIds) implements Serializable { + + public static GroupDto fromGroup(Group group, Locale locale) { + return new GroupDto(group.getStringId(), group.getDisplayName(), group.getIdentifier(), group.getOwnerUsername(), + group.getAuthoritySet().stream().map(AuthorityDto::fromAuthority).collect(Collectors.toSet()), + group.getProcessRoles().stream().map(processRole -> new ProcessRoleDto(processRole, locale)).collect(Collectors.toSet()), + group.getGroupIds() == null ? new HashSet<>() : new HashSet<>(group.getGroupIds()), + group.getSubgroupIds() == null ? new HashSet<>() : new HashSet<>(group.getSubgroupIds()) + ); + } + +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/petrinet/ProcessRoleDto.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/petrinet/ProcessRoleDto.java new file mode 100644 index 00000000000..5c820c76913 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/petrinet/ProcessRoleDto.java @@ -0,0 +1,28 @@ +package com.netgrif.application.engine.objects.dto.response.petrinet; + +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; + +import java.io.Serializable; +import java.util.Locale; + +public record ProcessRoleDto(String stringId, String name, String description, String importId, String netImportId, + String netVersion, String netStringId, boolean global) implements Serializable { + + /** + * This constructor doesn't set attributes regarding the Petri net. + *

+ * Use the ProcessRoleFactory to create instances that have these attributes set. + */ + public ProcessRoleDto(ProcessRole role, Locale locale) { + this(role.getStringId(), role.getLocalisedName(locale), role.getDescription(), role.getImportId(), null, null, null, role.isGlobal()); + } + + public ProcessRoleDto(ProcessRole role, Locale locale, String netImportId, + String netVersion, String netStringId) { + this(role.getStringId(), role.getLocalisedName(locale), role.getDescription(), role.getImportId(),netImportId, netVersion, netStringId, role.isGlobal()); + } + + public ProcessRoleDto(String id, String name, String description, boolean global) { + this(id, name, description, null, null, null, null, global); + } +} diff --git a/nae-object-library/src/test/java/com/netgrif/application/engine/objects/auth/domain/AbstractActorTest.java b/nae-object-library/src/test/java/com/netgrif/application/engine/objects/auth/domain/AbstractActorTest.java new file mode 100644 index 00000000000..c8b82e91b1b --- /dev/null +++ b/nae-object-library/src/test/java/com/netgrif/application/engine/objects/auth/domain/AbstractActorTest.java @@ -0,0 +1,423 @@ +package com.netgrif.application.engine.objects.auth.domain; + +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class AbstractActorTest { + + static class TestActor extends AbstractActor { + public TestActor() { + super(); + } + + public TestActor(ObjectId id, String realmId) { + super(id, realmId); + } + + public TestActor(String stringId, String realmId) { + super(stringId, realmId); + } + + public TestActor(ObjectId id, String realmId, Map> attributes, + Set authorityIds, Set authoritySet, + Set processRoleIds, Set processRoles, + Set groupIds) { + super(id, realmId, attributes, authorityIds, authoritySet, processRoleIds, processRoles, groupIds); + } + + @Override + String getName() { + return "test-actor"; + } + + @Override + String getFullName() { + return "Test Actor FullName"; + } + } + + static class TestAuthority extends Authority { + private final ObjectId id; + + public TestAuthority(String name) { + super(name); + this.id = new ObjectId(); + } + + public TestAuthority(String id, String name) { + super(name); + this.id = new ObjectId(id); + } + + @Override + public ObjectId get_id() { + return id; + } + + @Override + public String getStringId() { + return id.toString(); + } + } + + static class TestProcessRole extends ProcessRole { + public TestProcessRole(String id) { + super(id); + } + } + + @Test + void testConstructorsAndIdManagement() { + TestActor actorDefault = new TestActor(); + assertNull(actorDefault.getId()); + assertNull(actorDefault.getRealmId()); + assertEquals("test-actor", actorDefault.getName()); + assertEquals("Test Actor FullName", actorDefault.getFullName()); + + ObjectId objectId = new ObjectId(); + TestActor actorWithObjId = new TestActor(objectId, "realm-1"); + assertEquals(objectId, actorWithObjId.getId()); + assertEquals(objectId.toString(), actorWithObjId.getStringId()); + assertEquals("realm-1", actorWithObjId.getRealmId()); + + String stringId = new ObjectId().toString(); + TestActor actorWithStringId = new TestActor(stringId, "realm-2"); + assertEquals(stringId, actorWithStringId.getId().toString()); + assertEquals("realm-2", actorWithStringId.getRealmId()); + + ObjectId newId = new ObjectId(); + actorDefault.setId(newId); + assertEquals(newId, actorDefault.getId()); + + String newIdStr = new ObjectId().toString(); + actorDefault.setId(newIdStr); + assertEquals(newIdStr, actorDefault.getId().toString()); + + actorDefault.setRealmId("realm-updated"); + assertEquals("realm-updated", actorDefault.getRealmId()); + } + + @Test + void testAllArgsConstructor() { + ObjectId id = new ObjectId(); + Map> attrs = new HashMap<>(); + attrs.put("key1", new Attribute<>("val1", false)); + Set authIds = Set.of(new ObjectId().toString()); + Set authSet = Set.of(new TestAuthority("USER")); + String roleIdStr = new com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId().getFullId(); + Set roleIds = Set.of(roleIdStr); + Set roles = Set.of(new TestProcessRole(roleIdStr)); + Set groupIds = Set.of(new ObjectId().toString()); + + TestActor actor = new TestActor(id, "realm-3", attrs, authIds, authSet, roleIds, roles, groupIds); + assertEquals(id, actor.getId()); + assertEquals("realm-3", actor.getRealmId()); + assertEquals(attrs, actor.getAttributes()); + assertEquals(authIds, actor.getAuthorityIds()); + assertEquals(authSet, actor.getAuthoritySet()); + assertEquals(roleIds, actor.getProcessRoleIds()); + assertEquals(roles, actor.getProcessRoles()); + assertEquals(groupIds, actor.getGroupIds()); + } + + @Test + void testAttributesOperations() { + TestActor actor = new TestActor(); + assertNotNull(actor.getAttributes()); + assertTrue(actor.getAttributeKeys().isEmpty()); + assertNull(actor.getAttributeValue("missing")); + assertNull(actor.getAttribute("missing")); + assertFalse(actor.isAttributeSet("missing")); + + // Setting attributes map + actor.setAttributes(null); + assertNotNull(actor.getAttributes()); + assertTrue(actor.getAttributes().isEmpty()); + + Map> map = new HashMap<>(); + map.put("attr1", new Attribute<>("value1", true)); + map.put("attr2", new Attribute<>(null, false)); + actor.setAttributes(map); + assertEquals(2, actor.getAttributeKeys().size()); + assertEquals("value1", actor.getAttributeValue("attr1")); + assertNull(actor.getAttributeValue("attr2")); + assertTrue(actor.isAttributeSet("attr1")); + assertFalse(actor.isAttributeSet("attr2")); + assertNotNull(actor.getAttribute("attr1")); + + // setAttribute single + actor.setAttribute("attr3", "value3", false); + assertEquals("value3", actor.getAttributeValue("attr3")); + assertTrue(actor.isAttributeSet("attr3")); + + // removeAttribute + actor.removeAttribute("attr1"); + assertNull(actor.getAttributeValue("attr1")); + assertFalse(actor.isAttributeSet("attr1")); + + // test when internal map is null + TestActor nullAttrActor = new TestActor(new ObjectId(), "realm", null, null, null, null, null, null); + assertNotNull(nullAttrActor.getAttributes()); // getAttributes lazily creates map + nullAttrActor.attributes = null; // force null for branch coverage + assertNull(nullAttrActor.getAttributeValue("key")); + assertNull(nullAttrActor.getAttribute("key")); + assertFalse(nullAttrActor.isAttributeSet("key")); + assertTrue(nullAttrActor.getAttributeKeys().isEmpty()); + nullAttrActor.removeAttribute("key"); + assertNotNull(nullAttrActor.attributes); + + nullAttrActor.attributes = null; + nullAttrActor.setAttribute("newKey", "newVal", true); + assertEquals("newVal", nullAttrActor.getAttributeValue("newKey")); + } + + @Test + void testValidateRequiredAttributes() { + TestActor actor = new TestActor(); + assertTrue(actor.validateRequiredAttributes()); + + actor.setAttribute("opt", null, false); + assertTrue(actor.validateRequiredAttributes()); + + Attribute reqAttr = new Attribute<>(); + reqAttr.setRequired(true); + actor.getAttributes().put("req", reqAttr); + assertFalse(actor.validateRequiredAttributes()); + + actor.setAttribute("req", "validValue", true); + assertTrue(actor.validateRequiredAttributes()); + } + + @Test + void testAuthorityOperations() { + TestActor actor = new TestActor(); + assertNotNull(actor.getAuthorityIds()); + assertNotNull(actor.getAuthoritySet()); + assertFalse(actor.isAdmin()); + + // setAuthorityIds + actor.setAuthorityIds(null); + assertTrue(actor.getAuthorityIds().isEmpty()); + assertTrue(actor.getAuthoritySet().isEmpty()); + + actor.setAuthorityIds(Set.of("auth-id-1")); + assertEquals(Set.of("auth-id-1"), actor.getAuthorityIds()); + assertTrue(actor.getAuthoritySet().isEmpty()); + + // setAuthoritySet + TestAuthority userAuth = new TestAuthority("USER"); + TestAuthority adminAuth = new TestAuthority(Authority.admin); + actor.setAuthoritySet(Set.of(userAuth, adminAuth)); + assertEquals(2, actor.getAuthoritySet().size()); + assertEquals(2, actor.getAuthorityIds().size()); + assertTrue(actor.getAuthorityIds().contains(userAuth.getStringId())); + assertTrue(actor.getAuthorityIds().contains(adminAuth.getStringId())); + assertTrue(actor.isAdmin()); + + actor.setAuthoritySet(null); + assertTrue(actor.getAuthoritySet().isEmpty()); + assertTrue(actor.getAuthorityIds().isEmpty()); + assertFalse(actor.isAdmin()); + + // addAuthority + actor.addAuthority(null); + assertTrue(actor.getAuthoritySet().isEmpty()); + + actor.addAuthority(userAuth); + assertTrue(actor.getAuthoritySet().contains(userAuth)); + assertTrue(actor.getAuthorityIds().contains(userAuth.getStringId())); + + // removeAuthority with exact match + actor.removeAuthority(null); + actor.removeAuthority(userAuth); + assertFalse(actor.getAuthoritySet().contains(userAuth)); + assertFalse(actor.getAuthorityIds().contains(userAuth.getStringId())); + + // removeAuthority with unequal instance but same name + TestAuthority authA = new TestAuthority("MANAGER"); + TestAuthority authB = new TestAuthority("MANAGER") { + @Override + public boolean equals(Object o) { + return false; // force equals to return false + } + }; + actor.addAuthority(authA); + actor.removeAuthority(authB); + assertFalse(actor.getAuthoritySet().contains(authA)); + + // removeAuthorityByName + actor.addAuthority(userAuth); + actor.addAuthority(adminAuth); + actor.removeAuthorityByName("NON_EXISTING"); + assertEquals(2, actor.getAuthoritySet().size()); + actor.removeAuthorityByName(userAuth.getName()); + assertEquals(1, actor.getAuthoritySet().size()); + assertFalse(actor.getAuthorityIds().contains(userAuth.getStringId())); + + // null fields coverage + TestActor nullAuthActor = new TestActor(); + nullAuthActor.authorityIds = null; + nullAuthActor.authoritySet = null; + assertNotNull(nullAuthActor.getAuthorityIds()); + nullAuthActor.authorityIds = null; + nullAuthActor.authoritySet = null; + nullAuthActor.addAuthority(userAuth); + assertEquals(1, nullAuthActor.getAuthoritySet().size()); + + nullAuthActor.authorityIds = null; + nullAuthActor.authoritySet = null; + nullAuthActor.removeAuthority(userAuth); + assertNotNull(nullAuthActor.authorityIds); + assertNotNull(nullAuthActor.authoritySet); + + nullAuthActor.authoritySet = null; + nullAuthActor.removeAuthorityByName("USER"); + assertNotNull(nullAuthActor.authoritySet); + } + + @Test + void testProcessRoleOperations() { + TestActor actor = new TestActor(); + assertNotNull(actor.getProcessRoleIds()); + assertNotNull(actor.getProcessRoles()); + + String r1Id = new com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId().getFullId(); + String r2Id = new com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId().getFullId(); + + // setProcessRoleIds + actor.setProcessRoleIds(null); + assertTrue(actor.getProcessRoleIds().isEmpty()); + assertTrue(actor.getProcessRoles().isEmpty()); + + actor.setProcessRoleIds(Set.of(r1Id)); + assertEquals(Set.of(r1Id), actor.getProcessRoleIds()); + assertTrue(actor.getProcessRoles().isEmpty()); + + // setProcessRoles + TestProcessRole role1 = new TestProcessRole(r1Id); + TestProcessRole role2 = new TestProcessRole(r2Id); + actor.setProcessRoles(Set.of(role1, role2)); + assertEquals(2, actor.getProcessRoles().size()); + assertEquals(2, actor.getProcessRoleIds().size()); + assertTrue(actor.getProcessRoleIds().contains(r1Id)); + + actor.setProcessRoles(null); + assertTrue(actor.getProcessRoles().isEmpty()); + assertTrue(actor.getProcessRoleIds().isEmpty()); + + // addProcessRole + actor.addProcessRole(null); + assertTrue(actor.getProcessRoles().isEmpty()); + + actor.addProcessRole(role1); + assertTrue(actor.getProcessRoles().contains(role1)); + assertTrue(actor.getProcessRoleIds().contains(r1Id)); + + // addAllProcessRoles + actor.addAllProcessRoles(null); + actor.addAllProcessRoles(List.of()); + assertEquals(1, actor.getProcessRoles().size()); + + actor.addAllProcessRoles(List.of(role2)); + assertEquals(2, actor.getProcessRoles().size()); + + // removeProcessRole + actor.removeProcessRole(null); + actor.removeProcessRole(role1); + assertFalse(actor.getProcessRoles().contains(role1)); + assertFalse(actor.getProcessRoleIds().contains(r1Id)); + + // removeProcessRole fallback by stringId + TestProcessRole role2Copy = new TestProcessRole(r2Id) { + @Override + public boolean equals(Object o) { + return false; + } + }; + actor.removeProcessRole(role2Copy); + assertFalse(actor.getProcessRoleIds().contains(r2Id)); + + // removeProcessRoleById + actor.addProcessRole(role1); + actor.removeProcessRoleById(r1Id); + assertFalse(actor.getProcessRoleIds().contains(r1Id)); + + // clearProcessRoles + actor.addProcessRole(role1); + actor.addProcessRole(role2); + assertEquals(2, actor.getProcessRoles().size()); + actor.clearProcessRoles(); + assertTrue(actor.getProcessRoles().isEmpty()); + assertTrue(actor.getProcessRoleIds().isEmpty()); + + // null fields branches + TestActor nullRoleActor = new TestActor(); + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + assertNotNull(nullRoleActor.getProcessRoleIds()); + + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + nullRoleActor.addProcessRole(role1); + assertEquals(1, nullRoleActor.getProcessRoles().size()); + + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + nullRoleActor.addAllProcessRoles(List.of(role2)); + assertEquals(1, nullRoleActor.getProcessRoles().size()); + + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + nullRoleActor.removeProcessRole(role1); + assertNotNull(nullRoleActor.processRoleIds); + assertNotNull(nullRoleActor.processRoles); + + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + nullRoleActor.removeProcessRoleById(r1Id); + assertNotNull(nullRoleActor.processRoleIds); + assertNotNull(nullRoleActor.processRoles); + + nullRoleActor.processRoleIds = null; + nullRoleActor.processRoles = null; + nullRoleActor.clearProcessRoles(); + assertNotNull(nullRoleActor.processRoleIds); + assertNotNull(nullRoleActor.processRoles); + } + + @Test + void testGroupOperations() { + TestActor actor = new TestActor(); + assertNotNull(actor.getGroupIds()); + + actor.setGroupIds(null); + assertTrue(actor.getGroupIds().isEmpty()); + + actor.setGroupIds(Set.of("g1", "g2")); + assertEquals(Set.of("g1", "g2"), actor.getGroupIds()); + + actor.addGroupId("g3"); + assertTrue(actor.getGroupIds().contains("g3")); + + actor.removeGroupId("g1"); + assertFalse(actor.getGroupIds().contains("g1")); + + // null groupIds branches + TestActor nullGroupActor = new TestActor(); + nullGroupActor.groupIds = null; + assertNotNull(nullGroupActor.getGroupIds()); + + nullGroupActor.groupIds = null; + nullGroupActor.addGroupId("g1"); + assertEquals(Set.of("g1"), nullGroupActor.getGroupIds()); + + nullGroupActor.groupIds = null; + nullGroupActor.removeGroupId("g1"); + assertNotNull(nullGroupActor.groupIds); + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java deleted file mode 100644 index a27c227fd02..00000000000 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netgrif.application.engine.adapter.spring.auth.service; - -import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; -import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import org.springframework.stereotype.Component; - -@Component -public class DefaultLoggedUserFactory implements ActorTransformer.LoggedUserFactory { - - @Override - public LoggedUser create() { - return new LoggedUserImpl(); - } -} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java deleted file mode 100644 index 0e60422b87b..00000000000 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netgrif.application.engine.adapter.spring.configuration; - -import com.netgrif.application.engine.adapter.spring.auth.service.DefaultLoggedUserFactory; -import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; -import jakarta.annotation.PostConstruct; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class LoggedUserConfiguration { - - @PostConstruct - public void initializeLoggedUserFactory() { - ActorTransformer.setLoggedUserFactory(new DefaultLoggedUserFactory()); - } -} diff --git a/nae-user-ce/README.md b/nae-user-ce/README.md index 014e7c1948f..efade1c4978 100644 --- a/nae-user-ce/README.md +++ b/nae-user-ce/README.md @@ -49,7 +49,7 @@ of the same interface type — the CE default will not be loaded. | Class | Purpose | |--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `CollectionNameProvider` | Resolves the MongoDB collection name for a given realm ID (`users_`); supports default realm, admin realm, and multi-realm lookups | -| `UserMongoEventListener` | Spring Data `AfterConvertCallback` that eagerly resolves `processRoleIds` and `authorityIds` into their full domain objects after every MongoDB read | +| `ActorMongoEventListener` | Spring Data `AfterConvertCallback` that eagerly resolves `processRoleIds` and `authorityIds` into their full domain objects after every MongoDB read | ### Key design decisions diff --git a/nae-user-ce/pom.xml b/nae-user-ce/pom.xml index 91fc13e816c..735086216b4 100644 --- a/nae-user-ce/pom.xml +++ b/nae-user-ce/pom.xml @@ -142,6 +142,16 @@ 1.3.2 provided + + org.junit.jupiter + junit-jupiter + test + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java new file mode 100644 index 00000000000..41e24bb144b --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java @@ -0,0 +1,24 @@ +package com.netgrif.application.engine.auth.config; + +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.auth.service.AuthorityService; +import com.netgrif.application.engine.auth.service.DefaultLoggedUserFactory; +import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; + +@Configuration +@RequiredArgsConstructor +public class LoggedUserConfiguration { + + private final GroupService groupService; + private final ProcessRoleService processRoleService; + private final AuthorityService authorityService; + + @PostConstruct + public void initializeLoggedUserFactory() { + ActorTransformer.setLoggedUserFactory(new DefaultLoggedUserFactory(groupService, processRoleService, authorityService)); + } +} diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserMongoEventListener.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/ActorMongoEventListener.java similarity index 86% rename from nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserMongoEventListener.java rename to nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/ActorMongoEventListener.java index 38f661df3d8..b9422f8b3cd 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserMongoEventListener.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/ActorMongoEventListener.java @@ -13,14 +13,14 @@ @Slf4j @Component -public class UserMongoEventListener implements AfterConvertCallback { +public class ActorMongoEventListener implements AfterConvertCallback { private final ProcessRoleService processRoleService; private final AuthorityService authorityService; - public UserMongoEventListener(@Lazy ProcessRoleService processRoleService, - @Lazy AuthorityService authorityService) { + public ActorMongoEventListener(@Lazy ProcessRoleService processRoleService, + @Lazy AuthorityService authorityService) { this.processRoleService = processRoleService; this.authorityService = authorityService; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java similarity index 81% rename from application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java rename to nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java index 260f705d5e4..8c44f423047 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java @@ -1,12 +1,11 @@ package com.netgrif.application.engine.auth.service; -import com.netgrif.application.engine.auth.service.interfaces.IAuthorizationService; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service -public class AuthorizationService implements IAuthorizationService { +public class AuthorizationServiceImpl implements AuthorizationService { @Autowired private UserService userService; diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java new file mode 100644 index 00000000000..5eec2737876 --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java @@ -0,0 +1,81 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.objects.auth.domain.*; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Set; + +@Component +@RequiredArgsConstructor +public class DefaultLoggedUserFactory implements ActorTransformer.LoggedUserFactory { + + private final GroupService groupService; + + private final ProcessRoleService processRoleService; + + private final AuthorityService authorityService; + + @Override + public LoggedUser create() { + return new LoggedUserImpl(); + } + + @Override + public void resolveProcessRoles(AbstractActor user) { + Set processRoleIds = user.getProcessRoleIds(); + user.getGroupIds().forEach(groupId -> { + resolveProcessRolesRecursively(groupService.findById(groupId), processRoleIds, new HashSet<>()); + }); + + user.getProcessRoleIds().forEach(processRoleId -> { + ProcessRole role = processRoleService.findById(processRoleId); + if (role != null) { + user.getProcessRoles().add(role); + } + }); + } + + @Override + public void resolveProcessRolesRecursively(AbstractActor actor, Set processRoleIds, Set visitedGroupIds) { + processRoleIds.addAll(actor.getProcessRoleIds()); + if (!actor.getGroupIds().isEmpty()) { + actor.getGroupIds().stream().filter(groupId -> !visitedGroupIds.contains(groupId)).forEach(groupId -> { + Group group = groupService.findById(groupId); + processRoleIds.addAll(group.getProcessRoleIds()); + visitedGroupIds.add(groupId); + resolveProcessRolesRecursively(group, processRoleIds, visitedGroupIds); + }); + } + } + + @Override + public void resolveAuthorities(AbstractActor user) { + Set authorityIds = user.getAuthorityIds(); + user.getGroupIds().forEach(groupId -> { + resolveAuthoritiesRecursively(groupService.findById(groupId), authorityIds, new HashSet<>()); + }); + + user.getAuthorityIds().forEach(authorityId -> { + Authority authority = authorityService.getOne(authorityId); + user.getAuthoritySet().add(authority); + }); + } + + @Override + public void resolveAuthoritiesRecursively(AbstractActor actor, Set authorityIds, Set visitedGroupIds) { + authorityIds.addAll(actor.getAuthorityIds()); + if (!actor.getGroupIds().isEmpty()) { + actor.getGroupIds().stream().filter(groupId -> !visitedGroupIds.contains(groupId)).forEach(groupId -> { + Group group = groupService.findById(groupId); + authorityIds.addAll(group.getAuthorityIds()); + visitedGroupIds.add(groupId); + resolveAuthoritiesRecursively(group, authorityIds, visitedGroupIds); + }); + } + } +} diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java index 513e96090a4..e53b0200db3 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java @@ -1,14 +1,19 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.provider.CollectionNameProvider; import com.netgrif.application.engine.auth.repository.GroupRepository; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.domain.Group; -import com.netgrif.application.engine.objects.auth.dto.GroupSearchDto; import com.netgrif.application.engine.objects.common.ResourceNotFoundException; import com.netgrif.application.engine.objects.common.ResourceNotFoundExceptionCode; +import com.netgrif.application.engine.objects.dto.request.group.GroupSearchRequestDto; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.PageImpl; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; @@ -21,6 +26,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.util.Pair; +import org.springframework.util.Assert; import java.time.LocalDateTime; import java.util.*; @@ -46,6 +52,8 @@ public class GroupServiceImpl implements GroupService { private MongoTemplate mongoTemplate; + private ProcessRoleService processRoleService; + @Autowired public void setCollectionNameProvider(CollectionNameProvider collectionNameProvider) { this.collectionNameProvider = collectionNameProvider; @@ -81,13 +89,19 @@ public void setPaginationProperties(PaginationProperties paginationProperties) { this.paginationProperties = paginationProperties; } + @Lazy + @Autowired + public void setProcessRoleService(ProcessRoleService processRoleService) { + this.processRoleService = processRoleService; + } + @Override public void delete(Group group) { if (!groupRepository.existsById(group.getStringId())) { log.error("Group [{}] does not exist", group.getStringId()); throw new IllegalArgumentException("Group " + group.getStringId() + " does not exist"); } - if (group.getMemberIds() != null) { + if (group.getMemberIds() != null && !group.getMemberIds().isEmpty()) { Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); Page members; do { @@ -100,7 +114,7 @@ public void delete(Group group) { pageable = pageable.next(); } while (members.hasNext()); } - if (group.getSubgroupIds() != null) { + if (group.getSubgroupIds() != null && !group.getSubgroupIds().isEmpty()) { log.debug("Removing group [{}] from child groups [{}]", group.getStringId(), group.getSubgroupIds()); List subGroups = findAllByIds(group.getSubgroupIds(), Pageable.unpaged()).stream().toList(); subGroups.forEach(subgroup -> { @@ -108,7 +122,7 @@ public void delete(Group group) { save(subgroup); }); } - if (group.getGroupIds() != null) { + if (group.getGroupIds() != null && !group.getGroupIds().isEmpty()) { log.debug("Removing group [{}] from parent groups [{}]", group.getStringId(), group.getGroupIds()); List groups = findAllByIds(group.getGroupIds(), Pageable.unpaged()).stream().toList(); groups.forEach(grup -> { @@ -122,7 +136,11 @@ public void delete(Group group) { @Override public Group save(Group group) { - log.debug("Saving group: [{}]", group.getStringId()); + if (groupRepository.existsById(group.getStringId())) { + log.debug("Updating group: [{}]", group.getIdentifier()); + } else { + log.debug("Saving new group: [{}]", group.getIdentifier()); + } group.setModifiedAt(LocalDateTime.now()); return groupRepository.save(group); } @@ -173,8 +191,10 @@ public Optional findByIdentifier(String identifier) { @Override public Group getDefaultSystemGroup() { - if (defaultSystemGroup == null) { + if (!groupRepository.existsByIdentifier(groupConfigurationProperties.getDefaultGroupIdentifier())) { defaultSystemGroup = create(groupConfigurationProperties.getDefaultGroupIdentifier(), groupConfigurationProperties.getDefaultGroupTitle(), userService.getSystem()); + } else if (defaultSystemGroup == null) { + defaultSystemGroup = findByIdentifier(groupConfigurationProperties.getDefaultGroupIdentifier()).orElseThrow(() -> new IllegalStateException("Default system group does not exist")); } return defaultSystemGroup; } @@ -191,6 +211,12 @@ public Group create(AbstractUser groupOwner) { @Override public Group create(String identifier, String title, AbstractUser groupOwner) { + if (identifier == null || identifier.isBlank()) { + throw new IllegalArgumentException("Group identifier cannot be null or blank."); + } + if (groupRepository.existsByIdentifier(identifier)) { + throw new IllegalArgumentException("Group with identifier [%s] already exists.".formatted(identifier)); + } log.info("Creating default group for user: [{}]", groupOwner.getStringId()); Group group = new com.netgrif.application.engine.adapter.spring.auth.domain.Group(identifier, groupOwner.getRealmId()); group.setOwnerId(groupOwner.getStringId()); @@ -224,28 +250,48 @@ public Group getDefaultUserGroup(AbstractUser user) { @Override public void addUserToDefaultSystemGroup(AbstractUser user) { log.info("Adding user [{}] to default group", user.getStringId()); - addUser(user, getDefaultSystemGroup()); + addUser(getDefaultSystemGroup(), user); + } + + @Override + public Group assignUsersToGroup(String groupId, Set userIds) { + userIds = userIds == null ? new HashSet<>() : userIds; + Group group = this.findById(groupId); + Set currentGroupMemberIds = group.getMemberIds(); + + Set removableMemberIds = new HashSet<>(currentGroupMemberIds); + removableMemberIds.removeAll(userIds); + + Set newMemberIds = new HashSet<>(userIds); + newMemberIds.removeAll(currentGroupMemberIds); + + removableMemberIds.forEach(toBeRemovedId -> removeUser(group, userService.findById(toBeRemovedId, group.getRealmId()))); + newMemberIds.forEach(toBeAddedId -> addUser(group, userService.findById(toBeAddedId, group.getRealmId()))); + return group; } @Override - public Group addUser(String userId, String groupId, String realmId) { - return addUser(userService.findById(userId, realmId), groupId); + public Group addUser(String groupId, String userId, String realmId) { + return addUser(groupId, userService.findById(userId, realmId)); } @Override - public Group addUser(String userId, Group group, String realmId) { + public Group addUser(Group group, String userId, String realmId) { AbstractUser user = userService.findById(userId, realmId); - return addUser(user, group); + return addUser(group, user); } @Override - public Group addUser(AbstractUser user, String groupIdentifier) { - Group group = findByIdentifier(groupIdentifier).orElseThrow(() -> new IllegalArgumentException("Group with identifier [%s] not found. ".formatted(groupIdentifier))); - return addUser(user, group); + public Group addUser(String groupId, AbstractUser user) { + Group group = findById(groupId); + return addUser(group, user); } @Override - public Group addUser(AbstractUser user, Group group) { + public Group addUser(Group group, AbstractUser user) { + Assert.notNull(user, "User cannot be null"); + Assert.notNull(group, "Group cannot be null"); + log.info("Adding user [{}] to group [{}]", user.getStringId(), group.getStringId()); user.addGroupId(group.getStringId()); group.addMemberId(user.getStringId()); @@ -254,13 +300,21 @@ public Group addUser(AbstractUser user, Group group) { } @Override - public Group removeUser(AbstractUser user, String groupIdentifier) { - Group group = findByIdentifier(groupIdentifier).orElseThrow(() -> new IllegalArgumentException("Group with identifier [%s] not found. ".formatted(groupIdentifier))); - return removeUser(user, group); + public Group removeUser(String groupId, String userId, String realmId) { + return removeUser(groupId, userService.findById(userId, realmId)); + } + + @Override + public Group removeUser(String groupId, AbstractUser user) { + Group group = findById(groupId); + return removeUser(group, user); } @Override - public Group removeUser(AbstractUser user, Group group) { + public Group removeUser(Group group, AbstractUser user) { + Assert.notNull(user, "User cannot be null"); + Assert.notNull(group, "Group cannot be null"); + log.info("Removing user [{}] from group [{}]", user.getStringId(), group.getStringId()); user.removeGroupId(group.getStringId()); group.removeMemberId(user.getStringId()); @@ -299,19 +353,81 @@ public Page findAllFromRealmIn(Collection realmIds, Pageable page } @Override - public Group assignAuthority(String groupId, String authorityId) { + public Group assignAuthorities(String groupId, Set authorityIds) { + if (authorityIds == null) { + return findById(groupId); + } + Group group = this.findById(groupId); + Set currentAuthorityIds = group.getAuthorityIds(); + + Set removableAuthorityIds = new HashSet<>(currentAuthorityIds); + removableAuthorityIds.removeAll(authorityIds); + + Set newAuthorityIds = new HashSet<>(authorityIds); + newAuthorityIds.removeAll(currentAuthorityIds); + + removableAuthorityIds.forEach(toBeRemovedId -> removeAuthority(groupId, toBeRemovedId)); + newAuthorityIds.forEach(toBeAddedId -> addAuthority(groupId, toBeAddedId)); + return findById(groupId); + } + + @Override + public Group addAuthority(String groupId, String authorityId) { Group group = findById(groupId); - group.addAuthority(authorityService.getOne(authorityId)); + Authority authority = authorityService.getOne(authorityId); + return addAuthority(group, authority); + } + + @Override + public Group addAuthority(Group group, Authority authority) { + Assert.notNull(group, "Group cannot be null"); + Assert.notNull(authority, "Authority cannot be null"); + group.addAuthority(authority); + return save(group); + } + + @Override + public Group removeAuthority(String groupId, String authorityId) { + Group group = findById(groupId); + Authority authority = authorityService.getOne(authorityId); + return removeAuthority(group, authority); + } + + @Override + public Group removeAuthority(Group group, Authority authority) { + Assert.notNull(group, "Group cannot be null"); + Assert.notNull(authority, "Authority cannot be null"); + group.removeAuthority(authority); return save(group); } + @Override + public Group assignSubgroups(String parentGroupId, Set childGroupIds) { + if (childGroupIds == null) { + return findById(parentGroupId); + } + Group parentGroup = this.findById(parentGroupId); + Set currentSubgroupIds = parentGroup.getSubgroupIds(); + + Set removableGroupIds = new HashSet<>(currentSubgroupIds); + removableGroupIds.removeAll(childGroupIds); + + Set newSubgroupIds = new HashSet<>(childGroupIds); + newSubgroupIds.removeAll(currentSubgroupIds); + + removableGroupIds.forEach(toBeRemovedId -> removeSubgroup(parentGroupId, toBeRemovedId)); + newSubgroupIds.forEach(toBeAddedId -> addSubgroup(parentGroupId, toBeAddedId)); + return parentGroup; + } + @Override public Pair addSubgroup(String parentGroupId, String childGroupId) { if (parentGroupId.equals(childGroupId)) { throw new IllegalArgumentException("Trying to add group to itself [%s]!".formatted(parentGroupId)); } Group parentGroup = this.findById(parentGroupId); - return this.addSubgroup(parentGroup, childGroupId); + Group childGroup = this.findById(childGroupId); + return this.addSubgroup(parentGroup, childGroup); } @Override @@ -334,10 +450,18 @@ public Pair addSubgroup(String parentGroupId, Group childGroup) { @Override public Pair addSubgroup(Group parentGroup, Group childGroup) { + Assert.notNull(parentGroup, "Parent group cannot be null"); + Assert.notNull(childGroup, "Child group cannot be null"); + // TODO: maybe handle groups cycles here? if (parentGroup.getStringId().equals(childGroup.getStringId())) { throw new IllegalArgumentException("Trying to add group to itself [%s]!".formatted(parentGroup.getStringId())); } + + if (parentGroup.getRealmId() != null && !parentGroup.getRealmId().equals(childGroup.getRealmId())) { + throw new IllegalArgumentException("Trying to add group [%s] to parent group [%s] from different realm!".formatted(childGroup.getStringId(), parentGroup.getStringId())); + } + parentGroup.addSubGroupId(childGroup.getStringId()); childGroup.addGroupId(parentGroup.getStringId()); log.info("Adding group [{}] to parent group [{}]", childGroup.getStringId(), parentGroup.getStringId()); @@ -346,6 +470,50 @@ public Pair addSubgroup(Group parentGroup, Group childGroup) { return Pair.of(parentGroup, childGroup); } + @Override + public Pair removeSubgroup(String parentGroupId, String childGroupId) { + if (parentGroupId.equals(childGroupId)) { + throw new IllegalArgumentException("Trying to remove group from itself [%s]!".formatted(parentGroupId)); + } + Group parentGroup = this.findById(parentGroupId); + Group childGroup = this.findById(childGroupId); + return this.removeSubgroup(parentGroup, childGroup); + } + + @Override + public Pair removeSubgroup(Group parentGroup, String childGroupId) { + if (parentGroup.getStringId().equals(childGroupId)) { + throw new IllegalArgumentException("Trying to remove group from itself [%s]!".formatted(parentGroup.getStringId())); + } + Group childGroup = this.findById(childGroupId); + return this.removeSubgroup(parentGroup, childGroup); + } + + @Override + public Pair removeSubgroup(String parentGroupId, Group childGroup) { + if (childGroup.getStringId().equals(parentGroupId)) { + throw new IllegalArgumentException("Trying to remove group from itself [%s]!".formatted(childGroup.getStringId())); + } + Group parentGroup = this.findById(parentGroupId); + return this.removeSubgroup(parentGroup, childGroup); + } + + @Override + public Pair removeSubgroup(Group parentGroup, Group childGroup) { + Assert.notNull(parentGroup, "Parent group cannot be null"); + Assert.notNull(childGroup, "Child group cannot be null"); + + if (parentGroup.getStringId().equals(childGroup.getStringId())) { + throw new IllegalArgumentException("Trying to remove group from itself [%s]!".formatted(parentGroup.getStringId())); + } + parentGroup.removeSubgroupId(childGroup.getStringId()); + childGroup.removeGroupId(parentGroup.getStringId()); + log.info("Removing group [{}] from parent group [{}]", childGroup.getStringId(), parentGroup.getStringId()); + this.save(parentGroup); + this.save(childGroup); + return Pair.of(parentGroup, childGroup); + } + @Override public List getGroupParentGroupsById(String groupId) { Group group = this.findById(groupId); @@ -388,9 +556,13 @@ public String getGroupOwnerEmail(String groupId) { } @Override - public Page search(GroupSearchDto searchDto, Pageable pageable) { + public Page search(GroupSearchRequestDto searchDto, Pageable pageable) { List filters = new ArrayList<>(); - if (searchDto.getFullText() != null && !searchDto.getFullText().isBlank()) { + if (searchDto != null && searchDto.getIds() != null) { + Criteria criteria = Criteria.where("_id").in(searchDto.getIds()); + filters.add(criteria); + } + if (searchDto != null && searchDto.getFullText() != null && !searchDto.getFullText().isBlank()) { Criteria criteria = new Criteria().orOperator( Criteria.where("identifier").regex(searchDto.getFullText(), "i"), Criteria.where("displayName").regex(searchDto.getFullText(), "i"), @@ -398,7 +570,7 @@ public Page search(GroupSearchDto searchDto, Pageable pageable) { ); filters.add(criteria); } - if (searchDto.getRealmId() != null && !searchDto.getRealmId().isBlank()) { + if (searchDto != null && searchDto.getRealmId() != null && !searchDto.getRealmId().isBlank()) { filters.add(Criteria.where("realmId").regex(searchDto.getRealmId(), "i")); } Query query = Query.query(filters.isEmpty() ? new Criteria() : new Criteria().andOperator(filters.toArray(new Criteria[0]))); @@ -407,6 +579,42 @@ public Page search(GroupSearchDto searchDto, Pageable pageable) { return new PageImpl<>(groups, pageable, count); } + @Override + public Group addRole(String groupId, String roleId) { + Group group = findById(groupId); + ProcessRole role = processRoleService.findById(new ProcessResourceId(roleId)); + return addRole(group, role); + } + + @Override + public Group addRole(Group group, ProcessRole processRole) { + Assert.notNull(group, "Group cannot be null"); + Assert.notNull(processRole, "Process role cannot be null"); + group.addProcessRole(processRole); + return save(group); + } + + @Override + public Group removeRole(String groupId, String roleId) { + Group group = findById(groupId); + ProcessRole role = processRoleService.findById(new ProcessResourceId(roleId)); + return removeRole(group, role); + } + + @Override + public Group removeRole(Group group, ProcessRole processRole) { + Assert.notNull(group, "Group cannot be null"); + Assert.notNull(processRole, "Process role cannot be null"); + + group.removeProcessRole(processRole); + return save(group); + } + + @Override + public Page findAllByProcessRoles(Collection roleIds, Pageable pageable) { + return groupRepository.findAllByProcessRoles__idIn(roleIds, pageable); + } + protected String getGroupOwnerEmail(Group groupCase) { return userService.findById(groupCase.getOwnerId(), groupCase.getRealmId()).getEmail(); } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java index 2f909d90944..f57ba8c30a0 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java @@ -2,9 +2,11 @@ import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.adapter.spring.petrinet.web.responsebodies.ProcessRole; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.dto.response.group.GroupDto; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import java.util.Locale; import java.util.stream.Collectors; @@ -17,9 +19,12 @@ public class UserFactoryImpl implements UserFactory { @Autowired private ProcessRoleFactory processRoleFactory; + @Autowired + private GroupService groupService; + @Override - public User getUser(AbstractUser user, Locale locale) { - User result = getUser(user); + public UserDto getUser(AbstractUser user, Locale locale) { + UserDto result = UserDto.createUser(user, groupService.findAllByIds(user.getGroupIds(), Pageable.unpaged()).stream().map(group -> GroupDto.fromGroup(group, locale)).collect(Collectors.toList())); String defaultRoleId = processRoleService.getDefaultRole().getStringId(); String anonymousRoleId = processRoleService.getAnonymousRole().getStringId(); @@ -36,7 +41,4 @@ public User getUser(AbstractUser user, Locale locale) { return result; } - protected User getUser(AbstractUser user) { - return User.createUser(user); - } } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index 218f8b364ad..5da3e61ec0a 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -584,6 +584,7 @@ public AbstractUser createSystemUser() { system.setLastName(UserConstants.SYSTEM_USER_SURNAME); system.setState(UserState.ACTIVE); saveUser(system); + systemUser = system; } return system; } diff --git a/nae-user-common/pom.xml b/nae-user-common/pom.xml index 9650f1e55bb..21d5afe656d 100644 --- a/nae-user-common/pom.xml +++ b/nae-user-common/pom.xml @@ -129,6 +129,11 @@ junit-jupiter test + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java index aaa9a0749e1..ada0294fc5c 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java @@ -1,10 +1,13 @@ package com.netgrif.application.engine.auth.repository; import com.netgrif.application.engine.objects.auth.domain.Group; +import com.netgrif.application.engine.objects.auth.domain.User; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; @@ -21,6 +24,15 @@ @Repository public interface GroupRepository extends MongoRepository, QuerydslPredicateExecutor { + + /** + * Checks if a {@link Group} entity exists with the given identifier. + * + * @param identifier the unique identifier of the group + * @return {@code true} if a group with the specified identifier exists, otherwise {@code false} + */ + boolean existsByIdentifier(String identifier); + /** * Finds paginated list of all {@link Group} entities that have the given owner ID. * @@ -58,6 +70,8 @@ public interface GroupRepository extends MongoRepository, Queryds Page findAllByRealmIdIn(Collection realmIds, Pageable pageable); + Page findAllByProcessRoles__idIn(Collection rolesId, Pageable pageable); + void removeAllByRealmIdIn(Collection realmIds); void removeAllByRealmId(String realmId); diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java new file mode 100644 index 00000000000..c8d5dca211f --- /dev/null +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java @@ -0,0 +1,5 @@ +package com.netgrif.application.engine.auth.service; + +public interface AuthorizationService { + boolean hasAuthority(String authority); +} diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java index 81f5a20714c..1ec21340737 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java @@ -1,8 +1,11 @@ package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.domain.Group; -import com.netgrif.application.engine.objects.auth.dto.GroupSearchDto; +import com.netgrif.application.engine.objects.dto.request.group.GroupSearchRequestDto; +import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; import org.springframework.data.mongodb.core.query.Query; import com.querydsl.core.types.Predicate; import org.springframework.data.domain.Page; @@ -12,6 +15,7 @@ import java.util.Collection; import java.util.Optional; import java.util.List; +import java.util.Set; /** * Service interface for managing user groups in the application. @@ -83,7 +87,14 @@ public interface GroupService { * @return the {@link Group} with the specified ID */ Group findById(String id); - + + /** + * Finds all groups matching the specified collection of IDs with pagination support. + * + * @param ids collection of group IDs to search for + * @param pageable pagination information + * @return a page of {@link Group}s that match the provided IDs + */ Page findAllByIds(Collection ids, Pageable pageable); /** @@ -139,14 +150,24 @@ public interface GroupService { Group getDefaultSystemGroup(); /** - * Adds a user to a group within a specific realm. + * Assigns a set of users to a group based on its ID. + *

+ * This method updates the membership of the specified group by adding the provided user IDs. * + * @param groupId the ID of the group to which the users will be assigned + * @param userIds a set of user IDs to assign to the group + * @return the updated {@link Group} object including the newly assigned users + */ + Group assignUsersToGroup(String groupId, Set userIds); + + /** + * Adds a user to a group within a specific realm. + * @param groupId id of the group * @param userId ID of the user to add - * @param groupId ID of the group * @param realmId ID of the realm * @return the updated {@link Group} */ - Group addUser(String userId, String groupId, String realmId); + Group addUser(String groupId, String userId, String realmId); /** * Adds a user to a group within a specific realm. @@ -156,16 +177,15 @@ public interface GroupService { * @param realmId ID of the realm * @return the updated {@link Group} */ - Group addUser(String userId, Group group, String realmId); + Group addUser(Group group, String userId, String realmId); /** * Adds a user to a group specified by identifier. - * + * @param groupId id of the target group * @param user the user to add - * @param groupIdentifier identifier of the target group * @return the updated {@link Group} */ - Group addUser(AbstractUser user, String groupIdentifier); + Group addUser(String groupId, AbstractUser user); /** * Adds a user to a specific group. @@ -174,25 +194,34 @@ public interface GroupService { * @param group the group to add the user to * @return the updated {@link Group} */ - Group addUser(AbstractUser user, Group group); + Group addUser(Group group, AbstractUser user); + + /** + * Removes a user from a group within a specific realm. + * @param groupId the unique identifier of the group + * @param userId the ID of the user to remove + * @param realmId the ID of the realm where the group exists + * @return the updated {@link Group} + */ + Group removeUser(String groupId, String userId, String realmId); /** - * Removes a user from a group specified by identifier. + * Removes a user from a group specified by its unique identifier. * - * @param user the user to remove - * @param groupIdentifier identifier of the target group + * @param groupId the unique identifier of the target group + * @param user the user to remove * @return the updated {@link Group} */ - Group removeUser(AbstractUser user, String groupIdentifier); + Group removeUser(String groupId, AbstractUser user); /** * Removes a user from a specific group. * - * @param user the user to remove - * @param group the group to remove the user from + * @param user the user to be removed + * @param group the group from which the user will be removed * @return the updated {@link Group} */ - Group removeUser(AbstractUser user, Group group); + Group removeUser(Group group, AbstractUser user); /** * Finds groups matching a given predicate with pagination. @@ -203,8 +232,17 @@ public interface GroupService { */ Page findByPredicate(Predicate predicate, Pageable pageable); + /** + * Finds groups matching the specified MongoDB query with pagination support. + * + * @param query the MongoDB query defining the criteria for searching groups + * @param pageable pagination information + * @return a page of {@link Group}s that match the query criteria + */ Page findByQuery(Query query, Pageable pageable); + Group assignAuthorities(String groupId, Set authorityIds); + /** * Assigns an authority to a group. * @@ -212,7 +250,52 @@ public interface GroupService { * @param authorityId ID of the authority to assign * @return the updated {@link Group} */ - Group assignAuthority(String groupId, String authorityId); + Group addAuthority(String groupId, String authorityId); + + + /** + * Assigns a specific authority to the provided group. + *

+ * This method updates the group by adding the specified authority to its list of associated authorities. + * + * @param group the {@link Group} object to which the authority will be added + * @param authority the {@link Authority} to be assigned to the group + * @return the updated {@link Group} object with the newly assigned authority + */ + Group addAuthority(Group group, Authority authority); + + /** + * Removes a specific authority from a group. + * + * @param groupId the ID of the group from which the authority will be removed + * @param authorityId the ID of the authority to remove + * @return the updated {@link Group} object without the specified authority + */ + Group removeAuthority(String groupId, String authorityId); + + + /** + * Removes a specific authority from the provided group. + *

+ * This method updates the group by removing the specified authority. + * + * @param group the {@link Group} object from which the authority will be removed + * @param authority the {@link Authority} to be removed from the group + * @return the updated {@link Group} object without the specified authority + */ + Group removeAuthority(Group group, Authority authority); + + /** + * Assigns multiple subgroups to a parent group. + *

+ * This method establishes relationships between the specified child groups + * and the parent group identified by its ID. + * + * @param parentGroupId the ID of the parent group to which subgroups will be assigned + * @param childGroupIds the list of IDs of the subgroups to be assigned to the parent group + * @return the updated {@link Group} representing the parent group with its new subgroup assignments + */ + Group assignSubgroups(String parentGroupId, Set childGroupIds); /** * Adds a subgroup relationship between two groups. @@ -250,6 +333,43 @@ public interface GroupService { */ Pair addSubgroup(Group parentGroup, Group childGroup); + + /** + * Removes a subgroup relationship between two groups by their IDs. + * + * @param parentGroupId the ID of the parent group + * @param childGroupId the ID of the child group + * @return a {@link Pair} containing both the updated parent and child {@link Group}s + */ + Pair removeSubgroup(String parentGroupId, String childGroupId); + + /** + * Removes a subgroup relationship between a parent group object and a child group by its ID. + * + * @param parentGroup the parent group object + * @param childGroupId the ID of the child group + * @return a {@link Pair} containing both the updated parent and child {@link Group}s + */ + Pair removeSubgroup(Group parentGroup, String childGroupId); + + /** + * Removes a subgroup relationship between a parent group by its ID and a child group object. + * + * @param parentGroupId the ID of the parent group + * @param childGroup the child group object + * @return a {@link Pair} containing both the updated parent and child {@link Group}s + */ + Pair removeSubgroup(String parentGroupId, Group childGroup); + + /** + * Removes a subgroup relationship between two group objects. + * + * @param parentGroup the parent group object + * @param childGroup the child group object + * @return a {@link Pair} containing both the updated parent and child {@link Group}s + */ + Pair removeSubgroup(Group parentGroup, Group childGroup); + /** * Retrieves all parent groups of a specified group. * @@ -302,9 +422,49 @@ public interface GroupService { /** * Searches for groups based on the provided search criteria and pageable details. * - * @param searchDto the search criteria encapsulated in a {@link GroupSearchDto} + * @param searchDto the search criteria encapsulated in a {@link GroupSearchRequestDto} * @param pageable pagination information for the results * @return a page of {@link Group} objects that match the search criteria */ - Page search(GroupSearchDto searchDto, Pageable pageable); + Page search(GroupSearchRequestDto searchDto, Pageable pageable); + + /** + * Adds a role to a specified group. + * + * @param groupId the ID of the group to which the role will be added + * @param roleId the ID of the role to add to the group + * @return the updated {@link Group} object with the new role assigned + */ + Group addRole(String groupId, String roleId); + + + /** + * Assigns a specific process role to a group. + * + * @param group the {@link Group} to which the role will be added + * @param processRole the {@link ProcessRole} to assign to the group + * @return the updated {@link Group} object with the assigned role + */ + Group addRole(Group group, ProcessRole processRole); + + /** + * Removes a role from a specified group. + * + * @param groupId the ID of the group from which the role will be removed + * @param roleId the ID of the role to remove from the group + * @return the updated {@link Group} object without the specified role + */ + Group removeRole(String groupId, String roleId); + + + /** + * Removes a specified process role from a group. + * + * @param group the {@link Group} from which the role will be removed + * @param processRole the {@link ProcessRole} to remove from the group + * @return the updated {@link Group} object without the specified role + */ + Group removeRole(Group group, ProcessRole processRole); + + Page findAllByProcessRoles(Collection roleIds, Pageable pageable); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserFactory.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserFactory.java index 8d5b3d73092..4243ee96d68 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserFactory.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserFactory.java @@ -1,7 +1,7 @@ package com.netgrif.application.engine.auth.service; -import com.netgrif.application.engine.auth.web.responsebodies.User; +import com.netgrif.application.engine.auth.web.responsebodies.UserDto; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import java.util.Locale; @@ -12,5 +12,5 @@ public interface UserFactory { * @param locale the locale for translations * @return a full version of the user response object, that has all of its attributes set */ - User getUser(AbstractUser user, Locale locale); + UserDto getUser(AbstractUser user, Locale locale); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java similarity index 79% rename from nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java rename to nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java index 42605f8591e..c9c3e1b1193 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java @@ -6,15 +6,18 @@ import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.domain.Credential; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; +import com.netgrif.application.engine.objects.dto.response.group.GroupDto; import lombok.Data; import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @Data -public class User { +public class UserDto { public static final String ATTR_ENABLED_CREDENTIALS = "enabledCredentials"; private String id; @@ -28,22 +31,22 @@ public class User { private Set authorities; private Set processRoles; private Set negativeProcessRoles; - private Set nextGroups; - private User impersonated; + private Set groupIds; + private Set groups; + private UserDto impersonated; private LocalDateTime createdAt; private Map> attributes; private boolean enabled; private boolean emailVerified; protected UserState state; - public User(AbstractUser user) { + public UserDto(AbstractUser user) { Attribute> enabledCredentialsAttribute = new Attribute<>(); if (user instanceof com.netgrif.application.engine.objects.auth.domain.User domainUser) { Map> credentials = domainUser.getCredentials(); enabledCredentialsAttribute.setValue( - (credentials == null ? java.util.Map.>of() : credentials) + (credentials == null ? Map.>of() : credentials) .values().stream() - .filter(java.util.Objects::nonNull) .filter(Credential::isEnabled) .map(Credential::getType) .filter(java.util.Objects::nonNull) @@ -73,10 +76,18 @@ public User(AbstractUser user) { } } - public static User createUser(AbstractUser user) { - User result = new User(user); + public static UserDto createUser(AbstractUser user) { + UserDto result = new UserDto(user); result.setAuthorities(user.getAuthoritySet()); - result.setNextGroups(user.getGroupIds()); + result.setGroupIds(user.getGroupIds()); + return result; + } + + public static UserDto createUser(AbstractUser user, List groups) { + UserDto result = createUser(user); + if (groups != null) { + result.setGroups(new HashSet<>(groups)); + } return result; } } diff --git a/nae-user-common/src/test/java/com/netgrif/application/engine/auth/web/responsebodies/UserDtoTest.java b/nae-user-common/src/test/java/com/netgrif/application/engine/auth/web/responsebodies/UserDtoTest.java new file mode 100644 index 00000000000..b428b7e7937 --- /dev/null +++ b/nae-user-common/src/test/java/com/netgrif/application/engine/auth/web/responsebodies/UserDtoTest.java @@ -0,0 +1,271 @@ +package com.netgrif.application.engine.auth.web.responsebodies; + +import com.netgrif.application.engine.adapter.spring.petrinet.web.responsebodies.ProcessRole; +import com.netgrif.application.engine.objects.auth.domain.*; +import com.netgrif.application.engine.objects.auth.domain.enums.UserState; +import com.netgrif.application.engine.objects.dto.response.group.GroupDto; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class UserDtoTest { + + static class DomainUser extends User { + public DomainUser() { + super(); + this.id = new ObjectId(); + this.firstName = "John"; + this.lastName = "Doe"; + this.username = "johndoe"; + this.email = "john@example.com"; + this.realmId = "realm-1"; + this.avatar = "http://avatar.url"; + } + } + + static class NonDomainUser extends AbstractUser { + public NonDomainUser() { + super(); + this.id = new ObjectId(); + this.firstName = "Jane"; + this.lastName = "Smith"; + this.username = "janesmith"; + this.email = "jane@example.com"; + this.realmId = "realm-2"; + this.avatar = "http://avatar2.url"; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public void setPassword(String password) { + } + } + + static class TestAuthority extends Authority { + private final ObjectId id = new ObjectId(); + + public TestAuthority(String name) { + super(name); + } + + @Override + public ObjectId get_id() { + return id; + } + + @Override + public String getStringId() { + return id.toString(); + } + } + + @Test + void testConstructorWithDomainUserAndCredentials() { + DomainUser user = new DomainUser(); + user.setState(UserState.ACTIVE); + user.setEmailVerified(true); + user.setAttribute("customAttr", "customValue", false); + + // Add credentials + user.setCredential("password", new PasswordCredential("secret", 0, true)); + user.setCredential("token", new TokenCredential("token-123", 1, true)); + user.setCredential("disabled-cred", new StringCredential("disabled-type", "val", 2, false)); + user.setCredential("custom-cred", new Credential("custom-type", "val2", 3, true) {}); + + UserDto dto = new UserDto(user); + + assertEquals(user.getStringId(), dto.getId()); + assertEquals(user.getUsername(), dto.getUsername()); + assertEquals(user.getRealmId(), dto.getRealmId()); + assertEquals(user.getEmail(), dto.getEmail()); + assertEquals(user.getAvatar(), dto.getAvatar()); + assertEquals(user.getFirstName(), dto.getFirstName()); + assertEquals(user.getLastName(), dto.getLastName()); + assertEquals(user.getFullName(), dto.getFullName()); + assertTrue(dto.isEnabled()); + assertTrue(dto.isEmailVerified()); + assertEquals(UserState.ACTIVE, dto.getState()); + assertEquals(user.getCreatedAt(), dto.getCreatedAt()); + + assertNotNull(dto.getAttributes()); + assertTrue(dto.getAttributes().containsKey("customAttr")); + assertTrue(dto.getAttributes().containsKey(UserDto.ATTR_ENABLED_CREDENTIALS)); + + @SuppressWarnings("unchecked") + Attribute> credAttr = (Attribute>) dto.getAttributes().get(UserDto.ATTR_ENABLED_CREDENTIALS); + assertTrue(credAttr.isRequired()); + Set enabledTypes = credAttr.getValue(); + assertTrue(enabledTypes.contains("password")); + assertTrue(enabledTypes.contains("token")); + assertFalse(enabledTypes.contains("disabled-type")); + } + + @Test + void testConstructorWithDomainUserNullCredentialsAndNullAttributes() { + DomainUser user = new DomainUser(); + user.setCredentials(null); + user.setAttributes(null); + user.setState(UserState.INACTIVE); + user.setEmailVerified(false); + + UserDto dto = new UserDto(user); + + assertEquals(user.getStringId(), dto.getId()); + assertFalse(dto.isEnabled()); + assertFalse(dto.isEmailVerified()); + assertEquals(UserState.INACTIVE, dto.getState()); + assertNotNull(dto.getAttributes()); + assertFalse(dto.getAttributes().containsKey(UserDto.ATTR_ENABLED_CREDENTIALS)); + } + + @Test + void testConstructorWithNonDomainUser() { + NonDomainUser user = new NonDomainUser(); + user.setAttribute("attr1", "val1", true); + + UserDto dto = new UserDto(user); + + assertEquals(user.getStringId(), dto.getId()); + assertEquals(user.getUsername(), dto.getUsername()); + assertEquals(user.getRealmId(), dto.getRealmId()); + assertEquals(user.getEmail(), dto.getEmail()); + assertEquals(user.getAvatar(), dto.getAvatar()); + assertEquals(user.getFirstName(), dto.getFirstName()); + assertEquals(user.getLastName(), dto.getLastName()); + assertEquals(user.getFullName(), dto.getFullName()); + assertNull(dto.getState()); + assertFalse(dto.isEnabled()); + assertFalse(dto.isEmailVerified()); + assertNull(dto.getCreatedAt()); + assertNotNull(dto.getAttributes()); + assertFalse(dto.getAttributes().containsKey(UserDto.ATTR_ENABLED_CREDENTIALS)); + assertTrue(dto.getAttributes().containsKey("attr1")); + } + + @Test + void testCreateUserFactoryMethods() { + DomainUser user = new DomainUser(); + Authority auth1 = new TestAuthority("ADMIN"); + user.addAuthority(auth1); + user.addGroupId("group-1"); + + UserDto dto = UserDto.createUser(user); + assertNotNull(dto.getAuthorities()); + assertEquals(1, dto.getAuthorities().size()); + assertTrue(dto.getAuthorities().contains(auth1)); + assertNotNull(dto.getGroupIds()); + assertTrue(dto.getGroupIds().contains("group-1")); + assertNull(dto.getGroups()); + + // createUser with null groups + UserDto dtoNullGroups = UserDto.createUser(user, null); + assertNull(dtoNullGroups.getGroups()); + + // createUser with groups list + GroupDto groupDto1 = new GroupDto("group-1", "Group 1", "grp-1", "owner1", Set.of(), Set.of(), Set.of(), Set.of()); + UserDto dtoWithGroups = UserDto.createUser(user, List.of(groupDto1)); + assertNotNull(dtoWithGroups.getGroups()); + assertEquals(1, dtoWithGroups.getGroups().size()); + assertTrue(dtoWithGroups.getGroups().contains(groupDto1)); + } + + static class TestDomainProcessRole extends com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole { + public TestDomainProcessRole(String id) { + super(id); + } + } + + @Test + void testGettersSettersAndLombokMethods() { + UserDto dto1 = new UserDto(new DomainUser()); + UserDto dto2 = new UserDto(new DomainUser()); + + dto1.setId("id-1"); + dto2.setId("id-1"); + dto1.setUsername("user1"); + dto2.setUsername("user1"); + dto1.setRealmId("realm-1"); + dto2.setRealmId("realm-1"); + dto1.setEmail("u1@test.com"); + dto2.setEmail("u1@test.com"); + dto1.setAvatar("av1"); + dto2.setAvatar("av1"); + dto1.setFirstName("First"); + dto2.setFirstName("First"); + dto1.setLastName("Last"); + dto2.setLastName("Last"); + dto1.setFullName("First Last"); + dto2.setFullName("First Last"); + + Set authorities = Set.of(new TestAuthority("USER")); + dto1.setAuthorities(authorities); + dto2.setAuthorities(authorities); + + String roleCompositeId = new com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId().getFullId(); + ProcessRole role1 = new ProcessRole(new TestDomainProcessRole(roleCompositeId), Locale.ENGLISH); + Set roles = Set.of(role1); + dto1.setProcessRoles(roles); + dto2.setProcessRoles(roles); + dto1.setNegativeProcessRoles(roles); + dto2.setNegativeProcessRoles(roles); + + Set groupIds = Set.of("g1"); + dto1.setGroupIds(groupIds); + dto2.setGroupIds(groupIds); + + GroupDto groupDto = new GroupDto("g1", "Group", "grp", "owner", Set.of(), Set.of(), Set.of(), Set.of()); + Set groups = Set.of(groupDto); + dto1.setGroups(groups); + dto2.setGroups(groups); + + UserDto impersonated = new UserDto(new DomainUser()); + dto1.setImpersonated(impersonated); + dto2.setImpersonated(impersonated); + + LocalDateTime now = LocalDateTime.now(); + dto1.setCreatedAt(now); + dto2.setCreatedAt(now); + + Map> attributes = Map.of("k", new Attribute<>("v", false)); + dto1.setAttributes(attributes); + dto2.setAttributes(attributes); + + dto1.setEnabled(true); + dto2.setEnabled(true); + dto1.setEmailVerified(true); + dto2.setEmailVerified(true); + dto1.setState(UserState.ACTIVE); + dto2.setState(UserState.ACTIVE); + + assertEquals(dto1, dto2); + assertEquals(dto1.hashCode(), dto2.hashCode()); + assertNotNull(dto1.toString()); + assertEquals("id-1", dto1.getId()); + assertEquals("user1", dto1.getUsername()); + assertEquals("realm-1", dto1.getRealmId()); + assertEquals("u1@test.com", dto1.getEmail()); + assertEquals("av1", dto1.getAvatar()); + assertEquals("First", dto1.getFirstName()); + assertEquals("Last", dto1.getLastName()); + assertEquals("First Last", dto1.getFullName()); + assertEquals(authorities, dto1.getAuthorities()); + assertEquals(roles, dto1.getProcessRoles()); + assertEquals(roles, dto1.getNegativeProcessRoles()); + assertEquals(groupIds, dto1.getGroupIds()); + assertEquals(groups, dto1.getGroups()); + assertEquals(impersonated, dto1.getImpersonated()); + assertEquals(now, dto1.getCreatedAt()); + assertEquals(attributes, dto1.getAttributes()); + assertTrue(dto1.isEnabled()); + assertTrue(dto1.isEmailVerified()); + assertEquals(UserState.ACTIVE, dto1.getState()); + } +}