diff --git a/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java b/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java index 9b7c2bb0c98..b52ee3581b4 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java @@ -1091,7 +1091,6 @@ protected ProcessRole initRole(Role importRole) { role.setName(toI18NString(importRole.getName())); } if (importRole.isGlobal() != null && importRole.isGlobal()) { - role.set_id(new ProcessResourceId(new ObjectId())); role.setGlobal(importRole.isGlobal()); } else { role.set_id(new ProcessResourceId(new ObjectId(net.getStringId()))); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java index bd7f49907b1..3952d3b72db 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/config/ProcessBeansConfiguration.java @@ -11,6 +11,8 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.security.service.ISecurityContextService; import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; @@ -31,7 +33,9 @@ public ProcessRoleService processRoleService(ProcessRoleRepository processRoleRe ISecurityContextService securityContextService, @Lazy GroupService groupService, @Lazy RealmService realmService, - @Lazy PaginationProperties paginationProperties + @Lazy PaginationProperties paginationProperties, + @Lazy IWorkflowService workflowService, + @Lazy ITaskService taskService ) { return new com.netgrif.application.engine.petrinet.service.ProcessRoleService( processRoleRepository, @@ -43,7 +47,9 @@ public ProcessRoleService processRoleService(ProcessRoleRepository processRoleRe securityContextService, groupService, realmService, - paginationProperties + paginationProperties, + workflowService, + taskService ); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java index 641a0c50af1..a8dd005b207 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java @@ -6,6 +6,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.data.querydsl.QuerydslPredicateExecutor; /** @@ -56,4 +57,15 @@ public interface PetriNetRepository extends MongoRepository, Q * @param id the unique ID of the PetriNet to delete. */ void deleteBy_id(ObjectId id); + + + /** + * Finds a paginated list of {@link PetriNet} entities associated with a specific role ID. + * + * @param roleId the ID of the role to filter PetriNets by + * @param pageable the pagination details + * @return a {@link Page} of {@link PetriNet} entities matching the specified role ID + */ + @Query("{ 'roles.?0' : { $exists: true } }") + Page findAllByRoleId(String roleId, Pageable pageable); } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 18d214f0ce7..a4684616368 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -329,6 +329,13 @@ public PetriNetImportReference getNetFromCase(String caseId) { return pn; } + @Override + public Page findAllByRoleId(String roleId, Pageable pageable) { + Page nets = repository.findAllByRoleId(roleId, pageable); + nets.forEach(PetriNet::initializeArcs); + return nets; + } + @Override public Page getAll(Pageable pageable) { Page nets = repository.findAll(pageable); 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 e4f3fd4b35f..1c94340d3a8 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 @@ -1,5 +1,8 @@ package com.netgrif.application.engine.petrinet.service; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleNotFoundException; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleNotGlobalException; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleReferencedException; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.GroupService; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; @@ -12,6 +15,8 @@ import com.netgrif.application.engine.objects.importer.model.EventPhaseType; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action; +import com.netgrif.application.engine.objects.workflow.domain.Case; +import com.netgrif.application.engine.objects.workflow.domain.QCase; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.context.RoleContext; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.runner.RoleActionsRunner; import com.netgrif.application.engine.objects.petrinet.domain.events.Event; @@ -22,6 +27,9 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.security.service.ISecurityContextService; import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; +import lombok.Getter; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +48,7 @@ public class ProcessRoleService implements com.netgrif.application.engine.adapte private static final Logger log = LoggerFactory.getLogger(ProcessRoleService.class); private final UserService userService; + @Getter private final ProcessRoleRepository processRoleRepository; private final PetriNetRepository netRepository; private final ApplicationEventPublisher publisher; @@ -48,7 +57,12 @@ public class ProcessRoleService implements com.netgrif.application.engine.adapte private final ISecurityContextService securityContextService; private final GroupService groupService; private final RealmService realmService; + @Getter private final PaginationProperties paginationProperties; + @Getter + private final IWorkflowService workflowService; + @Getter + private final ITaskService taskService; private ProcessRole defaultRole; private ProcessRole anonymousRole; @@ -57,7 +71,7 @@ public ProcessRoleService(ProcessRoleRepository processRoleRepository, PetriNetRepository netRepository, ApplicationEventPublisher publisher, RoleActionsRunner roleActionsRunner, @Lazy IPetriNetService petriNetService, @Lazy UserService userService, ISecurityContextService securityContextService, @Lazy GroupService groupService, - @Lazy RealmService realmService, @Lazy PaginationProperties paginationProperties) { + @Lazy RealmService realmService, @Lazy PaginationProperties paginationProperties, @Lazy IWorkflowService workflowService, @Lazy ITaskService taskService) { this.processRoleRepository = processRoleRepository; this.netRepository = netRepository; this.publisher = publisher; @@ -68,6 +82,8 @@ public ProcessRoleService(ProcessRoleRepository processRoleRepository, this.groupService = groupService; this.realmService = realmService; this.paginationProperties = paginationProperties; + this.workflowService = workflowService; + this.taskService = taskService; } @Override @@ -145,7 +161,7 @@ protected void saveUserAndReloadContext(AbstractUser user, LoggedUser loggedUser String userId = user.getStringId(); securityContextService.saveToken(userId); - if (Objects.equals(userId, loggedUser.getId())) { + if (Objects.equals(userId, loggedUser.getStringId())) { loggedUser.getProcessRoles().clear(); loggedUser.setProcessRoles(user.getProcessRoles()); securityContextService.reloadSecurityContext(loggedUser); @@ -439,11 +455,67 @@ public void deleteRolesOfNet(PetriNet net, LoggedUser loggedUser) { this.processRoleRepository.deleteAllBy_idIn(deletedRoleIds); } + @Override public void clearCache() { this.defaultRole = null; this.anonymousRole = null; } + @Override + public void deleteGlobalRole(String roleId, LoggedUser loggedUser) { + ProcessRole processRole = this.findById(roleId); + if (processRole == null) { + throw new RoleNotFoundException("Role with id [%s] not found.".formatted(roleId)); + } + if (!processRole.isGlobal()) { + throw new RoleNotGlobalException("Role with id [%s] is not global.".formatted(roleId)); + } + if (ProcessRole.DEFAULT_ROLE.equals(processRole.getImportId()) || ProcessRole.ANONYMOUS_ROLE.equals(processRole.getImportId())) { + throw new IllegalArgumentException("Deleting core roles (DEFAULT/ANONYMOUS) is forbidden."); + } + if (isRoleReferenced(processRole)) { + throw new RoleReferencedException("Role with id [%s] is referenced by other processes. Please delete or update the process before deleting.".formatted(roleId)); + } + log.info("Initiating deletion of global role with import ID [{}] and object ID [{}]", processRole.getImportId(), processRole.getStringId()); + Pageable realmPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page realms; + do { + realms = realmService.getSmallRealm(realmPageable); + realms.forEach(realm -> { + Pageable usersPageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page users; + do { + users = this.userService.findAllByProcessRoles(Set.of(processRole.get_id()), realm.getName(), usersPageable); + for (AbstractUser user : users) { + removeRoleFromUser(user, processRole, loggedUser); + } + usersPageable = usersPageable.next(); + } while (users.hasNext()); + }); + realmPageable = realmPageable.next(); + } while (realms.hasNext()); + log.info("Deleting global role with import ID [{}] and object ID [{}]", processRole.getImportId(), processRole.getStringId()); + this.processRoleRepository.delete(processRole); + } + + protected boolean isRoleReferenced(ProcessRole processRole) { + Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()); + Page petriNetPage = petriNetService.findAllByRoleId(processRole.getStringId(), pageable); + return petriNetPage.getTotalElements() > 0; + } + + private void removeRoleFromUser(AbstractUser user, ProcessRole processRole, LoggedUser loggedUser) { + log.info("Removing global role with import ID [{}] and object ID [{}] from user [{}] with id [{}]", processRole.getImportId(), processRole.getStringId(), user.getFullName(), user.getStringId()); + if (user.getProcessRoles().isEmpty()) { + return; + } + Set newRoles = user.getProcessRoles().stream() + .filter(role -> !role.getStringId().equals(processRole.getStringId())) + .map(ProcessRole::get_id) + .collect(Collectors.toSet()); + this.assignRolesToUser(user, newRoles, loggedUser); + } + private ObjectId extractObjectId(String caseId) { String[] parts = caseId.split("-"); if (parts.length < 2) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index 9bd338b60b5..c6fd966cb51 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -344,4 +344,14 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti * @return a {@link PetriNetImportReference} linking the PetriNet */ PetriNetImportReference getNetFromCase(String caseId); + + + /** + * Retrieves a paginated list of {@link PetriNet} objects associated with a specific role ID. + * + * @param roleId the ID of the role to filter the PetriNets by + * @param pageable the pagination information + * @return a {@link Page} of {@link PetriNet} objects matching the role ID + */ + Page findAllByRoleId(String roleId, Pageable pageable); } \ No newline at end of file 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 new file mode 100644 index 00000000000..77cac9d06fd --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java @@ -0,0 +1,74 @@ +package com.netgrif.application.engine.petrinet.web; + +import com.netgrif.application.engine.adapter.spring.common.web.responsebodies.ResponseMessage; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleNotFoundException; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleNotGlobalException; +import com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.RoleReferencedException; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.objects.auth.domain.LoggedUser; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@Slf4j +@RestController +@RequiredArgsConstructor +@Tag(name = "ProcessRoles") +@RequestMapping("/api/roles") +public class ProcessRoleController { + + private final ProcessRoleService processRoleService; + + @PreAuthorize("@authorizationService.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") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Global role was deleted successfully"), + @ApiResponse(responseCode = "400", description = "Invalid role id"), + @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), + @ApiResponse(responseCode = "404", description = "Role not found"), + @ApiResponse(responseCode = "409", description = "Role is not global"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + @DeleteMapping(value = "/{id}",produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity deleteGlobalRole(@PathVariable("id") String id, Authentication auth) { + try { + LoggedUser user = (LoggedUser) auth.getPrincipal(); + processRoleService.deleteGlobalRole(id, user); + } catch (RoleNotFoundException e) { + String message = "Error when deleting global role [%s]".formatted(id); + log.error(message, e); + return ResponseEntity.status(404).body(ResponseMessage.createErrorMessage(e.getMessage())); + } catch (RoleNotGlobalException e) { + String message = "Error when deleting global role [%s]".formatted(id); + log.error(message, e); + return ResponseEntity.status(409).body(ResponseMessage.createErrorMessage(e.getMessage())); + } catch (RoleReferencedException e) { + String message = "Error when deleting global role [%s]".formatted(id); + log.error(message, e); + return ResponseEntity.status(400).body(ResponseMessage.createErrorMessage(e.getMessage())); + } catch (IllegalArgumentException e) { + String message = "Error when deleting global role [%s]".formatted(id); + log.error(message, e); + return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(e.getMessage())); + } + return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Global role was deleted successfully")); + } + +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNetSearch.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNetSearch.java index f39ec3dc0fe..3db8891f7c4 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNetSearch.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNetSearch.java @@ -28,6 +28,8 @@ public class PetriNetSearch { private ActorRef author; + private List roles; + private List negativeViewRoles; private Map tags; diff --git a/nae-object-library/src/main/resources/petriflow_schema.xsd b/nae-object-library/src/main/resources/petriflow_schema.xsd index 28e88a26d6f..0bbd2530d69 100644 --- a/nae-object-library/src/main/resources/petriflow_schema.xsd +++ b/nae-object-library/src/main/resources/petriflow_schema.xsd @@ -3,6 +3,6 @@ - + - \ No newline at end of file + diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotFoundException.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotFoundException.java new file mode 100644 index 00000000000..3de7181e433 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotFoundException.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.adapter.spring.petrinet.domain.roles; + +public class RoleNotFoundException extends RuntimeException { + public RoleNotFoundException(String message) { + super(message); + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotGlobalException.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotGlobalException.java new file mode 100644 index 00000000000..a2276689e93 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleNotGlobalException.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.adapter.spring.petrinet.domain.roles; + +public class RoleNotGlobalException extends RuntimeException { + public RoleNotGlobalException(String message) { + super(message); + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleReferencedException.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleReferencedException.java new file mode 100644 index 00000000000..4ad962e40d4 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/roles/RoleReferencedException.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.adapter.spring.petrinet.domain.roles; + +public class RoleReferencedException extends RuntimeException { + public RoleReferencedException(String message) { + super(message); + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java index 11d3de47058..77869a0093f 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/service/ProcessRoleService.java @@ -37,4 +37,5 @@ public interface ProcessRoleService { Page findAllGlobalRoles(Pageable pageable); void deleteRolesOfNet(PetriNet net, LoggedUser loggedUser); void clearCache(); + void deleteGlobalRole(String roleId, LoggedUser loggedUser); }