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..a7fd8854e27 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 @@ -5,6 +5,7 @@ import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRol import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties import com.netgrif.application.engine.adapter.spring.workflow.domain.QCase import com.netgrif.application.engine.adapter.spring.workflow.domain.QTask +import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.auth.service.GroupService import com.netgrif.application.engine.auth.service.UserDetailsServiceImpl import com.netgrif.application.engine.auth.service.UserService @@ -29,8 +30,12 @@ import com.netgrif.application.engine.integration.modules.ModuleHolder import com.netgrif.application.engine.mail.domain.MailDraft import com.netgrif.application.engine.mail.interfaces.IMailAttemptService import com.netgrif.application.engine.mail.interfaces.IMailService +import com.netgrif.application.engine.objects.annotations.Authorize 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.dto.AuthoritySearchDto +import com.netgrif.application.engine.objects.auth.dto.GroupSearchDto import com.netgrif.application.engine.menu.services.interfaces.DashboardItemService import com.netgrif.application.engine.menu.services.interfaces.DashboardManagementService import com.netgrif.application.engine.menu.services.interfaces.IMenuItemService @@ -157,6 +162,9 @@ class ActionDelegate extends DelegateExpando { @Autowired GroupService groupService + @Autowired + AuthorityService authorityService + @Autowired ProcessRoleService processRoleService @@ -1414,10 +1422,16 @@ class ActionDelegate extends DelegateExpando { changeUserByEmail(email, "email", cl) }, name : { cl -> - changeUserByEmail(email, "name", cl) + changeUserByEmail(email, "firstName", cl) + }, + firstName : { cl -> + changeUserByEmail(email, "firstName", cl) }, surname: { cl -> - changeUserByEmail(email, "surname", cl) + changeUserByEmail(email, "lastName", cl) + }, + lastName : { cl -> + changeUserByEmail(email, "lastName", cl) }, tel : { cl -> changeUserByEmail(email, "tel", cl) @@ -1430,10 +1444,16 @@ class ActionDelegate extends DelegateExpando { changeUser(id, "email", cl) }, name : { cl -> - changeUser(id, "name", cl) + changeUser(id, "firstName", cl) + }, + firstName : { cl -> + changeUser(id, "firstName", cl) }, surname: { cl -> - changeUser(id, "surname", cl) + changeUser(id, "lastName", cl) + }, + lastName : { cl -> + changeUser(id, "lastName", cl) }, tel : { cl -> changeUser(id, "tel", cl) @@ -1446,10 +1466,16 @@ class ActionDelegate extends DelegateExpando { changeUser(user, "email", cl) }, name : { cl -> - changeUser(user, "name", cl) + changeUser(user, "firstName", cl) + }, + firstName : { cl -> + changeUser(user, "firstName", cl) }, surname: { cl -> - changeUser(user, "surname", cl) + changeUser(user, "lastName", cl) + }, + lastName : { cl -> + changeUser(user, "lastName", cl) }, tel : { cl -> changeUser(user, "tel", cl) @@ -1458,7 +1484,7 @@ class ActionDelegate extends DelegateExpando { } def changeUserByEmail(String email, String attribute, def cl) { - Optional userOptional = userService.findUserByUsername(email, null) + Optional userOptional = userService.findUserByEmail(email, null) if (!userOptional.isPresent()) { log.error("Cannot find user with email [" + email + "]") return @@ -3055,4 +3081,127 @@ class ActionDelegate extends DelegateExpando { IStorageService storageService = storageResolverService.resolve(storageField.storageType) return storageService.getPath(aCase.stringId, fileFieldId, fileName) } + + /** + * Returns page of all authorities. + * + * @param pageable page configuration, by default the whole first backend page is returned + * @return page of {@link Authority} objects + */ + Page findAllAuthorities(Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize())) { + return authorityService.findAll(pageable) + } + + /** + * Returns authority of given name. If the authority does not exist, it is created. + * + * @param name name of the authority + * @return existing or newly created {@link Authority} + */ + Authority getOrCreateAuthority(String name) { + return authorityService.getOrCreate(name) + } + + /** + * Returns authority by its database id. + * + * @param id id of the authority + * @return found {@link Authority} + */ + Authority getAuthority(String id) { + return authorityService.getOne(id) + } + + /** + * Returns authority of given name. Throws an exception if such authority does not exist. + * + * @param name name of the authority + * @return found {@link Authority} + */ + Authority getAuthorityByName(String name) { + return authorityService.findByName(name) + } + + /** + * Returns authority of given name or null if such authority does not exist. + * + * @param name name of the authority + * @return found {@link Authority} or null + */ + Authority findAuthorityByName(String name) { + Optional authority = authorityService.findOptionalByName(name) + if (authority.isEmpty()) { + log.warn("Cannot find authority with name [" + name + "]") + return null + } + return authority.get() + } + + /** + * Returns authorities of given ids. + * + * @param ids ids of the authorities + * @param pageable page configuration, by default the whole first backend page is returned + * @return list of found {@link Authority} objects + */ + List findAuthoritiesByIds(Collection ids, Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize())) { + if (ids == null || ids.isEmpty()) { + return [] + } + return authorityService.findAllByIds(ids, pageable).content + } + + /** + * Returns authorities of given scope. Scope contains authorities of the same name prefix, f.e. "PROCESS*" + * returns authorities PROCESS_UPLOAD, PROCESS_DELETE etc. Scope "*" returns every authority. + * + * @param scope scope of the authorities + * @return list of found {@link Authority} objects + */ + List findAuthoritiesByScope(String scope) { + return authorityService.findByScope(scope) + } + + /** + * Searches authorities by full text query on the authority name. + * + * @param fullText text to be searched for, null or empty text returns all authorities + * @param pageable page configuration, by default the whole first backend page is returned + * @return list of found {@link Authority} objects + */ + List searchAuthorities(String fullText, Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize())) { + AuthoritySearchDto searchDto = new AuthoritySearchDto() + searchDto.setFullText(fullText) + return authorityService.search(searchDto, pageable).content + } + + /** + * Deletes authority of given name. Nothing happens if such authority does not exist. + * + * @param name name of the authority + */ + void deleteAuthority(String name) { + authorityService.delete(name) + } + + /** + * @return set of default authorities of a user + */ + Set defaultUserAuthorities() { + return authorityService.getDefaultUserAuthorities() + } + + /** + * @return set of default authorities of an anonymous user + */ + Set defaultAnonymousAuthorities() { + return authorityService.getDefaultAnonymousAuthorities() + } + + /** + * @return set of default authorities of an admin + */ + Set defaultAdminAuthorities() { + return authorityService.getDefaultAdminAuthorities() + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthorityController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthorityController.java new file mode 100644 index 00000000000..699bf290982 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthorityController.java @@ -0,0 +1,101 @@ +package com.netgrif.application.engine.auth.web; + +import com.netgrif.application.engine.adapter.spring.common.web.responsebodies.ResponseMessage; +import com.netgrif.application.engine.auth.service.AuthorityService; +import com.netgrif.application.engine.auth.web.requestbodies.NewAuthorityRequest; +import com.netgrif.application.engine.auth.web.responsebodies.AuthorityDto; +import com.netgrif.application.engine.objects.annotations.Authorize; +import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource; +import io.swagger.v3.oas.annotations.Operation; +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.beans.factory.annotation.Autowired; +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.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@RestController +@RequestMapping("/api/authority") +@RequiredArgsConstructor +@ConditionalOnProperty( + value = "nae.user.web.enabled", + havingValue = "true", + matchIfMissing = true +) +@Tag(name = "Authority") +public class AuthorityController { + + private final AuthorityService authorityService; + + @Authorize(authority = "ADMIN") + @Authorize(authority = "AUTHORITY_DELETE") + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @DeleteMapping(value = "/delete/{name}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public MessageResource delete(@PathVariable String name, Authentication auth) { + try { + authorityService.delete(name); + log.info("Authority [{}] has been deleted successfully.", name); + return new MessageResource(ResponseMessage.createSuccessMessage("Authority [" + name + "] has been deleted successfully.")); + } catch (IllegalArgumentException | ResourceNotFoundException e) { + log.error("Failed to delete authority [{}].", name, e); + return new MessageResource(ResponseMessage.createErrorMessage("Failed to delete authority.")); + } + } + + @Authorize(authority = "ADMIN") + @Authorize(authority = "AUTHORITY_CREATE") + @Operation(description = "Create authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public ResponseEntity create(@RequestBody NewAuthorityRequest request) { + try { + Authority authority = authorityService.getOrCreate(request.name); + log.info("Authority [{}] has been created successfully.", authority); + return ResponseEntity.ok(new AuthorityDto(authority)); + } catch (IllegalArgumentException | ResourceNotFoundException e) { + log.error("Failed to create authority [{}].", request.name, e); + return null; + } + } + + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/all", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public ResponseEntity> getAll(Pageable pageable) { + Page authorities = authorityService.findAll(pageable); + List authorityDtoList = authorities.stream().map(AuthorityDto::new).toList(); + return ResponseEntity.ok(new PageImpl<>(authorityDtoList, pageable, authorities.getTotalElements())); + } + + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/{name}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public ResponseEntity getOne(@PathVariable("name") String name) { + Optional authority = authorityService.findOptionalByName(name); + if (authority.isPresent()) { + return ResponseEntity.ok(new AuthorityDto(authority.get())); + } else { + log.error("Cannot find authority with name [{}].", name); + return null; + } + } + + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/scope/{scope}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public ResponseEntity> getAllByScope(@PathVariable("scope") String scope, Authentication auth) { + List authorities = authorityService.findByScope(scope); + List authorityDtoList = authorities.stream().map(AuthorityDto::new).toList(); + return ResponseEntity.ok(authorityDtoList); + } +} 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..0b75e886fcf 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 @@ -8,6 +8,7 @@ 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.objects.annotations.Authorize; 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 +55,8 @@ public class UserController { private final RealmService realmService; private final UserFactory userFactory; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_CREATE") @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"), @@ -172,44 +174,8 @@ 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')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "ROLE_ASSIGN_TO_USER") @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 +197,6 @@ 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')") @Operation(summary = "Get all authorities of the system", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @@ -267,7 +210,8 @@ public ResponseEntity> getAllAuthorities() { return ResponseEntity.ok(authorityService.findAll(Pageable.unpaged()).stream().toList()); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "AUTHORITY_ASSIGN_TO_USER") @Operation(summary = "Assign authority to the user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 9adfedd1c82..ecea814f81b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -47,6 +47,21 @@ public class CacheConfigurationProperties { */ private String loadedModules = "loadedModules"; + /** + * Default cache name for caching default user authorities. + */ + private String defaultUserAuthoritiesCache = "defaultUserAuthoritiesCache"; + + /** + * Default cache name for caching default anonymous authorities. + */ + private String defaultAnonymousAuthoritiesCache = "defaultAnonymousAuthoritiesCache"; + + /** + * Default cache name for caching default admin authorities. + */ + private String defaultAdminAuthoritiesCache = "defaultAdminAuthoritiesCache"; + /** * A list of additional custom cache names. * Allows users to define their own cache names for specific use cases. @@ -87,7 +102,7 @@ public class CacheConfigurationProperties { */ public Set getAllCaches() { Set caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetDefault, - petriNetLatest, petriNetCache, loadedModules)); + petriNetLatest, petriNetCache, loadedModules, defaultUserAuthoritiesCache, defaultAnonymousAuthoritiesCache, defaultAdminAuthoritiesCache)); caches.addAll(additional); return caches; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java index 6a4e9235999..c526eebed3d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java @@ -3,6 +3,7 @@ import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties; import com.netgrif.application.engine.elastic.service.interfaces.IElasticIndexService; import com.netgrif.application.engine.elastic.web.requestbodies.IndexParams; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.service.ReindexingTask; import com.netgrif.application.engine.workflow.service.CaseSearchService; @@ -79,7 +80,8 @@ public void setIndexService(IElasticIndexService indexService) { this.indexService = indexService; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "ELASTIC_REINDEX") @Operation(summary = "Reindex specified cases", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -117,7 +119,8 @@ public MessageResource reindex(@RequestBody Map searchBody, Auth } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "ELASTIC_REINDEX") @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/manager/web/SessionManagerController.java b/application-engine/src/main/java/com/netgrif/application/engine/manager/web/SessionManagerController.java index fc8f0a13435..d6dcdc55f53 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/manager/web/SessionManagerController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/manager/web/SessionManagerController.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.manager.web; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.manager.service.interfaces.ISessionManagerService; import com.netgrif.application.engine.manager.web.body.request.LogoutRequest; @@ -33,7 +34,7 @@ public class SessionManagerController { @Autowired private ISessionManagerService sessionManagerService; - @PreAuthorize("hasRole('ADMIN')") + @Authorize(authority = "ADMIN") @Operation(summary = "Get All logged users", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -47,7 +48,7 @@ public AllLoggedUsersResponse getAllSessions() { return new AllLoggedUsersResponse(loggedUsers); } - @PreAuthorize("hasRole('ADMIN')") + @Authorize(authority = "ADMIN") @Operation(summary = "Logout current user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -62,7 +63,7 @@ public MessageLogoutResponse logoutCurrentSession(@RequestBody LogoutRequest req return new MessageLogoutResponse(true); } - @PreAuthorize("hasRole('ADMIN')") + @Authorize(authority = "ADMIN") @Operation(summary = "Logout all user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) 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 index 5fca05b1095..291e38f6567 100644 --- 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 @@ -1,6 +1,7 @@ package com.netgrif.application.engine.orgstructure.web; import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.objects.annotations.Authorize; 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; @@ -37,7 +38,8 @@ public GroupController(GroupService service) { this.service = service; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_VIEW") @Operation(summary = "Get all groups in the system", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) 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 55ed5a35bbd..683249009fb 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 @@ -177,7 +177,6 @@ public List get(List petriNetIds) { } @Override - @Transactional public ImportPetriNetEventOutcome importPetriNet(ImportPetriNetParams importPetriNetParams) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException { validateAttributes(importPetriNetParams); 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..3adccbfdca6 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 @@ -5,6 +5,7 @@ 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.*; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.*; import com.netgrif.application.engine.objects.event.events.user.UserRoleChangeEvent; import com.netgrif.application.engine.objects.importer.model.EventPhaseType; 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..6494300feb4 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 @@ -5,6 +5,7 @@ import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetService; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; import com.netgrif.application.engine.importer.service.Importer; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; @@ -95,7 +96,8 @@ public static String decodeUrl(String s1) { } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_UPLOAD") @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")}) @@ -126,6 +128,8 @@ public EntityModel importPetriNet( } } + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_VIEW") @Operation(summary = "Get all processes", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(produces = MediaTypes.HAL_JSON_VALUE) public ResponseEntity> getAll(@RequestParam(value = "indentifier", required = false) String identifier, @RequestParam(value = "version", required = false) String version, Pageable pageable, Authentication auth, Locale locale) { @@ -182,7 +186,8 @@ public TransactionsResource getTransactions(@PathVariable("netId") String netId, return new TransactionsResource(net.getTransactions().values(), netId, locale); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_DOWNLOAD") @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) { @@ -220,7 +225,9 @@ PagedModel searchElasticPetriNets(@RequestBody PetriN return resources; } - @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_DELETE") + @Authorize(expression = "@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") @Operation(summary = "Delete process", description = "Caller must have the ADMIN role. Removes the specified process, along with it's cases, tasks and process roles.", security = {@SecurityRequirement(name = "BasicAuth")}) 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..6484b30dec1 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 @@ -5,6 +5,7 @@ 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.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -34,7 +35,7 @@ public class ProcessRoleController { private final ProcessRoleService processRoleService; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(expression = "@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") diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AuthorityRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AuthorityRunner.java index 4dd4369cbb2..f25555fe489 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AuthorityRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AuthorityRunner.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.startup.runner; -import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.auth.config.AuthorityConfigurationProperties; +import com.netgrif.application.engine.objects.auth.constants.AuthorizingObject; import com.netgrif.application.engine.auth.service.AuthorityService; import com.netgrif.application.engine.startup.ApplicationEngineStartupRunner; import com.netgrif.application.engine.startup.annotation.RunnerOrder; @@ -9,6 +10,8 @@ import org.springframework.boot.ApplicationArguments; import org.springframework.stereotype.Component; +import java.util.List; + @Slf4j @Component @RunnerOrder(60) @@ -17,12 +20,16 @@ public class AuthorityRunner implements ApplicationEngineStartupRunner { private final AuthorityService service; + private final AuthorityConfigurationProperties authorityConfigurationProperties; + @Override public void run(ApplicationArguments args) throws Exception { - service.getOrCreate(Authority.user); - service.getOrCreate(Authority.admin); - service.getOrCreate(Authority.systemAdmin); - service.getOrCreate(Authority.anonymous); + createAll(); } + void createAll() { + List.of(AuthorizingObject.values()).forEach(authority -> service.getOrCreate(authority.name())); + authorityConfigurationProperties.getAdditionalAuthorizingObjects().forEach(service::getOrCreate); + log.info("Authorities created."); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java index 0a3d0d12ffb..9f6b30efbaa 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java @@ -19,7 +19,7 @@ public class TaskAuthorizationService extends AbstractAuthorizationService imple private ITaskService taskService; @Override - public Boolean userHasAtLeastOneRolePermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { + public Boolean userHasAtLeastOneRolePermission(AbstractUser loggedUser, String taskId, RolePermission... permissions) { return userHasAtLeastOneRolePermission(loggedUser, taskService.findById(taskId), permissions); } @@ -40,7 +40,7 @@ public Boolean userHasAtLeastOneRolePermission(AbstractUser user, Task task, Rol } @Override - public Boolean userHasUserListPermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { + public Boolean userHasUserListPermission(AbstractUser loggedUser, String taskId, RolePermission... permissions) { return userHasUserListPermission(loggedUser, taskService.findById(taskId), permissions); } @@ -65,11 +65,6 @@ public Boolean userHasUserListPermission(AbstractUser user, Task task, RolePermi return checkPermissions(userPermissions, Arrays.stream(permissions).map(RolePermission::toString).toList()); } - @Override - public boolean isAssignee(LoggedUser loggedUser, String taskId) { - return isAssignee(loggedUser, taskService.findById(taskId)); - } - @Override public boolean isAssignee(AbstractUser user, String taskId) { return isAssignee(user, taskService.findById(taskId)); @@ -94,7 +89,7 @@ private boolean isAssigned(Task task) { } @Override - public boolean canCallAssign(LoggedUser loggedUser, String taskId) { + public boolean canCallAssign(AbstractUser loggedUser, String taskId) { // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() if (loggedUser.isAdmin()) { return true; @@ -113,7 +108,7 @@ public boolean canCallAssign(LoggedUser loggedUser, String taskId) { } @Override - public boolean canCallDelegate(LoggedUser loggedUser, String taskId) { + public boolean canCallDelegate(AbstractUser loggedUser, String taskId) { // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() if (loggedUser.isAdmin()) { return true; @@ -132,7 +127,7 @@ public boolean canCallDelegate(LoggedUser loggedUser, String taskId) { } @Override - public boolean canCallFinish(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException { + public boolean canCallFinish(AbstractUser loggedUser, String taskId) throws IllegalTaskStateException { if (!isAssigned(taskId)) { throw new IllegalTaskStateException("Task with ID '%s' cannot be finished, because it is not assigned!".formatted(taskId)); } @@ -166,7 +161,7 @@ private boolean canAssignedCancel(Task task) { } @Override - public boolean canCallCancel(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException { + public boolean canCallCancel(AbstractUser loggedUser, String taskId) throws IllegalTaskStateException { if (!isAssigned(taskId)) { throw new IllegalTaskStateException("Task with ID '%s' cannot be canceled, because it is not assigned!".formatted(taskId)); } @@ -193,13 +188,13 @@ public boolean canCallCancel(LoggedUser loggedUser, String taskId) throws Illega } @Override - public boolean canCallSaveData(LoggedUser loggedUser, String taskId) { + public boolean canCallSaveData(AbstractUser loggedUser, String taskId) { // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); } @Override - public boolean canCallSaveFile(LoggedUser loggedUser, String taskId) { + public boolean canCallSaveFile(AbstractUser loggedUser, String taskId) { // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 11c1f532c3d..96ea7dd6673 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -312,6 +312,7 @@ private boolean actorExists(ActorFieldValue actorFieldValue) { } } + @Override public CreateCaseEventOutcome createCase(CreateCaseParams createCaseParams) { fillAndValidateAttributes(createCaseParams); PetriNet petriNet = createCaseParams.getProcess(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java index 99b0c8e56b9..5c4e6ba895a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java @@ -1,36 +1,33 @@ package com.netgrif.application.engine.workflow.service.interfaces; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.petrinet.domain.roles.RolePermission; import com.netgrif.application.engine.petrinet.domain.throwable.IllegalTaskStateException; import com.netgrif.application.engine.objects.workflow.domain.Task; public interface ITaskAuthorizationService { - Boolean userHasAtLeastOneRolePermission(LoggedUser loggedUser, String taskId, RolePermission... permissions); + Boolean userHasAtLeastOneRolePermission(AbstractUser loggedUser, String taskId, RolePermission... permissions); Boolean userHasAtLeastOneRolePermission(AbstractUser user, Task task, RolePermission... permissions); - Boolean userHasUserListPermission(LoggedUser loggedUser, String taskId, RolePermission... permissions); + Boolean userHasUserListPermission(AbstractUser loggedUser, String taskId, RolePermission... permissions); Boolean userHasUserListPermission(AbstractUser user, Task task, RolePermission... permissions); - boolean isAssignee(LoggedUser loggedUser, String taskId); - boolean isAssignee(AbstractUser user, String taskId); boolean isAssignee(AbstractUser user, Task task); - boolean canCallAssign(LoggedUser loggedUser, String taskId); + boolean canCallAssign(AbstractUser loggedUser, String taskId); - boolean canCallDelegate(LoggedUser loggedUser, String taskId); + boolean canCallDelegate(AbstractUser loggedUser, String taskId); - boolean canCallFinish(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException; + boolean canCallFinish(AbstractUser loggedUser, String taskId) throws IllegalTaskStateException; - boolean canCallCancel(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException; + boolean canCallCancel(AbstractUser loggedUser, String taskId) throws IllegalTaskStateException; - boolean canCallSaveData(LoggedUser loggedUser, String taskId); + boolean canCallSaveData(AbstractUser loggedUser, String taskId); - boolean canCallSaveFile(LoggedUser loggedUser, String taskId); + boolean canCallSaveFile(AbstractUser loggedUser, String taskId); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java index 59f2bdb45bc..44d7b25734c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.workflow.web; +import com.netgrif.application.engine.objects.annotations.Authorize; import tools.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -70,7 +71,7 @@ public List getTasksOfCase(@PathVariable("id") String caseId, Loc return this.taskService.findAllByCase(caseId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallAssign(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallAssign(@userService.getAnonymousLogged(), #taskId)") @GetMapping(value = "/assign/{id}", produces = MediaTypes.HAL_JSON_VALUE) @Operation(summary = "Assign task", description = "Caller must be able to perform the task, or must be an ADMIN") @ApiResponses({@ApiResponse( @@ -85,7 +86,7 @@ public EntityModel assign(@PathVariable("id") String ta return super.assign(loggedUser, taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallFinish(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallFinish(@userService.getAnonymousLogged(), #taskId)") @GetMapping(value = "/finish/{id}", produces = MediaTypes.HAL_JSON_VALUE) @Operation(summary = "Finish task", description = "Caller must be assigned to the task, or must be an ADMIN") @ApiResponses({@ApiResponse( @@ -100,7 +101,7 @@ public EntityModel finish(@PathVariable("id") String ta return super.finish(loggedUser, taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallCancel(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallCancel(@userService.getAnonymousLogged(), #taskId)") @GetMapping(value = "/cancel/{id}", produces = MediaTypes.HAL_JSON_VALUE) @Operation(summary = "Cancel task", description = "Caller must be assigned to the task, or must be an ADMIN") @ApiResponses({@ApiResponse( @@ -123,7 +124,7 @@ public EntityModel getData(@PathVariable("id") String t } @Override - @PreAuthorize("@taskAuthorizationService.canCallSaveData(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveData(@userService.getAnonymousLogged(), #taskId)") @PostMapping(value = "/{id}/data", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8") @Operation(summary = "Set task data", description = "Caller must be assigned to the task, or must be an ADMIN") @ApiResponses({@ApiResponse( @@ -137,7 +138,7 @@ public EntityModel setData(@PathVariable("id") String t return super.setData(taskId, dataBody, locale); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") @Operation(summary = "Upload file into the task", description = "Caller must be assigned to the task, or must be an ADMIN") @PostMapping(value = "/{id}/file", produces = MediaTypes.HAL_JSON_VALUE) @@ -156,7 +157,7 @@ public ResponseEntity getFile(@PathVariable("id") String taskId, @Requ return super.getFile(taskId, fieldId); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") @Operation(summary = "Remove file from the task", description = "Caller must be assigned to the task, or must be an ADMIN") @DeleteMapping(value = "/{id}/file", produces = MediaTypes.HAL_JSON_VALUE) @@ -176,7 +177,7 @@ public ResponseEntity getFilePreview(@PathVariable("id") String taskId } @Override - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") @Operation(summary = "Upload multiple files into the task", description = "Caller must be assigned to the task, or must be an ADMIN") @PostMapping(value = "/{id}/files", produces = MediaTypes.HAL_JSON_VALUE) @@ -195,7 +196,7 @@ public ResponseEntity getNamedFile(@PathVariable("id") String taskId, return super.getNamedFile(taskId, fieldId, fileName); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(@userService.getAnonymousLogged(), #taskId)") @Operation(summary = "Remove file from tasks file list field value", description = "Caller must be assigned to the task, or must be an ADMIN") @DeleteMapping(value = "/{id}/file/named", produces = MediaTypes.HAL_JSON_VALUE) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java index f25d6a3a7ed..ff8a82b6436 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.workflow.web; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.UserService; @@ -44,7 +45,7 @@ public PublicWorkflowController(IWorkflowService workflowService, UserService us this.workflowService = workflowService; } - @PreAuthorize("@workflowAuthorizationService.canCallCreate(@userService.getAnonymousLogged(), #body.netId)") + @Authorize(expression = "@workflowAuthorizationService.canCallCreate(@userService.getAnonymousLogged(), #body.netId)") @PostMapping(value = "/case", consumes = "application/json;charset=UTF-8", produces = MediaTypes.HAL_JSON_VALUE) @Operation(summary = "Create new case") public EntityModel createCase(@RequestBody CreateCaseBody body, Locale locale) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java index 2bd0ee6a23b..d49bb56b969 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.workflow.web; +import com.netgrif.application.engine.objects.annotations.Authorize; import tools.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -89,7 +90,7 @@ public LocalisedTaskResource getOne(@PathVariable("id") String taskId, Locale lo return super.getOne(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallAssign(#auth.getPrincipal(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallAssign(#auth.getPrincipal(), #taskId)") @Operation(summary = "Assign task", description = "Caller must be able to perform the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -103,7 +104,7 @@ public EntityModel assign(Authentication auth, @PathVar return super.assign(loggedUser, taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallDelegate(#auth.getPrincipal(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallDelegate(#auth.getPrincipal(), #taskId)") @Operation(summary = "Delegate task", description = "Caller must be able to delegate the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -117,7 +118,7 @@ public EntityModel delegate(Authentication auth, @PathV return super.delegate(loggedUser, taskId, delegatedId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallFinish(#auth.getPrincipal(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallFinish(#auth.getPrincipal(), #taskId)") @Operation(summary = "Finish task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -131,7 +132,7 @@ public EntityModel finish(Authentication auth, @PathVar return super.finish(loggedUser, taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallCancel(#auth.getPrincipal(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallCancel(#auth.getPrincipal(), #taskId)") @Operation(summary = "Cancel task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -187,7 +188,7 @@ public EntityModel getData(@PathVariable("id") String t return super.getData(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallSaveData(#auth.getPrincipal(), #taskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveData(#auth.getPrincipal(), #taskId)") @Operation(summary = "Set task data", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -201,7 +202,7 @@ public EntityModel setData(Authentication auth, @PathVa return super.setData(taskId, dataBody, locale); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #dataBody.parentTaskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #dataBody.parentTaskId)") @Operation(summary = "Upload file into the task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -220,7 +221,7 @@ public ResponseEntity getFile(@PathVariable("id") String taskId, @Requ return super.getFile(taskId, fieldId); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") @Operation(summary = "Remove file from the task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -233,7 +234,7 @@ public EntityModel deleteFile(Authentication auth, @Pat return super.deleteFile(requestBody.getParentTaskId(), requestBody.getFieldId()); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") @Operation(summary = "Upload multiple files into the task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -252,7 +253,7 @@ public ResponseEntity getNamedFile(@PathVariable("id") String taskId, return super.getNamedFile(taskId, fieldId, fileName); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @Authorize(expression = "@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") @Operation(summary = "Remove file from tasks file list field value", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) 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..f9fdf20adc2 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 @@ -1,5 +1,6 @@ package com.netgrif.application.engine.workflow.web; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.elastic.web.requestbodies.singleaslist.SingleCaseSearchRequestAsList; @@ -80,7 +81,7 @@ public class WorkflowController { private IDataService dataService; - @PreAuthorize("@workflowAuthorizationService.canCallCreate(#auth.getPrincipal(), #body.netId)") + @Authorize(expression = "@workflowAuthorizationService.canCallCreate(#auth.getPrincipal(), #body.netId)") @Operation(summary = "Create new case", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public EntityModel createCase(@RequestBody CreateCaseBody body, Authentication auth, Locale locale) { @@ -188,7 +189,7 @@ public PagedModel findAllByAuthor(@PathVariable("id") String autho return resources; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = "ADMIN") @Operation(summary = "Reload tasks of case", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -224,7 +225,7 @@ public MessageResource reloadTasks(@PathVariable("id") String caseId) { // } // } - @PreAuthorize("@workflowAuthorizationService.canCallDelete(#auth.getPrincipal(), #caseId)") + @Authorize(expression = "@workflowAuthorizationService.canCallDelete(#auth.getPrincipal(), #caseId)") @Operation(summary = "Delete case", security = {@SecurityRequirement(name = "BasicAuth")}) @DeleteMapping(value = "/case/{id}", produces = MediaTypes.HAL_JSON_VALUE) public EntityModel deleteCase(Authentication auth, @PathVariable("id") String caseId, @RequestParam(defaultValue = "false") boolean deleteSubtree) { diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy index 6e26c217b21..c9edf3a7185 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy @@ -8,6 +8,7 @@ import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.elastic.domain.ElasticCaseRepository import com.netgrif.application.engine.elastic.domain.ElasticTaskRepository import com.netgrif.application.engine.elastic.service.ElasticIndexService +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer import com.netgrif.application.engine.petrinet.domain.repository.UriNodeRepository import com.netgrif.application.engine.petrinet.domain.roles.ProcessRoleRepository import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService @@ -15,6 +16,8 @@ import com.netgrif.application.engine.startup.runner.* import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService import org.springframework.beans.factory.annotation.Autowired import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.stereotype.Component @Component @@ -113,6 +116,12 @@ class TestHelper { } } + void setAuthentication() { + def user = userService.system + def auth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(user), user) + SecurityContextHolder.getContext().setAuthentication(auth) + } + private void clearMongoCollections() { int attempts = 0 while (true) { diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy new file mode 100644 index 00000000000..597bfb8c351 --- /dev/null +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy @@ -0,0 +1,114 @@ +package com.netgrif.application.engine.action + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.objects.workflow.domain.Case +import com.netgrif.application.engine.objects.workflow.domain.Task +import com.netgrif.application.engine.startup.ImportHelper +import com.netgrif.application.engine.workflow.service.interfaces.IDataService +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService +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.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import static org.junit.jupiter.api.Assertions.assertNotNull +import static org.junit.jupiter.api.Assertions.assertTrue + +@SpringBootTest +@ActiveProfiles(["test"]) +@ExtendWith(SpringExtension.class) +class ServiceMethodAuthorizationTest { + + @Autowired + private TestHelper testHelper + + @Autowired + private ImportHelper importHelper + + @Autowired + private IDataService dataService + + @Autowired + private ITaskService taskService + + @Test + void testAllServiceButtonsPostSetActions() { + testHelper.truncateDbs() + + def netOptional = importHelper.createNet("service_methods_test.xml") + assertTrue(netOptional.isPresent()) + def net = netOptional.get() + + Case useCase = importHelper.createCase("Service Test Case", net) + assertNotNull(useCase) + + Task task = taskService.findOne(useCase.tasks.first().task) + assertNotNull(task) + + // Trigger set on each button field + net.dataSet.values().findAll { it.type.name().equalsIgnoreCase("BUTTON") }.each { buttonField -> + def outcome = dataService.setData(task.stringId, ImportHelper.populateDataset([ + (buttonField.stringId): [ + "type": "button" + ] + ])) + assertNotNull(outcome) + } + } + + @Test + void testAllAuthorityServiceButtonsPostSetActions() { + testHelper.truncateDbs() + + def netOptional = importHelper.createNet("authority_service_methods_test.xml") + assertTrue(netOptional.isPresent()) + def net = netOptional.get() + + Case useCase = importHelper.createCase("Authority Service Test Case", net) + assertNotNull(useCase) + + Task task = taskService.findOne(useCase.tasks.first().task) + assertNotNull(task) + + // Trigger set on each button field + net.dataSet.values().findAll { it.type.name().equalsIgnoreCase("BUTTON") }.each { buttonField -> + def outcome = dataService.setData(task.stringId, ImportHelper.populateDataset([ + (buttonField.stringId): [ + "type": "button" + ] + ])) + assertNotNull(outcome) + } + } + + @Test + void testAllAuthorityApiButtonsPostSetActions() { + executeAllButtonsOfNet("authority_api_methods_test.xml", "Authority API Test Case") + } + + private void executeAllButtonsOfNet(String netFileName, String caseTitle) { + testHelper.truncateDbs() + + def netOptional = importHelper.createNet(netFileName) + assertTrue(netOptional.isPresent()) + def net = netOptional.get() + + Case useCase = importHelper.createCase(caseTitle, net) + assertNotNull(useCase) + + Task task = taskService.findOne(useCase.tasks.first().task) + assertNotNull(task) + + // Trigger set on each button field + net.dataSet.values().findAll { it.type.name().equalsIgnoreCase("BUTTON") }.each { buttonField -> + def outcome = dataService.setData(task.stringId, ImportHelper.populateDataset([ + (buttonField.stringId): [ + "type": "button" + ] + ])) + assertNotNull(outcome) + } + } +} diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/auth/AuthorityServiceTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/AuthorityServiceTest.groovy new file mode 100644 index 00000000000..0b55274d058 --- /dev/null +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/auth/AuthorityServiceTest.groovy @@ -0,0 +1,376 @@ +package com.netgrif.application.engine.auth + +import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.auth.config.AuthorityConfigurationProperties +import com.netgrif.application.engine.auth.repository.AuthorityRepository +import com.netgrif.application.engine.auth.service.AuthorityService +import com.netgrif.application.engine.objects.auth.domain.Authority +import com.netgrif.application.engine.objects.auth.dto.AuthoritySearchDto +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.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cache.CacheManager +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension + +import static org.junit.jupiter.api.Assertions.* + +@ExtendWith(SpringExtension.class) +@ActiveProfiles(["test"]) +@SpringBootTest +class AuthorityServiceTest { + + @Autowired + private TestHelper testHelper + + @Autowired + private AuthorityService authorityService + + @Autowired + private AuthorityRepository authorityRepository + + @Autowired + private AuthorityConfigurationProperties authorityConfigurationProperties + + @Autowired + private CacheManager cacheManager + + @BeforeEach + void init() { + testHelper.truncateDbs() + cacheManager.cacheNames.each { name -> + cacheManager.getCache(name)?.clear() + } + } + + @Test + void testGetOrCreate_WhenAuthorityDoesNotExist_CreatesAndReturnsNew() { + long initialCount = authorityRepository.count() + + Authority created = authorityService.getOrCreate("ROLE_TEST_CREATE") + + assertNotNull(created) + assertEquals("ROLE_TEST_CREATE", created.getName()) + assertEquals("ROLE_TEST_CREATE", created.getAuthority()) + assertNotNull(created.get_id()) + assertNotNull(created.getStringId()) + assertEquals(initialCount + 1, authorityRepository.count()) + + Optional found = authorityRepository.findByName("ROLE_TEST_CREATE") + assertTrue(found.isPresent()) + assertEquals(created.get_id(), found.get().get_id()) + } + + @Test + void testGetOrCreate_WhenAuthorityExists_ReturnsExistingWithoutDuplicate() { + Authority first = authorityService.getOrCreate("ROLE_DUPLICATE_CHECK") + long countAfterFirst = authorityRepository.count() + + Authority second = authorityService.getOrCreate("ROLE_DUPLICATE_CHECK") + + assertNotNull(second) + assertEquals(first.get_id(), second.get_id()) + assertEquals("ROLE_DUPLICATE_CHECK", second.getName()) + assertEquals(countAfterFirst, authorityRepository.count()) + } + + @Test + void testGetOne_WhenExists_ReturnsAuthority() { + Authority created = authorityService.getOrCreate("ROLE_GET_ONE") + + Authority fetched = authorityService.getOne(created.getStringId()) + + assertNotNull(fetched) + assertEquals(created.get_id(), fetched.get_id()) + assertEquals("ROLE_GET_ONE", fetched.getName()) + } + + @Test + void testGetOne_WhenDoesNotExist_ThrowsIllegalArgumentException() { + String nonExistingId = new ObjectId().toString() + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.getOne(nonExistingId) + }) + + assertTrue(exception.getMessage().contains("Authority with id " + nonExistingId + " not found")) + } + + @Test + void testFindAll_WithPagination() { + long initialCount = authorityRepository.count() + (1..5).each { i -> + authorityService.getOrCreate("ROLE_PAGED_${i}") + } + long totalExpected = initialCount + 5 + + Page page1 = authorityService.findAll(PageRequest.of(0, 2)) + assertEquals(2, page1.getContent().size()) + assertEquals(totalExpected, page1.getTotalElements()) + + Page page2 = authorityService.findAll(PageRequest.of(1, 2)) + assertEquals(2, page2.getContent().size()) + assertNotEquals(page1.getContent().get(0).getName(), page2.getContent().get(0).getName()) + + Page unpaged = authorityService.findAll(Pageable.unpaged()) + assertEquals(totalExpected, unpaged.getContent().size()) + } + + @Test + void testFindAllByIds() { + Authority auth1 = authorityService.getOrCreate("ROLE_ID_1") + Authority auth2 = authorityService.getOrCreate("ROLE_ID_2") + authorityService.getOrCreate("ROLE_ID_3") + + Page result = authorityService.findAllByIds([auth1.getStringId(), auth2.getStringId()], PageRequest.of(0, 10)) + + assertEquals(2, result.getTotalElements()) + Set names = result.getContent().collect { it.getName() } as Set + assertTrue(names.contains("ROLE_ID_1")) + assertTrue(names.contains("ROLE_ID_2")) + assertFalse(names.contains("ROLE_ID_3")) + + Page emptyResult = authorityService.findAllByIds([new ObjectId().toString()], PageRequest.of(0, 10)) + assertEquals(0, emptyResult.getTotalElements()) + } + + @Test + void testSearch_WithFullText() { + authorityService.getOrCreate("PROCESS_CREATE") + authorityService.getOrCreate("PROCESS_DELETE") + authorityService.getOrCreate("USER_CREATE") + authorityService.getOrCreate("OTHER_PERMISSION") + + AuthoritySearchDto searchDto = new AuthoritySearchDto() + searchDto.setFullText("process") + + Page searchResult = authorityService.search(searchDto, PageRequest.of(0, 10)) + assertEquals(2, searchResult.getTotalElements()) + assertTrue(searchResult.getContent().every { it.getName().startsWith("PROCESS_") }) + + searchDto.setFullText("create") + Page createResult = authorityService.search(searchDto, PageRequest.of(0, 10)) + assertEquals(2, createResult.getTotalElements()) + Set createNames = createResult.getContent().collect { it.getName() } as Set + assertTrue(createNames.contains("PROCESS_CREATE")) + assertTrue(createNames.contains("USER_CREATE")) + } + + @Test + void testSearch_WithEmptyOrNullFullText() { + long initialCount = authorityRepository.count() + authorityService.getOrCreate("AUTH_1") + authorityService.getOrCreate("AUTH_2") + long totalExpected = initialCount + 2 + + AuthoritySearchDto emptyDto = new AuthoritySearchDto() + emptyDto.setFullText("") + Page emptyResult = authorityService.search(emptyDto, PageRequest.of(0, 100)) + assertEquals(totalExpected, emptyResult.getTotalElements()) + + AuthoritySearchDto blankDto = new AuthoritySearchDto() + blankDto.setFullText(" ") + Page blankResult = authorityService.search(blankDto, PageRequest.of(0, 100)) + assertEquals(totalExpected, blankResult.getTotalElements()) + + AuthoritySearchDto nullDto = new AuthoritySearchDto() + nullDto.setFullText(null) + Page nullResult = authorityService.search(nullDto, PageRequest.of(0, 100)) + assertEquals(totalExpected, nullResult.getTotalElements()) + } + + @Test + void testDelete_ExistingAuthority() { + authorityService.getOrCreate("ROLE_TO_DELETE") + assertTrue(authorityService.findOptionalByName("ROLE_TO_DELETE").isPresent()) + + authorityService.delete("ROLE_TO_DELETE") + + assertTrue(authorityService.findOptionalByName("ROLE_TO_DELETE").isEmpty()) + } + + @Test + void testDelete_NonExistingAuthority_DoesNotThrow() { + authorityService.delete("ROLE_DOES_NOT_EXIST") + } + + @Test + void testDelete_ScopedAuthorityName_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.delete("PROCESS_*") + }) + assertEquals("The authority name is not valid. Scope is suitable for this function.", exception.getMessage()) + } + + @Test + void testDelete_InvalidScopeFormat_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.delete("PRO*CESS") + }) + assertEquals("The authority name or scope is not valid.", exception.getMessage()) + } + + @Test + void testFindByName_WhenExists_ReturnsAuthority() { + authorityService.getOrCreate("ROLE_FIND_BY_NAME") + + Authority found = authorityService.findByName("ROLE_FIND_BY_NAME") + + assertNotNull(found) + assertEquals("ROLE_FIND_BY_NAME", found.getName()) + } + + @Test + void testFindByName_WhenNotFound_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.findByName("NON_EXISTING_AUTH") + }) + assertEquals("Could not find authority with name [NON_EXISTING_AUTH]", exception.getMessage()) + } + + @Test + void testFindByName_ScopedName_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.findByName("ROLE_*") + }) + assertEquals("The authority name is not valid. Scope is suitable for this function.", exception.getMessage()) + } + + @Test + void testFindByName_InvalidScopeFormat_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.findByName("ROLE_*_ADMIN") + }) + assertEquals("The authority name or scope is not valid.", exception.getMessage()) + } + + @Test + void testFindOptionalByName() { + authorityService.getOrCreate("OPTIONAL_PRESENT") + + Optional present = authorityService.findOptionalByName("OPTIONAL_PRESENT") + assertTrue(present.isPresent()) + assertEquals("OPTIONAL_PRESENT", present.get().getName()) + + Optional missing = authorityService.findOptionalByName("OPTIONAL_MISSING") + assertTrue(missing.isEmpty()) + } + + @Test + void testFindByScope_WildcardAll() { + long initialCount = authorityRepository.count() + authorityService.getOrCreate("AUTH_A") + authorityService.getOrCreate("AUTH_B") + authorityService.getOrCreate("AUTH_C") + long totalExpected = initialCount + 3 + + List all = authorityService.findByScope("*") + + assertEquals(totalExpected, all.size()) + Set names = all.collect { it.getName() } as Set + assertTrue(names.containsAll(["AUTH_A", "AUTH_B", "AUTH_C"])) + } + + @Test + void testFindByScope_PrefixScope() { + authorityService.getOrCreate("TASK_READ") + authorityService.getOrCreate("TASK_WRITE") + authorityService.getOrCreate("TASK_DELETE") + authorityService.getOrCreate("USER_READ") + + List taskAuthorities = authorityService.findByScope("TASK_*") + + assertEquals(3, taskAuthorities.size()) + Set names = taskAuthorities.collect { it.getName() } as Set + assertTrue(names.containsAll(["TASK_READ", "TASK_WRITE", "TASK_DELETE"])) + assertFalse(names.contains("USER_READ")) + + List emptyScope = authorityService.findByScope("UNKNOWN_SCOPE_*") + assertTrue(emptyScope.isEmpty()) + } + + @Test + void testFindByScope_ExactAuthorityName() { + authorityService.getOrCreate("SINGLE_AUTH") + + List singleList = authorityService.findByScope("SINGLE_AUTH") + + assertEquals(1, singleList.size()) + assertEquals("SINGLE_AUTH", singleList.get(0).getName()) + } + + @Test + void testFindByScope_InvalidScopeFormat_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.findByScope("IN*VALID") + }) + assertEquals("The authority name or scope is not valid.", exception.getMessage()) + } + + @Test + void testFindByScope_NonExistentExactName_ThrowsIllegalArgumentException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, { + authorityService.findByScope("NON_EXISTENT_EXACT_NAME") + }) + assertEquals("Could not find authority with name [NON_EXISTENT_EXACT_NAME]", exception.getMessage()) + } + + @Test + void testGetDefaultUserAuthorities() { + authorityService.getOrCreate("USER_READ") + authorityService.getOrCreate("USER_WRITE_OWN") + authorityService.getOrCreate("USER_WRITE_ALL") + authorityService.getOrCreate("ADMIN_ALL") + + authorityConfigurationProperties.setDefaultUserAuthorities(["USER_READ", "USER_WRITE_*"]) + + Set defaultAuthorities = authorityService.getDefaultUserAuthorities() + + assertNotNull(defaultAuthorities) + assertEquals(3, defaultAuthorities.size()) + Set names = defaultAuthorities.collect { it.getName() } as Set + assertTrue(names.containsAll(["USER_READ", "USER_WRITE_OWN", "USER_WRITE_ALL"])) + assertFalse(names.contains("ADMIN_ALL")) + } + + @Test + void testGetDefaultAnonymousAuthorities() { + authorityService.getOrCreate("PUBLIC_VIEW") + authorityService.getOrCreate("PUBLIC_EXPORT") + authorityService.getOrCreate("INTERNAL_VIEW") + + authorityConfigurationProperties.setDefaultAnonymousAuthorities(["PUBLIC_*"]) + + Set defaultAuthorities = authorityService.getDefaultAnonymousAuthorities() + + assertNotNull(defaultAuthorities) + assertEquals(2, defaultAuthorities.size()) + Set names = defaultAuthorities.collect { it.getName() } as Set + assertTrue(names.containsAll(["PUBLIC_VIEW", "PUBLIC_EXPORT"])) + assertFalse(names.contains("INTERNAL_VIEW")) + } + + @Test + void testGetDefaultAdminAuthorities() { + long initialCount = authorityRepository.count() + authorityService.getOrCreate("ADMIN_DASHBOARD") + authorityService.getOrCreate("ADMIN_USERS") + authorityService.getOrCreate("CUSTOM_PERMISSION") + long totalExpected = initialCount + 3 + + authorityConfigurationProperties.setDefaultAdminAuthorities(["*"]) + + Set defaultAuthorities = authorityService.getDefaultAdminAuthorities() + + assertNotNull(defaultAuthorities) + assertEquals(totalExpected, defaultAuthorities.size()) + Set names = defaultAuthorities.collect { it.getName() } as Set + assertTrue(names.containsAll(["ADMIN_DASHBOARD", "ADMIN_USERS", "CUSTOM_PERMISSION"])) + } +} diff --git a/application-engine/src/test/resources/petriNets/authority_api_methods_test.xml b/application-engine/src/test/resources/petriNets/authority_api_methods_test.xml new file mode 100644 index 00000000000..c104d9e1190 --- /dev/null +++ b/application-engine/src/test/resources/petriNets/authority_api_methods_test.xml @@ -0,0 +1,263 @@ + + + authority_api_methods_test + 1.0.0 + AAMT + Authority Action API Method test + true + true + false + + + + + btn_find_all_authorities + + <placeholder>findAllAuthorities</placeholder> + <event type="set"> + <id>find_all_authorities_set</id> + <actions phase="post"> + <action> + findAllAuthorities(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_get_or_create_authority</id> + <title/> + <placeholder>getOrCreateAuthority</placeholder> + <event type="set"> + <id>get_or_create_authority_set</id> + <actions phase="post"> + <action> + getOrCreateAuthority("API_TEST_AUTHORITY"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_get_authority</id> + <title/> + <placeholder>getAuthority</placeholder> + <event type="set"> + <id>get_authority_set</id> + <actions phase="post"> + <action> + getAuthority(getOrCreateAuthority("API_TEST_AUTHORITY").stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_get_authority_by_name</id> + <title/> + <placeholder>getAuthorityByName</placeholder> + <event type="set"> + <id>get_authority_by_name_set</id> + <actions phase="post"> + <action> + getOrCreateAuthority("API_TEST_AUTHORITY"); + getAuthorityByName("API_TEST_AUTHORITY"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_find_authority_by_name</id> + <title/> + <placeholder>findAuthorityByName</placeholder> + <event type="set"> + <id>find_authority_by_name_set</id> + <actions phase="post"> + <action> + findAuthorityByName("API_TEST_MISSING_AUTHORITY"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_find_authorities_by_ids</id> + <title/> + <placeholder>findAuthoritiesByIds</placeholder> + <event type="set"> + <id>find_authorities_by_ids_set</id> + <actions phase="post"> + <action> + findAuthoritiesByIds([getOrCreateAuthority("API_TEST_AUTHORITY").stringId]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_find_authorities_by_scope</id> + <title/> + <placeholder>findAuthoritiesByScope</placeholder> + <event type="set"> + <id>find_authorities_by_scope_set</id> + <actions phase="post"> + <action> + findAuthoritiesByScope("*"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_search_authorities</id> + <title/> + <placeholder>searchAuthorities</placeholder> + <event type="set"> + <id>search_authorities_set</id> + <actions phase="post"> + <action> + searchAuthorities("API_TEST"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_delete_authority</id> + <title/> + <placeholder>deleteAuthority</placeholder> + <event type="set"> + <id>delete_authority_set</id> + <actions phase="post"> + <action> + getOrCreateAuthority("API_TEST_AUTHORITY_TO_DELETE"); + deleteAuthority("API_TEST_AUTHORITY_TO_DELETE"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_default_user_authorities</id> + <title/> + <placeholder>defaultUserAuthorities</placeholder> + <event type="set"> + <id>default_user_authorities_set</id> + <actions phase="post"> + <action> + defaultUserAuthorities(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_default_anonymous_authorities</id> + <title/> + <placeholder>defaultAnonymousAuthorities</placeholder> + <event type="set"> + <id>default_anonymous_authorities_set</id> + <actions phase="post"> + <action> + defaultAnonymousAuthorities(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_default_admin_authorities</id> + <title/> + <placeholder>defaultAdminAuthorities</placeholder> + <event type="set"> + <id>default_admin_authorities_set</id> + <actions phase="post"> + <action> + defaultAdminAuthorities(); + </action> + </actions> + </event> + </data> + + <!-- TRANSITIONS --> + <transition> + <id>t1</id> + <x>250</x> + <y>150</y> + <label>Execute Authority API Tests</label> + <dataRef> + <id>btn_find_all_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_get_or_create_authority</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_get_authority</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_get_authority_by_name</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_find_authority_by_name</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_find_authorities_by_ids</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_find_authorities_by_scope</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_search_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_delete_authority</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_default_user_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_default_anonymous_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_default_admin_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </transition> +</document> diff --git a/application-engine/src/test/resources/petriNets/authority_service_methods_test.xml b/application-engine/src/test/resources/petriNets/authority_service_methods_test.xml new file mode 100644 index 00000000000..117bdd94c64 --- /dev/null +++ b/application-engine/src/test/resources/petriNets/authority_service_methods_test.xml @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> + <id>authority_service_methods_test</id> + <version>1.0.0</version> + <initials>ASMT</initials> + <title>Authority Service Method test + true + true + false + + + + + btn_authority_service_find_all + + <placeholder>authorityService.findAll</placeholder> + <event type="set"> + <id>authority_service_find_all_set</id> + <actions phase="post"> + <action> + authorityService.findAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_get_or_create</id> + <title/> + <placeholder>authorityService.getOrCreate</placeholder> + <event type="set"> + <id>authority_service_get_or_create_set</id> + <actions phase="post"> + <action> + authorityService.getOrCreate("ROLE_USER"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_get_one</id> + <title/> + <placeholder>authorityService.getOne</placeholder> + <event type="set"> + <id>authority_service_get_one_set</id> + <actions phase="post"> + <action> + authorityService.getOne(authorityService.getOrCreate("ROLE_USER").stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_find_all_by_ids</id> + <title/> + <placeholder>authorityService.findAllByIds</placeholder> + <event type="set"> + <id>authority_service_find_all_by_ids_set</id> + <actions phase="post"> + <action> + authorityService.findAllByIds([authorityService.getOrCreate("ROLE_USER").stringId], org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_search</id> + <title/> + <placeholder>authorityService.search</placeholder> + <event type="set"> + <id>authority_service_search_set</id> + <actions phase="post"> + <action> + authorityService.search(new com.netgrif.application.engine.objects.auth.dto.AuthoritySearchDto(), org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_delete</id> + <title/> + <placeholder>authorityService.delete</placeholder> + <event type="set"> + <id>authority_service_delete_set</id> + <actions phase="post"> + <action> + authorityService.delete("ROLE_TO_DELETE"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_find_by_scope</id> + <title/> + <placeholder>authorityService.findByScope</placeholder> + <event type="set"> + <id>authority_service_find_by_scope_set</id> + <actions phase="post"> + <action> + authorityService.findByScope("*"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_find_by_name</id> + <title/> + <placeholder>authorityService.findByName</placeholder> + <event type="set"> + <id>authority_service_find_by_name_set</id> + <actions phase="post"> + <action> + authorityService.findByName(authorityService.getOrCreate("ROLE_USER").name); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_find_optional_by_name</id> + <title/> + <placeholder>authorityService.findOptionalByName</placeholder> + <event type="set"> + <id>authority_service_find_optional_by_name_set</id> + <actions phase="post"> + <action> + authorityService.findOptionalByName("ROLE_USER"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_get_default_user_authorities</id> + <title/> + <placeholder>authorityService.getDefaultUserAuthorities</placeholder> + <event type="set"> + <id>authority_service_get_default_user_authorities_set</id> + <actions phase="post"> + <action> + authorityService.getDefaultUserAuthorities(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_get_default_anonymous_authorities</id> + <title/> + <placeholder>authorityService.getDefaultAnonymousAuthorities</placeholder> + <event type="set"> + <id>authority_service_get_default_anonymous_authorities_set</id> + <actions phase="post"> + <action> + authorityService.getDefaultAnonymousAuthorities(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_authority_service_get_default_admin_authorities</id> + <title/> + <placeholder>authorityService.getDefaultAdminAuthorities</placeholder> + <event type="set"> + <id>authority_service_get_default_admin_authorities_set</id> + <actions phase="post"> + <action> + authorityService.getDefaultAdminAuthorities(); + </action> + </actions> + </event> + </data> + + <!-- TRANSITIONS --> + <transition> + <id>t1</id> + <x>250</x> + <y>150</y> + <label>Execute Service Tests</label> + <dataRef> + <id>btn_authority_service_find_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_get_or_create</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_get_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_find_all_by_ids</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_search</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_delete</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_find_by_scope</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_find_by_name</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_find_optional_by_name</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_get_default_user_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_get_default_anonymous_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_authority_service_get_default_admin_authorities</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </transition> +</document> diff --git a/application-engine/src/test/resources/petriNets/service_methods_test.xml b/application-engine/src/test/resources/petriNets/service_methods_test.xml new file mode 100644 index 00000000000..5100883a951 --- /dev/null +++ b/application-engine/src/test/resources/petriNets/service_methods_test.xml @@ -0,0 +1,794 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> + <id>service_methods_test</id> + <version>1.0.0</version> + <initials>SMT</initials> + <title>Service Method test + true + true + false + + + + + btn_task_service_find_one + + <placeholder>taskService.findOne</placeholder> + <event type="set"> + <id>task_service_find_one_set</id> + <actions phase="post"> + <action> + taskService.findOne(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_by_id</id> + <title/> + <placeholder>taskService.findById</placeholder> + <event type="set"> + <id>task_service_find_by_id_set</id> + <actions phase="post"> + <action> + taskService.findById(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_optional_by_id</id> + <title/> + <placeholder>taskService.findOptionalById</placeholder> + <event type="set"> + <id>task_service_find_optional_by_id_set</id> + <actions phase="post"> + <action> + taskService.findOptionalById(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_all_by_id</id> + <title/> + <placeholder>taskService.findAllById</placeholder> + <event type="set"> + <id>task_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + taskService.findAllById([useCase.tasks.first().task]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_all_by_case</id> + <title/> + <placeholder>taskService.findAllByCase</placeholder> + <event type="set"> + <id>task_service_find_all_by_case_set</id> + <actions phase="post"> + <action> + taskService.findAllByCase(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_save</id> + <title/> + <placeholder>taskService.save</placeholder> + <event type="set"> + <id>task_service_save_set</id> + <actions phase="post"> + <action> + taskService.save(taskService.findOne(useCase.tasks.first().task)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_reload_tasks</id> + <title/> + <placeholder>taskService.reloadTasks</placeholder> + <event type="set"> + <id>task_service_reload_tasks_set</id> + <actions phase="post"> + <action> + taskService.reloadTasks(useCase, true); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_search_all</id> + <title/> + <placeholder>taskService.searchAll</placeholder> + <event type="set"> + <id>task_service_search_all_set</id> + <actions phase="post"> + <action> + taskService.searchAll(com.netgrif.application.engine.adapter.spring.workflow.domain.QTask.task.transitionId.eq("t1")); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_search_one</id> + <title/> + <placeholder>taskService.searchOne</placeholder> + <event type="set"> + <id>task_service_search_one_set</id> + <actions phase="post"> + <action> + taskService.searchOne(com.netgrif.application.engine.adapter.spring.workflow.domain.QTask.task.transitionId.eq("t1")); + </action> + </actions> + </event> + </data> + + <!-- WorkflowService methods --> + <data type="button"> + <id>btn_workflow_service_find_one</id> + <title/> + <placeholder>workflowService.findOne</placeholder> + <event type="set"> + <id>workflow_service_find_one_set</id> + <actions phase="post"> + <action> + workflowService.findOne(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_find_one_no_net</id> + <title/> + <placeholder>workflowService.findOneNoNet</placeholder> + <event type="set"> + <id>workflow_service_find_one_no_net_set</id> + <actions phase="post"> + <action> + workflowService.findOneNoNet(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_find_all_by_id</id> + <title/> + <placeholder>workflowService.findAllById</placeholder> + <event type="set"> + <id>workflow_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + workflowService.findAllById([useCase.stringId]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_get_all</id> + <title/> + <placeholder>workflowService.getAll</placeholder> + <event type="set"> + <id>workflow_service_get_all_set</id> + <actions phase="post"> + <action> + workflowService.getAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_save</id> + <title/> + <placeholder>workflowService.save</placeholder> + <event type="set"> + <id>workflow_service_save_set</id> + <actions phase="post"> + <action> + workflowService.save(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_update_marking</id> + <title/> + <placeholder>workflowService.updateMarking</placeholder> + <event type="set"> + <id>workflow_service_update_marking_set</id> + <actions phase="post"> + <action> + workflowService.updateMarking(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_set_petri_net</id> + <title/> + <placeholder>workflowService.setPetriNet</placeholder> + <event type="set"> + <id>workflow_service_set_petri_net_set</id> + <actions phase="post"> + <action> + workflowService.setPetriNet(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_search_all</id> + <title/> + <placeholder>workflowService.searchAll</placeholder> + <event type="set"> + <id>workflow_service_search_all_set</id> + <actions phase="post"> + <action> + workflowService.searchAll(com.netgrif.application.engine.adapter.spring.workflow.domain.QCase.case$.processIdentifier.eq(useCase.processIdentifier)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_search_one</id> + <title/> + <placeholder>workflowService.searchOne</placeholder> + <event type="set"> + <id>workflow_service_search_one_set</id> + <actions phase="post"> + <action> + workflowService.searchOne(com.netgrif.application.engine.adapter.spring.workflow.domain.QCase.case$.processIdentifier.eq(useCase.processIdentifier)); + </action> + </actions> + </event> + </data> + + <!-- UserService methods --> + <data type="button"> + <id>btn_user_service_get_logged_or_system</id> + <title/> + <placeholder>userService.getLoggedOrSystem</placeholder> + <event type="set"> + <id>user_service_get_logged_or_system_set</id> + <actions phase="post"> + <action> + userService.getLoggedOrSystem(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_get_system</id> + <title/> + <placeholder>userService.getSystem</placeholder> + <event type="set"> + <id>user_service_get_system_set</id> + <actions phase="post"> + <action> + userService.getSystem(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_by_id</id> + <title/> + <placeholder>userService.findById</placeholder> + <event type="set"> + <id>user_service_find_by_id_set</id> + <actions phase="post"> + <action> + userService.findById(useCase.author.id, null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_by_email</id> + <title/> + <placeholder>userService.findByEmail</placeholder> + <event type="set"> + <id>user_service_find_by_email_set</id> + <actions phase="post"> + <action> + userService.findByEmail(userService.getLoggedOrSystem().getEmail(), null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_all</id> + <title/> + <placeholder>userService.findAll</placeholder> + <event type="set"> + <id>user_service_find_all_set</id> + <actions phase="post"> + <action> + userService.findAll(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_user_by_username</id> + <title/> + <placeholder>userService.findUserByUsername</placeholder> + <event type="set"> + <id>user_service_find_user_by_username_set</id> + <actions phase="post"> + <action> + userService.findUserByUsername(userService.getLoggedOrSystem().getUsername(), null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_save_user</id> + <title/> + <placeholder>userService.saveUser</placeholder> + <event type="set"> + <id>user_service_save_user_set</id> + <actions phase="post"> + <action> + userService.saveUser(userService.getLoggedOrSystem()); + </action> + </actions> + </event> + </data> + + <!-- GroupService methods --> + <data type="button"> + <id>btn_group_service_find_by_id</id> + <title/> + <placeholder>groupService.findById</placeholder> + <event type="set"> + <id>group_service_find_by_id_set</id> + <actions phase="post"> + <action> + groupService.findById(groupService.getDefaultUserGroup(userService.getLoggedOrSystem()).stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_by_predicate</id> + <title/> + <placeholder>groupService.findByPredicate</placeholder> + <event type="set"> + <id>group_service_find_by_predicate_set</id> + <actions phase="post"> + <action> + groupService.findByPredicate(new com.netgrif.application.engine.objects.auth.domain.QGroup("group").identifier.eq("default"), org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_by_identifier</id> + <title/> + <placeholder>groupService.findByIdentifier</placeholder> + <event type="set"> + <id>group_service_find_by_identifier_set</id> + <actions phase="post"> + <action> + groupService.findByIdentifier("default"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_get_default_user_group</id> + <title/> + <placeholder>groupService.getDefaultUserGroup</placeholder> + <event type="set"> + <id>group_service_get_default_user_group_set</id> + <actions phase="post"> + <action> + groupService.getDefaultUserGroup(userService.getLoggedOrSystem()); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_all</id> + <title/> + <placeholder>groupService.findAll</placeholder> + <event type="set"> + <id>group_service_find_all_set</id> + <actions phase="post"> + <action> + groupService.findAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_save</id> + <title/> + <placeholder>groupService.save</placeholder> + <event type="set"> + <id>group_service_save_set</id> + <actions phase="post"> + <action> + groupService.save(groupService.getDefaultUserGroup(userService.getLoggedOrSystem())); + </action> + </actions> + </event> + </data> + + <!-- PetriNetService methods --> + <data type="button"> + <id>btn_petrinet_service_get_petrinet</id> + <title/> + <placeholder>petriNetService.getPetriNet</placeholder> + <event type="set"> + <id>petrinet_service_get_petrinet_set</id> + <actions phase="post"> + <action> + petriNetService.getPetriNet(useCase.petriNetId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_default_version_by_identifier</id> + <title/> + <placeholder>petriNetService.getDefaultVersionByIdentifier</placeholder> + <event type="set"> + <id>petrinet_service_get_default_version_by_identifier_set</id> + <actions phase="post"> + <action> + petriNetService.getDefaultVersionByIdentifier(useCase.processIdentifier); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_latest_version_by_identifier</id> + <title/> + <placeholder>petriNetService.getLatestVersionByIdentifier</placeholder> + <event type="set"> + <id>petrinet_service_get_latest_version_by_identifier_set</id> + <actions phase="post"> + <action> + petriNetService.getLatestVersionByIdentifier(useCase.processIdentifier); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_all</id> + <title/> + <placeholder>petriNetService.getAll</placeholder> + <event type="set"> + <id>petrinet_service_get_all_set</id> + <actions phase="post"> + <action> + petriNetService.getAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_all_default</id> + <title/> + <placeholder>petriNetService.getAllDefault</placeholder> + <event type="set"> + <id>petrinet_service_get_all_default_set</id> + <actions phase="post"> + <action> + petriNetService.getAllDefault(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_find_all_by_id</id> + <title/> + <placeholder>petriNetService.findAllById</placeholder> + <event type="set"> + <id>petrinet_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + petriNetService.findAllById([useCase.petriNetId]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_save</id> + <title/> + <placeholder>petriNetService.save</placeholder> + <event type="set"> + <id>petrinet_service_save_set</id> + <actions phase="post"> + <action> + petriNetService.save(petriNetService.getPetriNet(useCase.petriNetId)); + </action> + </actions> + </event> + </data> + + <!-- TRANSITIONS --> + <transition> + <id>t1</id> + <x>250</x> + <y>150</y> + <label>Execute Service Tests</label> + <!-- TaskService --> + <dataRef> + <id>btn_task_service_find_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_optional_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_all_by_case</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_reload_tasks</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_search_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_search_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- WorkflowService --> + <dataRef> + <id>btn_workflow_service_find_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_find_one_no_net</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_get_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_update_marking</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_set_petri_net</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_search_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_search_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- UserService --> + <dataRef> + <id>btn_user_service_get_logged_or_system</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_get_system</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_by_email</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_user_by_username</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_save_user</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- GroupService --> + <dataRef> + <id>btn_group_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_by_predicate</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_get_default_user_group</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- PetriNetService --> + <dataRef> + <id>btn_petrinet_service_get_petrinet</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_default_version_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_latest_version_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_all_default</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </transition> +</document> diff --git a/application-engine/src/test/resources/service_methods_test.xml b/application-engine/src/test/resources/service_methods_test.xml new file mode 100644 index 00000000000..5100883a951 --- /dev/null +++ b/application-engine/src/test/resources/service_methods_test.xml @@ -0,0 +1,794 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.org/petriflow.schema.xsd"> + <id>service_methods_test</id> + <version>1.0.0</version> + <initials>SMT</initials> + <title>Service Method test + true + true + false + + + + + btn_task_service_find_one + + <placeholder>taskService.findOne</placeholder> + <event type="set"> + <id>task_service_find_one_set</id> + <actions phase="post"> + <action> + taskService.findOne(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_by_id</id> + <title/> + <placeholder>taskService.findById</placeholder> + <event type="set"> + <id>task_service_find_by_id_set</id> + <actions phase="post"> + <action> + taskService.findById(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_optional_by_id</id> + <title/> + <placeholder>taskService.findOptionalById</placeholder> + <event type="set"> + <id>task_service_find_optional_by_id_set</id> + <actions phase="post"> + <action> + taskService.findOptionalById(useCase.tasks.first().task); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_all_by_id</id> + <title/> + <placeholder>taskService.findAllById</placeholder> + <event type="set"> + <id>task_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + taskService.findAllById([useCase.tasks.first().task]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_find_all_by_case</id> + <title/> + <placeholder>taskService.findAllByCase</placeholder> + <event type="set"> + <id>task_service_find_all_by_case_set</id> + <actions phase="post"> + <action> + taskService.findAllByCase(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_save</id> + <title/> + <placeholder>taskService.save</placeholder> + <event type="set"> + <id>task_service_save_set</id> + <actions phase="post"> + <action> + taskService.save(taskService.findOne(useCase.tasks.first().task)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_reload_tasks</id> + <title/> + <placeholder>taskService.reloadTasks</placeholder> + <event type="set"> + <id>task_service_reload_tasks_set</id> + <actions phase="post"> + <action> + taskService.reloadTasks(useCase, true); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_search_all</id> + <title/> + <placeholder>taskService.searchAll</placeholder> + <event type="set"> + <id>task_service_search_all_set</id> + <actions phase="post"> + <action> + taskService.searchAll(com.netgrif.application.engine.adapter.spring.workflow.domain.QTask.task.transitionId.eq("t1")); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_task_service_search_one</id> + <title/> + <placeholder>taskService.searchOne</placeholder> + <event type="set"> + <id>task_service_search_one_set</id> + <actions phase="post"> + <action> + taskService.searchOne(com.netgrif.application.engine.adapter.spring.workflow.domain.QTask.task.transitionId.eq("t1")); + </action> + </actions> + </event> + </data> + + <!-- WorkflowService methods --> + <data type="button"> + <id>btn_workflow_service_find_one</id> + <title/> + <placeholder>workflowService.findOne</placeholder> + <event type="set"> + <id>workflow_service_find_one_set</id> + <actions phase="post"> + <action> + workflowService.findOne(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_find_one_no_net</id> + <title/> + <placeholder>workflowService.findOneNoNet</placeholder> + <event type="set"> + <id>workflow_service_find_one_no_net_set</id> + <actions phase="post"> + <action> + workflowService.findOneNoNet(useCase.stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_find_all_by_id</id> + <title/> + <placeholder>workflowService.findAllById</placeholder> + <event type="set"> + <id>workflow_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + workflowService.findAllById([useCase.stringId]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_get_all</id> + <title/> + <placeholder>workflowService.getAll</placeholder> + <event type="set"> + <id>workflow_service_get_all_set</id> + <actions phase="post"> + <action> + workflowService.getAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_save</id> + <title/> + <placeholder>workflowService.save</placeholder> + <event type="set"> + <id>workflow_service_save_set</id> + <actions phase="post"> + <action> + workflowService.save(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_update_marking</id> + <title/> + <placeholder>workflowService.updateMarking</placeholder> + <event type="set"> + <id>workflow_service_update_marking_set</id> + <actions phase="post"> + <action> + workflowService.updateMarking(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_set_petri_net</id> + <title/> + <placeholder>workflowService.setPetriNet</placeholder> + <event type="set"> + <id>workflow_service_set_petri_net_set</id> + <actions phase="post"> + <action> + workflowService.setPetriNet(useCase); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_search_all</id> + <title/> + <placeholder>workflowService.searchAll</placeholder> + <event type="set"> + <id>workflow_service_search_all_set</id> + <actions phase="post"> + <action> + workflowService.searchAll(com.netgrif.application.engine.adapter.spring.workflow.domain.QCase.case$.processIdentifier.eq(useCase.processIdentifier)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_workflow_service_search_one</id> + <title/> + <placeholder>workflowService.searchOne</placeholder> + <event type="set"> + <id>workflow_service_search_one_set</id> + <actions phase="post"> + <action> + workflowService.searchOne(com.netgrif.application.engine.adapter.spring.workflow.domain.QCase.case$.processIdentifier.eq(useCase.processIdentifier)); + </action> + </actions> + </event> + </data> + + <!-- UserService methods --> + <data type="button"> + <id>btn_user_service_get_logged_or_system</id> + <title/> + <placeholder>userService.getLoggedOrSystem</placeholder> + <event type="set"> + <id>user_service_get_logged_or_system_set</id> + <actions phase="post"> + <action> + userService.getLoggedOrSystem(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_get_system</id> + <title/> + <placeholder>userService.getSystem</placeholder> + <event type="set"> + <id>user_service_get_system_set</id> + <actions phase="post"> + <action> + userService.getSystem(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_by_id</id> + <title/> + <placeholder>userService.findById</placeholder> + <event type="set"> + <id>user_service_find_by_id_set</id> + <actions phase="post"> + <action> + userService.findById(useCase.author.id, null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_by_email</id> + <title/> + <placeholder>userService.findByEmail</placeholder> + <event type="set"> + <id>user_service_find_by_email_set</id> + <actions phase="post"> + <action> + userService.findByEmail(userService.getLoggedOrSystem().getEmail(), null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_all</id> + <title/> + <placeholder>userService.findAll</placeholder> + <event type="set"> + <id>user_service_find_all_set</id> + <actions phase="post"> + <action> + userService.findAll(); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_find_user_by_username</id> + <title/> + <placeholder>userService.findUserByUsername</placeholder> + <event type="set"> + <id>user_service_find_user_by_username_set</id> + <actions phase="post"> + <action> + userService.findUserByUsername(userService.getLoggedOrSystem().getUsername(), null); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_user_service_save_user</id> + <title/> + <placeholder>userService.saveUser</placeholder> + <event type="set"> + <id>user_service_save_user_set</id> + <actions phase="post"> + <action> + userService.saveUser(userService.getLoggedOrSystem()); + </action> + </actions> + </event> + </data> + + <!-- GroupService methods --> + <data type="button"> + <id>btn_group_service_find_by_id</id> + <title/> + <placeholder>groupService.findById</placeholder> + <event type="set"> + <id>group_service_find_by_id_set</id> + <actions phase="post"> + <action> + groupService.findById(groupService.getDefaultUserGroup(userService.getLoggedOrSystem()).stringId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_by_predicate</id> + <title/> + <placeholder>groupService.findByPredicate</placeholder> + <event type="set"> + <id>group_service_find_by_predicate_set</id> + <actions phase="post"> + <action> + groupService.findByPredicate(new com.netgrif.application.engine.objects.auth.domain.QGroup("group").identifier.eq("default"), org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_by_identifier</id> + <title/> + <placeholder>groupService.findByIdentifier</placeholder> + <event type="set"> + <id>group_service_find_by_identifier_set</id> + <actions phase="post"> + <action> + groupService.findByIdentifier("default"); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_get_default_user_group</id> + <title/> + <placeholder>groupService.getDefaultUserGroup</placeholder> + <event type="set"> + <id>group_service_get_default_user_group_set</id> + <actions phase="post"> + <action> + groupService.getDefaultUserGroup(userService.getLoggedOrSystem()); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_find_all</id> + <title/> + <placeholder>groupService.findAll</placeholder> + <event type="set"> + <id>group_service_find_all_set</id> + <actions phase="post"> + <action> + groupService.findAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_group_service_save</id> + <title/> + <placeholder>groupService.save</placeholder> + <event type="set"> + <id>group_service_save_set</id> + <actions phase="post"> + <action> + groupService.save(groupService.getDefaultUserGroup(userService.getLoggedOrSystem())); + </action> + </actions> + </event> + </data> + + <!-- PetriNetService methods --> + <data type="button"> + <id>btn_petrinet_service_get_petrinet</id> + <title/> + <placeholder>petriNetService.getPetriNet</placeholder> + <event type="set"> + <id>petrinet_service_get_petrinet_set</id> + <actions phase="post"> + <action> + petriNetService.getPetriNet(useCase.petriNetId); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_default_version_by_identifier</id> + <title/> + <placeholder>petriNetService.getDefaultVersionByIdentifier</placeholder> + <event type="set"> + <id>petrinet_service_get_default_version_by_identifier_set</id> + <actions phase="post"> + <action> + petriNetService.getDefaultVersionByIdentifier(useCase.processIdentifier); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_latest_version_by_identifier</id> + <title/> + <placeholder>petriNetService.getLatestVersionByIdentifier</placeholder> + <event type="set"> + <id>petrinet_service_get_latest_version_by_identifier_set</id> + <actions phase="post"> + <action> + petriNetService.getLatestVersionByIdentifier(useCase.processIdentifier); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_all</id> + <title/> + <placeholder>petriNetService.getAll</placeholder> + <event type="set"> + <id>petrinet_service_get_all_set</id> + <actions phase="post"> + <action> + petriNetService.getAll(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_get_all_default</id> + <title/> + <placeholder>petriNetService.getAllDefault</placeholder> + <event type="set"> + <id>petrinet_service_get_all_default_set</id> + <actions phase="post"> + <action> + petriNetService.getAllDefault(org.springframework.data.domain.PageRequest.of(0, 10)); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_find_all_by_id</id> + <title/> + <placeholder>petriNetService.findAllById</placeholder> + <event type="set"> + <id>petrinet_service_find_all_by_id_set</id> + <actions phase="post"> + <action> + petriNetService.findAllById([useCase.petriNetId]); + </action> + </actions> + </event> + </data> + + <data type="button"> + <id>btn_petrinet_service_save</id> + <title/> + <placeholder>petriNetService.save</placeholder> + <event type="set"> + <id>petrinet_service_save_set</id> + <actions phase="post"> + <action> + petriNetService.save(petriNetService.getPetriNet(useCase.petriNetId)); + </action> + </actions> + </event> + </data> + + <!-- TRANSITIONS --> + <transition> + <id>t1</id> + <x>250</x> + <y>150</y> + <label>Execute Service Tests</label> + <!-- TaskService --> + <dataRef> + <id>btn_task_service_find_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_optional_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_find_all_by_case</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_reload_tasks</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_search_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_task_service_search_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- WorkflowService --> + <dataRef> + <id>btn_workflow_service_find_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_find_one_no_net</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_get_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_update_marking</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_set_petri_net</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_search_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_workflow_service_search_one</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- UserService --> + <dataRef> + <id>btn_user_service_get_logged_or_system</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_get_system</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_by_email</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_find_user_by_username</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_user_service_save_user</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- GroupService --> + <dataRef> + <id>btn_group_service_find_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_by_predicate</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_get_default_user_group</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_find_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_group_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + + <!-- PetriNetService --> + <dataRef> + <id>btn_petrinet_service_get_petrinet</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_default_version_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_latest_version_by_identifier</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_all</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_get_all_default</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_find_all_by_id</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + <dataRef> + <id>btn_petrinet_service_save</id> + <logic> + <behavior>editable</behavior> + </logic> + </dataRef> + </transition> +</document> diff --git a/docs/authorization/authority.md b/docs/authorization/authority.md new file mode 100644 index 00000000000..b01923c78c7 --- /dev/null +++ b/docs/authorization/authority.md @@ -0,0 +1,299 @@ +# Authority System + +The Netgrif Application Engine (NAE) uses **authorities** to protect resources and +operations from unauthorized access. Authorities are application-wide permissions that +can be assigned to users to grant them access to secured resources or operations. + +Authorities differ from process roles in that they are **not** related to a specific +process. While a process role controls what a user can do *inside* a Petriflow process +(e.g. assign a task, view a case), an authority controls access to application-level +functionality (e.g. importing a process, creating a user, deleting an authority). + +## Table of contents + +- [Concepts](#concepts) +- [Authorizing objects](#authorizing-objects) + - [Predefined authorizing objects](#predefined-authorizing-objects) + - [Custom authorizing objects](#custom-authorizing-objects) +- [Authority scopes](#authority-scopes) +- [Default authorities](#default-authorities) +- [Protecting code with `@Authorize`](#protecting-code-with-authorize) + - [Where can it be used](#where-can-it-be-used) + - [Annotation fields](#annotation-fields) + - [Combining conditions](#combining-conditions) + - [Multiple `@Authorize` annotations](#multiple-authorize-annotations) +- [How the check is evaluated](#how-the-check-is-evaluated) +- [Managing authorities via REST API](#managing-authorities-via-rest-api) + +## Concepts + +There are three core concepts in the authority system: + +| Concept | Description | +|----------------------|---------------------------------------------------------------------------------------------------------| +| `AuthorizingObject` | An enum value describing *what* is being protected (e.g. `PROCESS_UPLOAD`). | +| `Authority` | A persisted entity (stored in MongoDB) created from an authorizing object. It implements Spring Security's `GrantedAuthority`. | +| `@Authorize` | An annotation placed on a method or a class that requires a user to hold a given authority and/or satisfy an expression. | + +At application startup, an `Authority` entity is created for each `AuthorizingObject` +value (and for every custom authorizing object). Users are then granted a set of +authorities, and the `@Authorize` annotation is used across the codebase to enforce them. + +## Authorizing objects + +An authorizing object is a value of the `AuthorizingObject` enum. It represents an +authority that a user must hold to access a resource or invoke an operation. For example, +to import a new process (Petri net) into the application, the user must hold the authority +created from `AuthorizingObject.PROCESS_UPLOAD`. + +Authorizing objects are predefined and are used to create the `Authority` entities during +application startup by the `AuthorityRunner`. + +### Predefined authorizing objects + +The application engine defines the following authorizing objects: + +**Process** + +- `PROCESS_UPLOAD` — import a new process +- `PROCESS_VIEW` — retrieve all processes imported by any user +- `PROCESS_DELETE` — delete processes imported by any user +- `PROCESS_DOWNLOAD` — download processes imported by any user + +**User** + +- `USER_CREATE` — invite or create a user +- `USER_DELETE` — remove a user +- `USER_EDIT_ALL` — edit any user +- `USER_EDIT_SELF` — edit only the logged user + +**Group** + +- `GROUP_CREATE` — create a group +- `GROUP_CREATE_OWN` — create a group owned by the logged user +- `GROUP_DELETE` — delete a group created by any user +- `GROUP_DELETE_OWN` — delete a group created by the logged user +- `GROUP_ADD_USER` — add any user to any group +- `GROUP_ADD_USER_OWN` — add a user to a group owned by the logged user +- `GROUP_REMOVE_USER` — remove any user from any group +- `GROUP_REMOVE_USER_OWN` — remove a user from a group owned by the logged user +- `GROUP_ADD_SUBGROUP` — add a subgroup to any group +- `GROUP_ADD_SUBGROUP_OWN` — add a subgroup to a group owned by the logged user +- `GROUP_REMOVE_SUBGROUP` — remove a subgroup from any group +- `GROUP_REMOVE_SUBGROUP_OWN` — remove a subgroup from a group owned by the logged user + +**Role** + +- `ROLE_ASSIGN_TO_USER` — assign a process role to a user +- `ROLE_ASSIGN_TO_GROUP` — assign a process role to a group + +**Authority** + +- `AUTHORITY_CREATE` — create an authority +- `AUTHORITY_DELETE` — delete an authority +- `AUTHORITY_ASSIGN_TO_USER` — assign an authority to a user +- `AUTHORITY_ASSIGN_TO_GROUP` — assign an authority to a group + +**Elasticsearch** + +- `ELASTIC_REINDEX` — reindex the Elasticsearch database + +> The enum also declares the default values as `ADMIN`, `USER`, `SYSTEMADMIN`, and `ANONYMOUS` values. + +### Custom authorizing objects + +You can register your own authorizing objects using the +`nae.authority.authorizing-objects` property in `application.properties`. The listed +values are created as `Authority` entities on startup alongside the predefined ones: + +```properties +# Authorities +nae.authority.authorizing-objects=EXAMPLE_AUTHORITY_1,EXAMPLE_AUTHORITY_2 +``` + +## Authority scopes + +Authorities can be referenced by **scope**. A scope groups all authorities that share the +same name prefix, denoted by a trailing `*`. For example, the scope `PROCESS_*` covers +`PROCESS_UPLOAD`, `PROCESS_VIEW_ALL`, `PROCESS_DELETE_OWN`, and so on. The single `*` +scope represents all authorities in the system. + +Scopes are handy when assigning a set of related authorities (for example, as default +authorities) or when querying authorities via the REST API. Note that scope names are not +valid arguments for operations that expect a single, concrete authority (such as creating +or deleting an authority). + +## Default authorities + +Newly created users receive a set of default authorities. These defaults are configured +per user type using scopes and concrete authority names: + +```properties +nae.authority.defaultUserAuthorities=USER,PROCESS_VIEW,USER_EDIT_SELF,GROUP_DELETE_OWN +nae.authority.defaultAnonymousAuthorities=... +nae.authority.defaultAdminAuthorities=* +``` + +- `defaultUserAuthorities` — granted to a standard registered user. +- `defaultAnonymousAuthorities` — granted to the anonymous user. +- `defaultAdminAuthorities` — granted to the super/admin user (`*` grants everything). + +These properties are read by `AuthorityProperties` and resolved (including scopes) by the +`AuthorityService`. + +## Protecting code with `@Authorize` + +Any method in the engine can be protected with the `@Authorize` annotation. When the +annotated method is invoked, an AOP aspect intercepts the call and verifies that the +currently logged user is authorized before the method body runs. If the check fails, an +`AccessDeniedException` is thrown and the method is never executed. + +### Where can it be used + +The annotation targets both **methods** and **types**, so it can be applied to: + +- **REST controllers** — to guard HTTP endpoints. For example, the endpoints of the + authority management controller are protected with authorities such as + `AUTHORITY_CREATE` and `AUTHORITY_DELETE`: + + ```java + @Authorize(authority = "AUTHORITY_DELETE") + @DeleteMapping("/delete/{name}") + public MessageResource delete(@PathVariable String name, Authentication auth) { + authorityService.delete(name); + // ... + } + ``` + +- **`ActionDelegate` methods** — the Actions API available in Petriflow actions is backed + by `ActionDelegate`. Its methods are protected so that action code can only perform an + operation if the user running the action holds the required authority. For example, + inviting or deleting a user requires `USER_CREATE` / `USER_DELETE`: + + ```groovy + @Authorize(authority = "USER_CREATE") + MessageResource inviteUser(String email) { + // ... + } + + @Authorize(authority = "USER_DELETE") + void deleteUser(String email) { + // ... + } + ``` + +- **Service methods** — or any other Spring-managed bean method. + +> Because the check is implemented with Spring AOP, `@Authorize` only takes effect on +> calls that go through the Spring proxy. Self-invocations (a bean calling its own +> annotated method directly) are not intercepted. + +### Annotation fields + +`@Authorize` has two optional fields: + +- `authority` — an array of authority names the logged user must hold. When multiple + values are provided, the user must hold **all** of them. +- `expression` — a [Spring Expression Language (SpEL)](https://docs.spring.io/spring-framework/reference/core/expressions.html) + expression that must evaluate to `true`. Inside the expression you can reference: + - the arguments of the intercepted method as variables (e.g. `#userId`, `#email`), + - Spring beans (e.g. `@userService`). + +```groovy +@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") +def changeUserByEmail(String email) { + // ... +} +``` + +If a field is omitted, that part of the check is considered satisfied: + +- No `authority` → the authority check returns `true`. +- No `expression` → the expression check returns `true`. + +```java +// Requires only the PROCESS_UPLOAD authority +@Authorize(authority = "PROCESS_UPLOAD") +void importPetriNet(File petriNet) { /* code is here */ } +``` + +```java +// Requires only that the expression evaluates to true +@Authorize(expression = "#canUpload(#userId)") +void importPetriNet(String userId, File petriNet) { /* code is here */ } +``` + +### Combining conditions + +Within a single `@Authorize` annotation, the `authority` and `expression` checks are +combined with a logical **AND**. The user is authorized only if they hold the required +authority **and** the expression evaluates to `true`: + +```java +@Authorize(authority = "PROCESS_UPLOAD", expression = "#canUpload(#userId)") +void importPetriNet(String userId, File petriNet) { /* code is here */ } +``` + +### Multiple `@Authorize` annotations + +`@Authorize` is repeatable. When multiple annotations are placed on the same element, they +are combined with a logical **OR** — the user is authorized if **at least one** of the +`@Authorize` statements is satisfied. Under the hood, repeated annotations are grouped in +the `@Authorizations` container annotation. + +In the example below the user is authorized if they hold `USER_EDIT_ALL`, **or** if they +hold `USER_EDIT_SELF` and are editing their own account: + +```groovy +@Authorize(authority = "USER_EDIT_ALL") +@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().stringId.equals(#id)") +def changeUser(String id) { + // ... +} +``` + +## How the check is evaluated + +Authorization is enforced by the `BaseAuthorizationServiceAspect` bean, implemented using +Spring AOP. The aspect intercepts every call to a method annotated with `@Authorize` +(or its container `@Authorizations`) and evaluates the conditions as follows: + +1. For each `@Authorize` statement, the aspect checks: + - **Authority** — whether the logged user's granted authorities contain all of the + declared authority names (`hasAnyAuthority`). An empty/omitted `authority` passes. + - **Expression** — whether the SpEL `expression` evaluates to `true` + (`isAllowedByExpression`). An empty/omitted `expression` passes. Method arguments + are bound as SpEL variables and Spring beans are resolvable from the application + context. +2. A single statement passes when **both** its authority and expression checks pass (AND). +3. When several statements are present, the overall result is `true` if **any** statement + passes (OR). +4. If the overall result is `true`, the intercepted method proceeds. Otherwise, an + `AccessDeniedException` (`"Access Denied. User does not have required authorization + level."`) is thrown. + +If the expression cannot be parsed or throws while evaluating, it is treated as `false` +(access is not granted based on that expression). + +## Managing authorities via REST API + +Authorities can be managed at runtime through the authority REST controller under +`/api/authority`. All endpoints are themselves protected with `@Authorize`: + +| Method & path | Required authority | Description | +|------------------------------|--------------------|-----------------------------------------------| +| `POST /api/authority/create` | `AUTHORITY_CREATE` | Create (or return existing) authority by name | +| `DELETE /api/authority/delete/{name}` | `AUTHORITY_DELETE` | Delete an authority by name | +| `GET /api/authority/all` | `AUTHORITY_GET_ALL`* | Retrieve all authorities | +| `GET /api/authority/{name}` | `AUTHORITY_VIEW` | Retrieve a single authority by name | +| `GET /api/authority/scope/{scope}` | `AUTHORITY_VIEW` | Retrieve all authorities within a scope | + +These endpoints delegate to `AuthorityService`, which persists authorities in MongoDB via +`AuthorityRepository` and supports scope-based lookups through `findByScope`. + +> The controller is only registered when the `nae.user.web.enabled` property is `true` +> (which is the default). + +*Note: the "get all" endpoint requires the authority the controller declares for it; make +sure your users are granted the corresponding authority in your configuration. +``` \ No newline at end of file diff --git a/nae-object-library/pom.xml b/nae-object-library/pom.xml index 220085265e8..54f7b8b472e 100644 --- a/nae-object-library/pom.xml +++ b/nae-object-library/pom.xml @@ -121,6 +121,12 @@ <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + <version>26.0.2-1</version> + <scope>compile</scope> + </dependency> </dependencies> <build> diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorizations.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorizations.java new file mode 100644 index 00000000000..dfe7e1f0e50 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorizations.java @@ -0,0 +1,20 @@ +package com.netgrif.application.engine.objects.annotations; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define set of authorizing statements + * */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Authorizations { + + /** + * The array of authorizing statements, access will be granted, if one of these is true. + * */ + Authorize[] value(); +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorize.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorize.java new file mode 100644 index 00000000000..1a98baaebc8 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorize.java @@ -0,0 +1,25 @@ +package com.netgrif.application.engine.objects.annotations; + +import org.intellij.lang.annotations.Language; + +import java.lang.annotation.*; + +/** + * This annotation serves as marking for functions that requires additional authorization over the basic authentication. + * */ +@Repeatable(Authorizations.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface Authorize { + + /** + * The authorizing object to be checked, whether the user has it assigned. + * */ + String[] authority() default ""; + + /** + * The Spring-EL expression to be evaluated before invoking the protected method. + * */ + @Language("SpEL") + String expression() default ""; +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/constants/AuthorizingObject.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/constants/AuthorizingObject.java new file mode 100644 index 00000000000..bad0dcecc19 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/constants/AuthorizingObject.java @@ -0,0 +1,48 @@ +package com.netgrif.application.engine.objects.auth.constants; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The enum of all possible authorizing objects, that are used for creating authority objects. Authorizing object is a + * term that defines the goal of the authority, e.g. what kind of system process is protected via an authority created + * using given authorizing object. + * */ +public enum AuthorizingObject { + ADMIN, + USER, + SYSTEMADMIN, + ANONYMOUS, + PROCESS_UPLOAD, + PROCESS_DOWNLOAD, + PROCESS_VIEW, + PROCESS_DELETE, + USER_CREATE, + USER_DELETE, + USER_EDIT_ALL, + USER_EDIT_SELF, + GROUP_CREATE, + GROUP_CREATE_OWN, + GROUP_DELETE, + GROUP_DELETE_OWN, + GROUP_ADD_USER, + GROUP_ADD_USER_OWN, + GROUP_REMOVE_USER, + GROUP_REMOVE_USER_OWN, + GROUP_ADD_SUBGROUP, + GROUP_ADD_SUBGROUP_OWN, + GROUP_REMOVE_SUBGROUP, + GROUP_REMOVE_SUBGROUP_OWN, + ROLE_ASSIGN_TO_USER, + ROLE_ASSIGN_TO_GROUP, + AUTHORITY_ASSIGN_TO_USER, + AUTHORITY_ASSIGN_TO_GROUP, + AUTHORITY_CREATE, + AUTHORITY_DELETE, + ELASTIC_REINDEX; + + public static List<String> stringValues() { + return Arrays.stream(AuthorizingObject.values()).map(Enum::name).collect(Collectors.toList()); + } +} diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java index 353764085c7..01dc04267a8 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java @@ -44,6 +44,8 @@ public abstract class Authority implements Serializable { */ public static final String anonymous = "ANONYMOUS_USER"; + public static final String SCOPE_SUFFIX = "*"; + /** * MongoDB ObjectId of the authority. */ diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java index a158a4f392b..388d74ce9dc 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java @@ -7,7 +7,8 @@ public enum ResourceNotFoundExceptionCode { DEFAULT_SYSTEM_GROUP_NOT_FOUND("defaultSystemGroupNotFound"), DEFAULT_USER_GROUP_NOT_FOUND("defaultUserGroupNotFound"), - DEFAULT_PROCESS_NOT_FOUND("defaultProcessNotFound"); + DEFAULT_PROCESS_NOT_FOUND("defaultProcessNotFound"), + AUTHORITY_NOT_FOUND("authorityNotFound"); private final String key; diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java index eadac8c7e9b..f735a52da26 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/utils/NaeReflectionUtils.java @@ -3,13 +3,22 @@ import com.netgrif.application.engine.adapter.spring.utils.exceptions.AmbiguousMethodCallException; import org.springframework.aop.framework.AopProxyUtils; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public final class NaeReflectionUtils { + private static final String GROOVY_OBJECT = "groovy.lang.GroovyObject"; + + private static final String GROOVY_GENERATED = "groovy.transform.Generated"; + + private static final String GROOVY_CLASS_LOADER = "groovy.lang.GroovyClassLoader"; + + private static final Map<Class<?>, Boolean> CACHE = new ConcurrentHashMap<>(); + private NaeReflectionUtils() { throw new IllegalStateException("No instances. Utility class"); } @@ -138,4 +147,69 @@ private static Method findMethodWithSuperClassParams(Object bean, String methodT return methodToInvoke; } } + + public static boolean isGroovyClass(Class<?> clazz) { + if (clazz == null) { + return false; + } + return CACHE.computeIfAbsent(clazz, NaeReflectionUtils::detect); + } + + private static boolean detect(Class<?> clazz) { + return implementsGroovyObject(clazz) + || loadedByGroovyClassLoader(clazz.getClassLoader()) + || hasGroovyGeneratedMember(clazz) + || hasMetaClassField(clazz); + } + + /** Walks the whole type hierarchy and compares interface names as plain strings. */ + private static boolean implementsGroovyObject(Class<?> clazz) { + Deque<Class<?>> queue = new ArrayDeque<>(); + queue.add(clazz); + while (!queue.isEmpty()) { + Class<?> current = queue.poll(); + if (GROOVY_OBJECT.equals(current.getName())) { + return true; + } + queue.addAll(Arrays.asList(current.getInterfaces())); + if (current.getSuperclass() != null) { + queue.add(current.getSuperclass()); + } + } + return false; + } + + /** Covers dynamically compiled scripts/actions that have no .class resource on disk. */ + private static boolean loadedByGroovyClassLoader(ClassLoader loader) { + for (Class<?> c = loader == null ? null : loader.getClass(); c != null; c = c.getSuperclass()) { + if (GROOVY_CLASS_LOADER.equals(c.getName())) { + return true; + } + } + return false; + } + + /** Groovy 3+ marks all synthetic members with @groovy.transform.Generated. */ + private static boolean hasGroovyGeneratedMember(Class<?> clazz) { + return hasGroovyGenerated(clazz) + || java.util.Arrays.stream(clazz.getDeclaredMethods()).anyMatch(NaeReflectionUtils::hasGroovyGenerated) + || java.util.Arrays.stream(clazz.getDeclaredConstructors()).anyMatch(NaeReflectionUtils::hasGroovyGenerated); + } + + private static boolean hasGroovyGenerated(AnnotatedElement element) { + for (Annotation annotation : element.getDeclaredAnnotations()) { + if (GROOVY_GENERATED.equals(annotation.annotationType().getName())) { + return true; + } + } + return false; + } + + private static boolean hasMetaClassField(Class<?> clazz) { + try { + return clazz.getDeclaredField("metaClass") != null; + } catch (NoSuchFieldException | SecurityException e) { + return false; + } + } } diff --git a/nae-user-ce/pom.xml b/nae-user-ce/pom.xml index 91fc13e816c..8c5ea421f50 100644 --- a/nae-user-ce/pom.xml +++ b/nae-user-ce/pom.xml @@ -142,6 +142,10 @@ <version>1.3.2</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.aspectj</groupId> + <artifactId>aspectjweaver</artifactId> + </dependency> </dependencies> <build> diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java index f8dc0971834..50e8306a54e 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorityServiceImpl.java @@ -1,12 +1,15 @@ package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl; +import com.netgrif.application.engine.auth.config.AuthorityConfigurationProperties; import com.netgrif.application.engine.auth.repository.AuthorityRepository; import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.dto.AuthoritySearchDto; import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -15,16 +18,17 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.transaction.annotation.Transactional; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import static com.netgrif.application.engine.objects.auth.domain.Authority.SCOPE_SUFFIX; + @Slf4j public class AuthorityServiceImpl implements AuthorityService { private AuthorityRepository authorityRepository; private MongoTemplate mongoTemplate; + private AuthorityConfigurationProperties authorityProperties; @Autowired public void setAuthorityRepository(AuthorityRepository authorityRepository) { @@ -36,6 +40,11 @@ public void setMongoTemplate(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } + @Autowired + public void setAuthorityProperties(AuthorityConfigurationProperties authorityProperties) { + this.authorityProperties = authorityProperties; + } + @Override public Page<Authority> findAll(Pageable pageable) { return authorityRepository.findAll(pageable); @@ -74,4 +83,112 @@ public Page<Authority> search(AuthoritySearchDto searchDto, Pageable pageable) { List<Authority> authorities = mongoTemplate.find(query.with(pageable), Authority.class); return new PageImpl<>(authorities, pageable, count); } + + /** + * Removes authority from database based on provided name if exists + * @param name of authority to be deleted + * */ + @Override + public void delete(String name) { + if (isScoped(name)) { + throw new IllegalArgumentException("The authority name is not valid. Scope is suitable for this function."); + } + Optional<Authority> authority = authorityRepository.findByName(name); + if (authority.isEmpty()) { + log.warn("Authority with name [{}] not found", name); + return; + } + authorityRepository.delete(authority.get()); + } + + /** + * Returns authorities of provided scope. A scope contains authorities of the same name prefix, such as authorities + * of PROCESS scope: PROCESS_UPLOAD, PROCESS_DELETE etc. + * @param scope to be searched for + * @return list of authorities of given scope + * */ + @Override + public List<Authority> findByScope(String scope) { + List<Authority> authorities; + if (scope.equals(SCOPE_SUFFIX)) + authorities = authorityRepository.findAll(); + else if (isScoped(scope)) { + String prefix = scope.replace(SCOPE_SUFFIX, Strings.EMPTY); + authorities = authorityRepository.findAllByNameStartsWith(prefix); + } else { + authorities = Collections.singletonList(findByName(scope)); + } + return authorities; + } + + /** + * Returns authority based on name, throws exception if authority name is not valid or authority with provided name + * cannot be found. + * @param name of authority + * @return authority object + * */ + @Override + public Authority findByName(String name) { + if (isScoped(name)) { + throw new IllegalArgumentException("The authority name is not valid. Scope is suitable for this function."); + } + Optional<Authority> authority = authorityRepository.findByName(name); + if (authority.isEmpty()) { + throw new IllegalArgumentException("Could not find authority with name [" + name + "]"); + } + return authority.get(); + } + + + /** + * Returns authority from database based on provided ID + * @param id of authority to be retrieved + * @return optional of authority + * */ + @Override + public Optional<Authority> findOptionalByName(String id) { + return authorityRepository.findByName(id); + } + + /** + * Returns the default authorities for simple user + * @return set of authorities + * */ + @Override + @Cacheable("defaultUserAuthoritiesCache") + public Set<Authority> getDefaultUserAuthorities() { + return authorityProperties.getDefaultUserAuthorities().stream().map(this::findByScope).flatMap(Collection::stream).collect(Collectors.toSet()); + } + + /** + * Returns the default authorities for anonymous user + * @return set of authorities + * */ + @Override + @Cacheable("defaultAnonymousAuthoritiesCache") + public Set<Authority> getDefaultAnonymousAuthorities() { + return authorityProperties.getDefaultAnonymousAuthorities().stream().map(this::findByScope).flatMap(Collection::stream).collect(Collectors.toSet()); + } + + /** + * Returns the default authorities for admin user + * @return set of authorities + * */ + @Override + @Cacheable("defaultAdminAuthoritiesCache") + public Set<Authority> getDefaultAdminAuthorities() { + return authorityProperties.getDefaultAdminAuthorities().stream().map(this::findByScope).flatMap(Collection::stream).collect(Collectors.toSet()); + } + + /** + * Checks for authorityName, if it is valid scope name + * @param authorityName of authority + * @return boolean whether the provided name is valid scope name + * */ + private boolean isScoped(String authorityName) { + if (authorityName.contains(SCOPE_SUFFIX) && authorityName.indexOf(SCOPE_SUFFIX) != authorityName.length() - 1) { + throw new IllegalArgumentException("The authority name or scope is not valid."); + } + return authorityName.endsWith(SCOPE_SUFFIX); + } } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/BaseAuthorizationServiceAspect.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/BaseAuthorizationServiceAspect.java new file mode 100644 index 00000000000..3f7a497100b --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/BaseAuthorizationServiceAspect.java @@ -0,0 +1,139 @@ +package com.netgrif.application.engine.auth.service; + + +import com.netgrif.application.engine.adapter.spring.utils.NaeReflectionUtils; +import com.netgrif.application.engine.objects.annotations.Authorizations; +import com.netgrif.application.engine.objects.annotations.Authorize; +import com.netgrif.application.engine.objects.auth.domain.Authority; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.expression.ExpressionUtils; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * The aspect service for authorization system. + * */ +@Slf4j +@Aspect +@Service +public class BaseAuthorizationServiceAspect { + + private final UserService userService; + + private final ExpressionParser parser; + + private final StandardEvaluationContext evaluationContext; + + public BaseAuthorizationServiceAspect(ApplicationContext applicationContext, + UserService userService) { + this.userService = userService; + parser = new SpelExpressionParser(); + evaluationContext = new StandardEvaluationContext(); + evaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); + } + + /** + * The advice that handles incoming authorization requests from join points defined with @annotation(authorization) + * @param joinPoint the incoming method invocation join point + * @param authorizations the incoming annotation with authorization parameters + * */ + @Around(value = "@annotation(authorizations)", argNames = "joinPoint,authorizations") + protected final Object authorize(ProceedingJoinPoint joinPoint, Authorizations authorizations) throws Throwable { + boolean result = false; + if (authorizations.value() != null) { + for (Authorize authorize : authorizations.value()) { + result = result || (isAllowedByExpression(joinPoint, authorize.expression()) && hasAnyAuthority(authorize.authority())); + } + } + if (result) { + return joinPoint.proceed(); + } else { + throw new AccessDeniedException("Access Denied. User does not have required authorization level."); + } + } + + /** + * The advice that handles incoming authorization requests from join points defined with @annotation(authorization) + * @param joinPoint the incoming method invocation join point + * @param authorize the incoming annotation with authorization parameter + * */ + @Around(value = "@annotation(authorize)", argNames = "joinPoint,authorize") + protected final Object authorize(ProceedingJoinPoint joinPoint, Authorize authorize) throws Throwable { + boolean result = false; + if (authorize != null) { + result = isAllowedByExpression(joinPoint, authorize.expression()) && hasAnyAuthority(authorize.authority()); + } + if (result) { + return joinPoint.proceed(); + } else { + throw new AccessDeniedException("Access Denied. User does not have required authorization level."); + } + } + + /** + * Checks whether the currently logged user has all the input authorizing objects. + * @param authorizingObject to be checked for user + * @return boolean if user has all the authorizing objects. + * */ + public final boolean hasAnyAuthority(String[] authorizingObject) { + if (authorizingObject == null || authorizingObject.length == 0 || Arrays.stream(authorizingObject).allMatch(String::isEmpty)) + return true; + Set<String> loggedUserAuthorities = this.userService.getLoggedUser().getAuthoritySet().stream().map(Authority::getAuthority).collect(Collectors.toSet()); + return loggedUserAuthorities.containsAll(Arrays.asList(authorizingObject)); + } + + /** + * Parser and evaluator function for Spring-EL expression. It creates parser for SpEL expression, context for it, + * and evaluates the authorization SpEL expression. It resolves the arguments of invoked method and sets as + * variables into the evaluation context, because in the SpEL expression there can be method arguments as variables + * alongside beans. + * @param joinPoint the incoming method invocation join point + * @param expression the SpEL expression + * @return the evaluated value, whether the SpEL expression returns true or not + * */ + protected boolean isAllowedByExpression(ProceedingJoinPoint joinPoint, String expression) { + if (expression == null || expression.isEmpty()) { + return true; + } + + List<Object> args = Arrays.asList(joinPoint.getArgs()); + + if (NaeReflectionUtils.isGroovyClass(joinPoint.getTarget().getClass())) { + for (int i = 0; i < args.size(); i++) { + evaluationContext.setVariable("arg" + i, args.get(i)); + } + } else { + List<String> argNames = Arrays.asList(((MethodSignature) joinPoint.getSignature()).getParameterNames()); + argNames.forEach(name -> evaluationContext.setVariable(name, args.get(argNames.indexOf(name)))); + } + + boolean allowed; + try { + allowed = ExpressionUtils.evaluateAsBoolean(parser.parseExpression(expression), evaluationContext); + return allowed; + } catch (EvaluationException | NullPointerException e) { + log.warn("Failed to parse expression '{}'.", expression); + return false; + } + } + +} 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..886a0019f27 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 @@ -4,6 +4,7 @@ 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.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.Group; import com.netgrif.application.engine.objects.auth.dto.GroupSearchDto; 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..e9434b8d33c 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 @@ -6,6 +6,7 @@ import com.netgrif.application.engine.auth.config.GroupConfigurationProperties; import com.netgrif.application.engine.auth.provider.CollectionNameProvider; import com.netgrif.application.engine.auth.repository.UserRepository; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.constants.UserConstants; import com.netgrif.application.engine.objects.auth.domain.*; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; @@ -156,6 +157,19 @@ public Optional<AbstractUser> findUserByUsername(String username, String realmId return userOpt; } + @Override + public Optional<AbstractUser> findUserByEmail(String email, String realmId) { + log.debug("Finding user by username [{}] in realm [{}]", email, realmId); + String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId); + Optional<AbstractUser> userOpt = userRepository.findByEmail(email, mongoTemplate, collectionName).map(user -> user); + if (userOpt.isPresent()) { + log.debug("User [{}] found in realm [{}]", email, realmId); + } else { + log.warn("User [{}] not found in realm [{}]", email, realmId); + } + return userOpt; + } + @Override public Page<AbstractUser> findAllUsersByQuery(Query query, String realmName, Pageable pageable) { log.trace("Retrieving all users in realm [{}]", realmName); diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/config/AuthorityConfigurationProperties.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/config/AuthorityConfigurationProperties.java new file mode 100644 index 00000000000..95f74d66c4b --- /dev/null +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/config/AuthorityConfigurationProperties.java @@ -0,0 +1,68 @@ +package com.netgrif.application.engine.auth.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration properties for authority management in the Netgrif Application Engine. + * <p> + * This class provides configuration for default authorities assigned to different user types + * and allows defining additional custom authorizing objects. + * <p> + * Configuration properties are bound with the prefix {@code netgrif.engine.authority}. + * <p> + * Example configuration in application.properties or application.yml: + * <pre> + * netgrif.engine.authority.default-user-authorities=USER_READ,USER_WRITE + * netgrif.engine.authority.default-anonymous-authorities=PUBLIC_READ + * netgrif.engine.authority.default-admin-authorities=ADMIN_ALL + * netgrif.engine.authority.additional-authorizing-objects=CUSTOM_OBJECT_1,CUSTOM_OBJECT_2 + * </pre> + * + * @see com.netgrif.application.engine.auth.service.BaseAuthorizationServiceAspect + * @see com.netgrif.application.engine.startup.runner.AuthorityRunner + */ +@Data +@Component +@ConfigurationProperties(prefix = "netgrif.engine.authority") +public class AuthorityConfigurationProperties { + + /** + * List of default authorities assigned to regular users upon creation. + * <p> + * These authorities define the baseline permissions for standard user accounts + * in the application. + */ + private List<String> defaultUserAuthorities = new ArrayList<>(); + + /** + * List of default authorities assigned to anonymous (unauthenticated) users. + * <p> + * These authorities define the permissions available to users who are not + * logged into the system. + */ + private List<String> defaultAnonymousAuthorities = new ArrayList<>(); + + /** + * List of default authorities assigned to administrator users. + * <p> + * These authorities define elevated permissions for users with administrative + * privileges in the application. + */ + private List<String> defaultAdminAuthorities = new ArrayList<>(); + + /** + * List of additional custom authorizing objects to be created at application startup. + * <p> + * This allows extending the authorization system with custom authority types beyond + * the predefined ones. These objects will be created by the {@code AuthorityRunner} + * during application initialization. + * + * @see com.netgrif.application.engine.startup.runner.AuthorityRunner + */ + private List<String> additionalAuthorizingObjects = new ArrayList<>(); +} diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java index df908b01391..926301d37bd 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/AuthorityRepository.java @@ -9,6 +9,7 @@ import java.util.Collection; +import java.util.List; import java.util.Optional; /** @@ -34,4 +35,6 @@ public interface AuthorityRepository extends MongoRepository<Authority, String> * @return a {@link Page} containing the matching {@link Authority} entities */ Page<Authority> findAllBy_idIn(Collection<ObjectId> ids, Pageable pageable); + + List<Authority> findAllByNameStartsWith(String prefix); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java index 0197249bc87..b7ace49bc58 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorityService.java @@ -2,10 +2,14 @@ import com.netgrif.application.engine.objects.auth.domain.Authority; import com.netgrif.application.engine.objects.auth.dto.AuthoritySearchDto; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; /** * Service interface for managing {@link Authority} entities. @@ -54,4 +58,18 @@ public interface AuthorityService { * @return a {@link Page} of {@link Authority} entities matching the search criteria. */ Page<Authority> search(AuthoritySearchDto searchDto, Pageable pageable); + + void delete(String name); + + List<Authority> findByScope(String scope); + + Authority findByName(String name); + + Optional<Authority> findOptionalByName(String id); + + Set<Authority> getDefaultUserAuthorities(); + + Set<Authority> getDefaultAnonymousAuthorities(); + + Set<Authority> getDefaultAdminAuthorities(); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java index 9490c21308e..46fad0bcbc3 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java @@ -72,6 +72,15 @@ public interface UserService { */ Optional<AbstractUser> findUserByUsername(String username, String realmName); + /** + * Finds a user by email within a specific realm. + * + * @param email the email address to search for + * @param realmId the id of the realm + * @return an Optional containing the user if found, otherwise empty + */ + Optional<AbstractUser> findUserByEmail(String email, String realmId); + Page<AbstractUser> findAllUsersByQuery(Query query, String realmName, Pageable pageable); /** diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/NewAuthorityRequest.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/NewAuthorityRequest.java new file mode 100644 index 00000000000..82d6a98646d --- /dev/null +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/NewAuthorityRequest.java @@ -0,0 +1,10 @@ +package com.netgrif.application.engine.auth.web.requestbodies; + +import lombok.Data; + +@Data +public class NewAuthorityRequest { + + public String name; + +} diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/AuthorityDto.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/AuthorityDto.java new file mode 100644 index 00000000000..67aa09d7185 --- /dev/null +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/AuthorityDto.java @@ -0,0 +1,17 @@ +package com.netgrif.application.engine.auth.web.responsebodies; + +import com.netgrif.application.engine.objects.auth.domain.Authority; +import lombok.Data; + +@Data +public class AuthorityDto { + + private String id; + + private String name; + + public AuthorityDto(Authority authority) { + this.id = authority.getStringId(); + this.name = authority.getName(); + } +}