From b810b0b54168d92f3754c50d7d52519e330bc63a Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 18 Aug 2026 09:28:13 +0200 Subject: [PATCH 1/9] Add advanced authorization support: introduce `Authorize` and `Authorizations` annotations, implement `BaseAuthorizationServiceAspect`, extend Authority management, and update dependencies. --- nae-object-library/pom.xml | 6 + .../objects/annotations/Authorizations.java | 20 +++ .../engine/objects/annotations/Authorize.java | 25 ++++ .../engine/objects/auth/domain/Authority.java | 2 + .../common/ResourceNotFoundExceptionCode.java | 3 +- nae-user-ce/pom.xml | 4 + .../auth/service/AuthorityServiceImpl.java | 118 +++++++++++++++- .../BaseAuthorizationServiceAspect.java | 127 ++++++++++++++++++ .../AuthorityConfigurationProperties.java | 19 +++ .../auth/repository/AuthorityRepository.java | 3 + .../engine/auth/service/AuthorityService.java | 21 +++ 11 files changed, 344 insertions(+), 4 deletions(-) create mode 100644 nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorizations.java create mode 100644 nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Authorize.java create mode 100644 nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/BaseAuthorizationServiceAspect.java create mode 100644 nae-user-common/src/main/java/com/netgrif/application/engine/auth/config/AuthorityConfigurationProperties.java diff --git a/nae-object-library/pom.xml b/nae-object-library/pom.xml index f9b71b3efea..f17366d26ce 100644 --- a/nae-object-library/pom.xml +++ b/nae-object-library/pom.xml @@ -121,6 +121,12 @@ junit-jupiter test + + org.jetbrains + annotations + 26.0.2-1 + compile + 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/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-user-ce/pom.xml b/nae-user-ce/pom.xml index 4823d85744d..d51d4a8049f 100644 --- a/nae-user-ce/pom.xml +++ b/nae-user-ce/pom.xml @@ -142,6 +142,10 @@ 1.3.2 provided + + org.aspectj + aspectjweaver + 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..554f6d3ae3e 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) { @@ -74,4 +78,112 @@ public Page search(AuthoritySearchDto searchDto, Pageable pageable) { List 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 = 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 findByScope(String scope) { + List 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 = 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 findOptionalByName(String id) { + return authorityRepository.findByName(id); + } + + /** + * Returns the default authorities for simple user + * @return set of authorities + * */ + @Override + @Cacheable("defaultUserAuthoritiesCache") + public Set 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 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 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..1e896f4a116 --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/BaseAuthorizationServiceAspect.java @@ -0,0 +1,127 @@ +package com.netgrif.application.engine.auth.service; + + +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.context.ApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +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 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 args = Arrays.asList(joinPoint.getArgs()); + List 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-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..4d324a42f14 --- /dev/null +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/config/AuthorityConfigurationProperties.java @@ -0,0 +1,19 @@ +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.List; + +@Data +@Component +@ConfigurationProperties(prefix = "netgrif.engine.authority") +public class AuthorityConfigurationProperties { + + private List defaultUserAuthorities; + + private List defaultAnonymousAuthorities; + + private List defaultAdminAuthorities; +} 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 * @return a {@link Page} containing the matching {@link Authority} entities */ Page findAllBy_idIn(Collection ids, Pageable pageable); + + List 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..5b877f1c434 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,21 @@ public interface AuthorityService { * @return a {@link Page} of {@link Authority} entities matching the search criteria. */ Page search(AuthoritySearchDto searchDto, Pageable pageable); + + void delete(String name); + + List findByScope(String scope); + + Authority findByName(String name); + + Optional findOptionalByName(String id); + + @Cacheable("defaultUserAuthoritiesCache") + Set getDefaultUserAuthorities(); + + @Cacheable("defaultAnonymousAuthoritiesCache") + Set getDefaultAnonymousAuthorities(); + + @Cacheable("defaultAdminAuthoritiesCache") + Set getDefaultAdminAuthorities(); } From fd3d06fdaf102d89e47591d1e4a252b4cc811efb Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 8 Sep 2026 14:47:01 +0200 Subject: [PATCH 2/9] Introduce Authority Management API with REST endpoints, `@Authorize` annotation support, and comprehensive documentation. --- .../engine/auth/web/AuthorityController.java | 93 +++++ docs/authorization/authority.md | 326 ++++++++++++++++++ .../requestbodies/NewAuthorityRequest.java | 10 + 3 files changed, 429 insertions(+) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthorityController.java create mode 100644 docs/authorization/authority.md create mode 100644 nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/requestbodies/NewAuthorityRequest.java 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..54524e1495a --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthorityController.java @@ -0,0 +1,93 @@ +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.interfaces.IAuthorityService; +import com.netgrif.application.engine.auth.web.requestbodies.NewAuthorityRequest; +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.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; + +@Slf4j +@RestController +@RequestMapping("/api/authority") +@ConditionalOnProperty( + value = "nae.user.web.enabled", + havingValue = "true", + matchIfMissing = true +) +@Tag(name = "Authority") +public class AuthorityController { + + @Autowired + private IAuthorityService authorityService; + + @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 [" + name + "] has been deleted successfully."); + 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 = "AUTHORITY_CREATE") + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public EntityModel create(@RequestBody NewAuthorityRequest request, Authentication auth) { + try { + Authority authority = authorityService.getOrCreate(request.name); + log.info("Authority [" + authority + "] has been created successfully."); + return AuthorityResource.of(authority); + } catch (IllegalArgumentException | ResourceNotFoundException e) { + log.error("Failed to create authority [" + request.name + "].", e); + return null; + } + } + + @Authorize(authority = "AUTHORITY_GET_ALL") + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/all", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public CollectionModel getAll(Authentication auth) { + return new AuthoritiesResources(authorityService.findAll()); + } + + @Authorize(authority = "AUTHORITY_VIEW") + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/{name}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public EntityModel getOne(@PathVariable("name") String name, Authentication auth) { + Optional authority = authorityService.findOptionalByName(name); + if (authority.isPresent()) { + return AuthorityResource.of(authority.get()); + } else { + log.error("Cannot find authority with name [" + name + "]."); + return null; + } + } + + @Authorize(authority = "AUTHORITY_VIEW") + @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @GetMapping(value = "/scope/{scope}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public CollectionModel getAllByScope(@PathVariable("scope") String scope, Authentication auth) { + return new AuthoritiesResources(authorityService.findByScope(scope)); + } +} diff --git a/docs/authorization/authority.md b/docs/authorization/authority.md new file mode 100644 index 00000000000..962452e41fa --- /dev/null +++ b/docs/authorization/authority.md @@ -0,0 +1,326 @@ +# 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 following authorizing objects are defined by the engine: + +**Process** + +- `PROCESS_UPLOAD` — import a new process +- `PROCESS_VIEW_ALL` — retrieve all processes imported by any user +- `PROCESS_VIEW_OWN` — retrieve only processes imported by the logged user +- `PROCESS_DELETE_ALL` — delete processes imported by any user +- `PROCESS_DELETE_OWN` — delete processes imported by the logged user + +**Filter** + +- `FILTER_UPLOAD` — upload a filter +- `FILTER_DELETE_OWN` — delete a filter created by the logged user +- `FILTER_DELETE_ALL` — delete a filter created 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 +- `USER_VIEW_ALL` — retrieve all users +- `USER_VIEW_SELF` — retrieve only the logged user + +**Group** + +- `GROUP_CREATE` — create a group +- `GROUP_DELETE_OWN` — delete a group created by the logged user +- `GROUP_DELETE_ALL` — delete a group created by any user +- `GROUP_ALL_ADD_USER` — add any user to any group +- `GROUP_OWN_ADD_USER` — add a user to a group owned by the logged user +- `GROUP_ALL_REMOVE_USER` — remove any user from any group +- `GROUP_OWN_REMOVE_USER` — remove a user from a group owned by the logged user +- `GROUP_VIEW_ALL` — retrieve any group +- `GROUP_VIEW_OWN` — retrieve a group of the logged user +- `GROUP_MEMBERSHIP_SELF` — manage the logged user's own group membership + +**Role** + +- `ROLE_ASSIGN_TO_USER` — assign a process role to a user + +**Authority** + +- `AUTHORITY_CREATE` — create an authority +- `AUTHORITY_DELETE` — delete an authority +- `AUTHORITY_VIEW` — retrieve an authority + +**Case** + +- `CASE_VIEW_ALL` — view all cases +- `CASE_CREATE` — create a case +- `CASE_DELETE` — delete a case +- `CASE_DATA_GET_ALL` — get all data of a case + +**Task** + +- `TASK_RELOAD` — reload tasks +- `TASK_ASSIGN` — assign a task +- `TASK_FINISH` — finish a task +- `TASK_CANCEL` — cancel a task +- `TASK_DELEGATE` — delegate a task +- `TASK_SAVE_DATA` — save data on a task + +**Elasticsearch** + +- `ELASTIC_REINDEX` — reindex the Elasticsearch database + +**LDAP** + +- `LDAP_GROUP_GET_ALL` — get all LDAP groups +- `LDAP_GROUP_ASSIGN_ROLES` — assign roles to LDAP groups + +> The enum also declares the `DEFAULT` value and the deprecated `ADMIN` and `USER` +> values, which are kept for backward compatibility and should not be used in new code. + +### 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=FILTER_UPLOAD,FILTER_DELETE_OWN,USER_EDIT_SELF,GROUP_OWN_ADD_USER,... +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`, `AUTHORITY_DELETE`, and `AUTHORITY_VIEW`: + + ```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-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; + +} From 0151d77a9b8a45aeb2fa02db6ce6559c5315e885 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 9 Sep 2026 10:45:38 +0200 Subject: [PATCH 3/9] Enhance Authority Management: introduce `AuthorizingObject` enum, improve `@Authorize` annotation functionality, add `AuthorityDto`, refactor REST endpoints, and update documentation. --- .../logic/action/ActionDelegate.groovy | 9 +++ .../engine/auth/web/AuthorityController.java | 52 +++++++++------- docs/authorization/authority.md | 13 ++-- .../auth/constants/AuthorizingObject.java | 60 +++++++++++++++++++ .../auth/web/responsebodies/AuthorityDto.java | 17 ++++++ 5 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/constants/AuthorizingObject.java create mode 100644 nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/AuthorityDto.java 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..2df9ff46b7b 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 @@ -29,6 +29,7 @@ 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.menu.services.interfaces.DashboardItemService @@ -1409,6 +1410,8 @@ class ActionDelegate extends DelegateExpando { mailService.sendMail(mailDraft) } + @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") def changeUserByEmail(String email) { [email : { cl -> changeUserByEmail(email, "email", cl) @@ -1425,6 +1428,8 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().id.equals(#id)") def changeUser(String id) { [email : { cl -> changeUser(id, "email", cl) @@ -1441,6 +1446,8 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().id.equals(#user.id)") def changeUser(AbstractUser user) { [email : { cl -> changeUser(user, "email", cl) @@ -1457,6 +1464,8 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") def changeUserByEmail(String email, String attribute, def cl) { Optional userOptional = userService.findUserByUsername(email, null) if (!userOptional.isPresent()) { 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 index 54524e1495a..0a4fb2d3c58 100644 --- 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 @@ -1,8 +1,9 @@ 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.interfaces.IAuthorityService; +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; @@ -12,14 +13,17 @@ 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.CollectionModel; -import org.springframework.hateoas.EntityModel; 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 @@ -34,60 +38,64 @@ public class AuthorityController { @Autowired - private IAuthorityService authorityService; + private AuthorityService authorityService; - @Authorize(authority = "AUTHORITY_DELETE") + @Authorize(authority = {"AUTHORITY_DELETE", "ADMIN"}) @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 [" + name + "] has been deleted successfully."); + 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); + log.error("Failed to delete authority [{}].", name, e); return new MessageResource(ResponseMessage.createErrorMessage("Failed to delete authority.")); } } - @Authorize(authority = "AUTHORITY_CREATE") + @Authorize(authority = {"AUTHORITY_CREATE", "ADMIN"}) @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public EntityModel create(@RequestBody NewAuthorityRequest request, Authentication auth) { + public ResponseEntity create(@RequestBody NewAuthorityRequest request) { try { Authority authority = authorityService.getOrCreate(request.name); - log.info("Authority [" + authority + "] has been created successfully."); - return AuthorityResource.of(authority); + 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); + log.error("Failed to create authority [{}].", request.name, e); return null; } } - @Authorize(authority = "AUTHORITY_GET_ALL") + @Authorize(authority = {"AUTHORITY_GET_ALL", "ADMIN"}) @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/all", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public CollectionModel getAll(Authentication auth) { - return new AuthoritiesResources(authorityService.findAll()); + 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())); } - @Authorize(authority = "AUTHORITY_VIEW") + @Authorize(authority = {"AUTHORITY_VIEW", "ADMIN"}) @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/{name}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public EntityModel getOne(@PathVariable("name") String name, Authentication auth) { + public ResponseEntity getOne(@PathVariable("name") String name) { Optional authority = authorityService.findOptionalByName(name); if (authority.isPresent()) { - return AuthorityResource.of(authority.get()); + return ResponseEntity.ok(new AuthorityDto(authority.get())); } else { - log.error("Cannot find authority with name [" + name + "]."); + log.error("Cannot find authority with name [{}].", name); return null; } } - @Authorize(authority = "AUTHORITY_VIEW") + @Authorize(authority = {"AUTHORITY_VIEW", "ADMIN"}) @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/scope/{scope}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public CollectionModel getAllByScope(@PathVariable("scope") String scope, Authentication auth) { - return new AuthoritiesResources(authorityService.findByScope(scope)); + 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/docs/authorization/authority.md b/docs/authorization/authority.md index 962452e41fa..993038c23ba 100644 --- a/docs/authorization/authority.md +++ b/docs/authorization/authority.md @@ -51,7 +51,7 @@ application startup by the `AuthorityRunner`. ### Predefined authorizing objects -The following authorizing objects are defined by the engine: +The application engine defines the following authorizing objects: **Process** @@ -124,8 +124,7 @@ The following authorizing objects are defined by the engine: - `LDAP_GROUP_GET_ALL` — get all LDAP groups - `LDAP_GROUP_ASSIGN_ROLES` — assign roles to LDAP groups -> The enum also declares the `DEFAULT` value and the deprecated `ADMIN` and `USER` -> values, which are kept for backward compatibility and should not be used in new code. +> The enum also declares the default values as `ADMIN` and `USER` values. ### Custom authorizing objects @@ -156,7 +155,7 @@ Newly created users receive a set of default authorities. These defaults are con per user type using scopes and concrete authority names: ```properties -nae.authority.defaultUserAuthorities=FILTER_UPLOAD,FILTER_DELETE_OWN,USER_EDIT_SELF,GROUP_OWN_ADD_USER,... +nae.authority.defaultUserAuthorities=FILTER_UPLOAD,FILTER_DELETE_OWN,USER_EDIT_OWN,GROUP_OWN_ADD_USER,... nae.authority.defaultAnonymousAuthorities=... nae.authority.defaultAdminAuthorities=* ``` @@ -227,7 +226,7 @@ The annotation targets both **methods** and **types**, so it can be applied to: - Spring beans (e.g. `@userService`). ```groovy -@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") +@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().email.equals(#email)") def changeUserByEmail(String email) { // ... } @@ -269,11 +268,11 @@ are combined with a logical **OR** — the user is authorized if **at least one* 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: +hold `USER_EDIT_OWN` and are editing their own account: ```groovy @Authorize(authority = "USER_EDIT_ALL") -@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().stringId.equals(#id)") +@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().stringId.equals(#id)") def changeUser(String id) { // ... } 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..f120b0225c6 --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/constants/AuthorizingObject.java @@ -0,0 +1,60 @@ +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, + PROCESS_UPLOAD, + PROCESS_VIEW_ALL, + PROCESS_VIEW_OWN, + PROCESS_DELETE_ALL, + PROCESS_DELETE_OWN, + FILTER_UPLOAD, + FILTER_DELETE_ALL, + FILTER_DELETE_OWN, + USER_CREATE, + USER_DELETE, + USER_EDIT_ALL, + USER_EDIT_SELF, + USER_VIEW_ALL, + USER_VIEW_SELF, + GROUP_CREATE, + GROUP_DELETE_OWN, + GROUP_DELETE_ALL, + GROUP_ALL_ADD_USER, + GROUP_OWN_ADD_USER, + GROUP_ALL_REMOVE_USER, + GROUP_OWN_REMOVE_USER, + GROUP_VIEW_ALL, + GROUP_VIEW_OWN, + GROUP_MEMBERSHIP_SELF, + ROLE_ASSIGN_TO_USER, + AUTHORITY_CREATE, + AUTHORITY_DELETE, + AUTHORITY_VIEW, + CASE_VIEW_ALL, + CASE_CREATE, + CASE_DELETE, + CASE_DATA_GET_ALL, + TASK_RELOAD, + TASK_ASSIGN, + TASK_FINISH, + TASK_CANCEL, + TASK_DELEGATE, + TASK_SAVE_DATA, + ELASTIC_REINDEX, + LDAP_GROUP_GET_ALL, + LDAP_GROUP_ASSIGN_ROLES; + + public static List stringValues() { + return Arrays.stream(AuthorizingObject.values()).map(Enum::name).collect(Collectors.toList()); + } +} 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(); + } +} From 758404dcc0e9a8f6cd877aa6cc748473d24a4a34 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 9 Sep 2026 13:50:06 +0200 Subject: [PATCH 4/9] Refactor authorization system: adjust `@Authorize` annotation parameter handling, enhance `AuthorityConfigurationProperties` with documentation, improve `AuthorityRunner` authority creation logic, and extend `AuthorizingObject` enum. --- .../logic/action/ActionDelegate.groovy | 8 +-- .../startup/runner/AuthorityRunner.java | 17 ++++-- .../auth/constants/AuthorizingObject.java | 2 + .../BaseAuthorizationServiceAspect.java | 8 ++- .../AuthorityConfigurationProperties.java | 55 ++++++++++++++++++- 5 files changed, 73 insertions(+), 17 deletions(-) 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 2df9ff46b7b..465a5ba4ea1 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 @@ -1411,7 +1411,7 @@ class ActionDelegate extends DelegateExpando { } @Authorize(authority = "USER_EDIT_ALL") - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUserByEmail(String email) { [email : { cl -> changeUserByEmail(email, "email", cl) @@ -1428,8 +1428,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = "USER_EDIT_ALL") - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().id.equals(#id)") def changeUser(String id) { [email : { cl -> changeUser(id, "email", cl) @@ -1446,8 +1444,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = "USER_EDIT_ALL") - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().id.equals(#user.id)") def changeUser(AbstractUser user) { [email : { cl -> changeUser(user, "email", cl) @@ -1464,8 +1460,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = "USER_EDIT_ALL") - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") def changeUserByEmail(String email, String attribute, def cl) { Optional userOptional = userService.findUserByUsername(email, null) if (!userOptional.isPresent()) { 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/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 index f120b0225c6..303daeca9b8 100644 --- 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 @@ -12,6 +12,8 @@ public enum AuthorizingObject { ADMIN, USER, + SYSTEMADMIN, + ANONYMOUS, PROCESS_UPLOAD, PROCESS_VIEW_ALL, PROCESS_VIEW_OWN, 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 index 1e896f4a116..be8b4bbe92b 100644 --- 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 @@ -11,6 +11,8 @@ import org.aspectj.lang.reflect.MethodSignature; 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; @@ -112,9 +114,11 @@ protected boolean isAllowedByExpression(ProceedingJoinPoint joinPoint, String ex } List args = Arrays.asList(joinPoint.getArgs()); - List argNames = Arrays.asList(((MethodSignature) joinPoint.getSignature()).getParameterNames()); - argNames.forEach(name -> evaluationContext.setVariable(name, args.get(argNames.indexOf(name)))); + for (int i = 0; i < args.size(); i++) { + evaluationContext.setVariable("arg" + i, args.get(i)); + } + boolean allowed; try { allowed = ExpressionUtils.evaluateAsBoolean(parser.parseExpression(expression), evaluationContext); 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 index 4d324a42f14..95f74d66c4b 100644 --- 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 @@ -4,16 +4,65 @@ 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. + *

+ * This class provides configuration for default authorities assigned to different user types + * and allows defining additional custom authorizing objects. + *

+ * Configuration properties are bound with the prefix {@code netgrif.engine.authority}. + *

+ * Example configuration in application.properties or application.yml: + *

+ * 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
+ * 
+ * + * @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 { - private List defaultUserAuthorities; + /** + * List of default authorities assigned to regular users upon creation. + *

+ * These authorities define the baseline permissions for standard user accounts + * in the application. + */ + private List defaultUserAuthorities = new ArrayList<>(); - private List defaultAnonymousAuthorities; + /** + * List of default authorities assigned to anonymous (unauthenticated) users. + *

+ * These authorities define the permissions available to users who are not + * logged into the system. + */ + private List defaultAnonymousAuthorities = new ArrayList<>(); - private List defaultAdminAuthorities; + /** + * List of default authorities assigned to administrator users. + *

+ * These authorities define elevated permissions for users with administrative + * privileges in the application. + */ + private List defaultAdminAuthorities = new ArrayList<>(); + + /** + * List of additional custom authorizing objects to be created at application startup. + *

+ * 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 additionalAuthorizingObjects = new ArrayList<>(); } From 1eecccf75385c96ae08e61823ef6abe029068fcb Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 16 Sep 2026 15:04:39 +0200 Subject: [PATCH 5/9] Replace `@PreAuthorize` with `@Authorize` annotations across the codebase to standardize authorization handling and improve maintainability. Extend `AuthorizingObject` enum and refactor related methods, services, and documentation. --- .../logic/action/ActionDelegate.groovy | 61 ++++++++++++++++--- .../engine/auth/web/UserController.java | 9 +-- .../engine/elastic/web/ElasticController.java | 5 +- .../manager/web/SessionManagerController.java | 7 ++- .../orgstructure/web/GroupController.java | 2 +- .../petrinet/web/PetriNetController.java | 9 ++- .../petrinet/web/ProcessRoleController.java | 2 +- .../workflow/web/PublicTaskController.java | 17 +++--- .../web/PublicWorkflowController.java | 2 +- .../engine/workflow/web/TaskController.java | 19 +++--- .../workflow/web/WorkflowController.java | 7 ++- docs/authorization/authority.md | 39 ++---------- .../auth/constants/AuthorizingObject.java | 25 ++------ .../engine/auth/service/UserServiceImpl.java | 13 ++++ .../engine/auth/service/UserService.java | 9 +++ 15 files changed, 130 insertions(+), 96 deletions(-) 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 465a5ba4ea1..b5fc77e5e06 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 @@ -1156,43 +1156,51 @@ class ActionDelegate extends DelegateExpando { refs.find { it.transitionId == transitionId }.stringId } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleMongoId, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.addRole(user, roleMongoId) return actualUser } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, String netId, AbstractUser user = userService.loggedUser, Pageable pageable = Pageable.unpaged()) { List nets = petriNetService.getByIdentifier(netId, pageable).content nets.forEach({ net -> user = assignRole(roleId, net, user) }) return user } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, PetriNet net, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.addRole(user, net.roles.values().find { role -> role.importId == roleId }.stringId) return actualUser } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, String netId, Version version, AbstractUser user = userService.loggedUser) { PetriNet net = petriNetService.getPetriNet(netId, version) return assignRole(roleId, net, user) } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleMongoId, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.removeRole(user, roleMongoId) return actualUser } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, String netId, AbstractUser user = userService.loggedUser, Pageable pageable = Pageable.unpaged()) { List nets = petriNetService.getByIdentifier(netId, pageable).content nets.forEach({ net -> user = removeRole(roleId, net, user) }) return user } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, PetriNet net, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.removeRole(user, net.roles.values().find { role -> role.importId == roleId }.stringId) return actualUser } + @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, String netId, Version version, AbstractUser user = userService.loggedUser) { PetriNet net = petriNetService.getPetriNet(netId, version) return removeRole(roleId, net, user) @@ -1410,17 +1418,23 @@ class ActionDelegate extends DelegateExpando { mailService.sendMail(mailDraft) } - @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUserByEmail(String email) { [email : { cl -> 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) @@ -1428,15 +1442,23 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(String id) { [email : { cl -> 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) @@ -1444,15 +1466,23 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(AbstractUser user) { [email : { cl -> 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) @@ -1460,8 +1490,10 @@ class ActionDelegate extends DelegateExpando { ] } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") 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 @@ -1470,11 +1502,15 @@ class ActionDelegate extends DelegateExpando { changeUser(user, attribute, cl) } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(String id, String attribute, def cl) { AbstractUser user = userService.findById(id, null) changeUser(user, attribute, cl) } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(AbstractUser user, String attribute, def cl) { if (user == null) { log.error("Cannot find user.") @@ -1490,6 +1526,8 @@ class ActionDelegate extends DelegateExpando { userService.saveUser(user, null) } + @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") MessageResource inviteUser(String email) { NewUserRequest newUserRequest = new NewUserRequest() newUserRequest.email = email @@ -1498,6 +1536,7 @@ class ActionDelegate extends DelegateExpando { return inviteUser(newUserRequest) } + @Authorize(authority = ["USER_CREATE", "ADMIN"]) MessageResource inviteUser(NewUserRequest newUserRequest) { AbstractUser user = registrationService.createNewUser(newUserRequest) if (user == null) @@ -1508,6 +1547,7 @@ class ActionDelegate extends DelegateExpando { return MessageResource.successMessage("Done") } + @Authorize(authority = ["USER_DELETE", "ADMIN"]) void deleteUser(String email) { AbstractUser user = userService.findByEmail(email, null) if (user == null) { @@ -1517,6 +1557,7 @@ class ActionDelegate extends DelegateExpando { deleteUser(user) } + @Authorize(authority = ["USER_DELETE", "ADMIN"]) void deleteUser(AbstractUser user) { Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()) Page tasksAssignedToUserPage = taskService.findByUser(pageable, user) @@ -1537,6 +1578,8 @@ class ActionDelegate extends DelegateExpando { userService.deleteUser(user) } + @Authorize(authority = ["USER_VIEW_ALL", "ADMIN"]) + @Authorize(authority = "USER_VIEW_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") AbstractUser findUserByEmail(String email) { Optional userOpt = userService.findUserByUsername(email, null) if (userOpt.isEmpty()) { @@ -1547,6 +1590,8 @@ class ActionDelegate extends DelegateExpando { } } + @Authorize(authority = ["USER_VIEW_ALL", "ADMIN"]) + @Authorize(authority = "USER_VIEW_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") AbstractUser findUserById(String id) { AbstractUser user = userService.findById(id, null) if (user == null) { 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..abde08a5391 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,7 @@ public class UserController { private final RealmService realmService; private final UserFactory userFactory; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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"), @@ -209,7 +210,7 @@ public ResponseEntity getUser(@PathVariable("realmId") String realmId, @Pa // Page page = userService.findAllActiveByProcessRoles(roleResourceIds, pageable); // return ResponseEntity.ok(); // } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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 = { @@ -253,7 +254,7 @@ public ResponseEntity assignRolesToUser(@PathVariable("realmId" // } // } // - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "AUTHORITY_VIEW"}) @Operation(summary = "Get all authorities of the system", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @@ -267,7 +268,7 @@ public ResponseEntity> getAllAuthorities() { return ResponseEntity.ok(authorityService.findAll(Pageable.unpaged()).stream().toList()); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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/elastic/web/ElasticController.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java index 6a4e9235999..54b4cfb93b8 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,7 @@ public void setIndexService(IElasticIndexService indexService) { this.indexService = indexService; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "ELASTIC_REINDEX"}) @Operation(summary = "Reindex specified cases", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -117,7 +118,7 @@ public MessageResource reindex(@RequestBody Map searchBody, Auth } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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..121c7ccd219 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..f3d1d256f2e 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 @@ -37,7 +37,7 @@ public GroupController(GroupService service) { this.service = service; } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(expression = "@authorizationService.hasAuthority('ADMIN')") @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/web/PetriNetController.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java index d4b062650c2..42b7f725b82 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,7 @@ public static String decodeUrl(String s1) { } } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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 +127,7 @@ public EntityModel importPetriNet( } } + @Authorize(authority = {"ADMIN", "PROCESS_VIEW_ALL"}) @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 +184,7 @@ public TransactionsResource getTransactions(@PathVariable("netId") String netId, return new TransactionsResource(net.getTransactions().values(), netId, locale); } - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @Authorize(authority = {"ADMIN", "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 +222,8 @@ PagedModel searchElasticPetriNets(@RequestBody PetriN return resources; } - @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") + @Authorize(authority = {"ADMIN", "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..32f818883bf 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java @@ -34,7 +34,7 @@ public class ProcessRoleController { private final ProcessRoleService processRoleService; - @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") + @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/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..00581711cd1 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 @@ -44,7 +44,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..f6b55d61f4d 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/docs/authorization/authority.md b/docs/authorization/authority.md index 993038c23ba..35e496b8008 100644 --- a/docs/authorization/authority.md +++ b/docs/authorization/authority.md @@ -1,4 +1,4 @@ -# Authority System +Ple# Authority System The Netgrif Application Engine (NAE) uses **authorities** to protect resources and operations from unauthorized access. Authorities are application-wide permissions that @@ -56,16 +56,9 @@ The application engine defines the following authorizing objects: **Process** - `PROCESS_UPLOAD` — import a new process -- `PROCESS_VIEW_ALL` — retrieve all processes imported by any user -- `PROCESS_VIEW_OWN` — retrieve only processes imported by the logged user -- `PROCESS_DELETE_ALL` — delete processes imported by any user -- `PROCESS_DELETE_OWN` — delete processes imported by the logged user - -**Filter** - -- `FILTER_UPLOAD` — upload a filter -- `FILTER_DELETE_OWN` — delete a filter created by the logged user -- `FILTER_DELETE_ALL` — delete a filter created by any user +- `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** @@ -98,32 +91,12 @@ The application engine defines the following authorizing objects: - `AUTHORITY_CREATE` — create an authority - `AUTHORITY_DELETE` — delete an authority - `AUTHORITY_VIEW` — retrieve an authority - -**Case** - -- `CASE_VIEW_ALL` — view all cases -- `CASE_CREATE` — create a case -- `CASE_DELETE` — delete a case -- `CASE_DATA_GET_ALL` — get all data of a case - -**Task** - -- `TASK_RELOAD` — reload tasks -- `TASK_ASSIGN` — assign a task -- `TASK_FINISH` — finish a task -- `TASK_CANCEL` — cancel a task -- `TASK_DELEGATE` — delegate a task -- `TASK_SAVE_DATA` — save data on a task +- `AUTHORITY_ASSIGN_TO_USER` — assign an authority to a user **Elasticsearch** - `ELASTIC_REINDEX` — reindex the Elasticsearch database -**LDAP** - -- `LDAP_GROUP_GET_ALL` — get all LDAP groups -- `LDAP_GROUP_ASSIGN_ROLES` — assign roles to LDAP groups - > The enum also declares the default values as `ADMIN` and `USER` values. ### Custom authorizing objects @@ -155,7 +128,7 @@ Newly created users receive a set of default authorities. These defaults are con per user type using scopes and concrete authority names: ```properties -nae.authority.defaultUserAuthorities=FILTER_UPLOAD,FILTER_DELETE_OWN,USER_EDIT_OWN,GROUP_OWN_ADD_USER,... +nae.authority.defaultUserAuthorities=USER,PROCESS_VIEW,USER_EDIT_SELF,USER_VIEW_SELF,GROUP_VIEW_OWN,GROUP_DELETE_OWN,GROUP_MEMBERSHIP_SELF nae.authority.defaultAnonymousAuthorities=... nae.authority.defaultAdminAuthorities=* ``` 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 index 303daeca9b8..0c43361941e 100644 --- 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 @@ -15,13 +15,9 @@ public enum AuthorizingObject { SYSTEMADMIN, ANONYMOUS, PROCESS_UPLOAD, - PROCESS_VIEW_ALL, - PROCESS_VIEW_OWN, - PROCESS_DELETE_ALL, - PROCESS_DELETE_OWN, - FILTER_UPLOAD, - FILTER_DELETE_ALL, - FILTER_DELETE_OWN, + PROCESS_DOWNLOAD, + PROCESS_VIEW, + PROCESS_DELETE, USER_CREATE, USER_DELETE, USER_EDIT_ALL, @@ -39,22 +35,11 @@ public enum AuthorizingObject { GROUP_VIEW_OWN, GROUP_MEMBERSHIP_SELF, ROLE_ASSIGN_TO_USER, + AUTHORITY_ASSIGN_TO_USER, AUTHORITY_CREATE, AUTHORITY_DELETE, AUTHORITY_VIEW, - CASE_VIEW_ALL, - CASE_CREATE, - CASE_DELETE, - CASE_DATA_GET_ALL, - TASK_RELOAD, - TASK_ASSIGN, - TASK_FINISH, - TASK_CANCEL, - TASK_DELEGATE, - TASK_SAVE_DATA, - ELASTIC_REINDEX, - LDAP_GROUP_GET_ALL, - LDAP_GROUP_ASSIGN_ROLES; + ELASTIC_REINDEX; public static List stringValues() { return Arrays.stream(AuthorizingObject.values()).map(Enum::name).collect(Collectors.toList()); 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..07a893b56f4 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 @@ -156,6 +156,19 @@ public Optional findUserByUsername(String username, String realmId return userOpt; } + @Override + public Optional findUserByEmail(String email, String realmId) { + log.debug("Finding user by username [{}] in realm [{}]", email, realmId); + String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId); + Optional 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 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/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 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 findUserByEmail(String email, String realmId); + Page findAllUsersByQuery(Query query, String realmName, Pageable pageable); /** From 06004798464a227a1b2c464bae9b5c7f16f72b54 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Thu, 17 Sep 2026 13:00:37 +0200 Subject: [PATCH 6/9] Add `@Authorize` annotation to controllers for standardized authorization handling --- .../application/engine/orgstructure/web/GroupController.java | 1 + .../application/engine/petrinet/web/ProcessRoleController.java | 1 + .../engine/workflow/web/PublicWorkflowController.java | 1 + 3 files changed, 3 insertions(+) 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 f3d1d256f2e..92b57388ae2 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; 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 32f818883bf..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; 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 00581711cd1..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; From 0a1bd73e93b36a931c89dbce369662817ad3ec66 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Mon, 21 Sep 2026 09:59:58 +0200 Subject: [PATCH 7/9] Standardize `@Authorize` usage: decompose composite authorities and adjust method parameter types --- .../logic/action/ActionDelegate.groovy | 24 +- .../auth/service/RegistrationService.java | 3 + .../engine/auth/web/AuthorityController.java | 16 +- .../engine/auth/web/UserController.java | 69 +- .../engine/elastic/web/ElasticController.java | 6 +- .../manager/web/SessionManagerController.java | 6 +- .../orgstructure/web/GroupController.java | 3 +- .../petrinet/service/PetriNetService.java | 9 + .../petrinet/service/ProcessRoleService.java | 5 + .../petrinet/web/PetriNetController.java | 12 +- .../service/TaskAuthorizationService.java | 21 +- .../engine/workflow/service/TaskService.java | 5 + .../workflow/service/WorkflowService.java | 4 + .../interfaces/ITaskAuthorizationService.java | 19 +- .../workflow/web/WorkflowController.java | 2 +- .../ServiceMethodAuthorizationTest.groovy | 60 ++ .../petriNets/service_methods_test.xml | 794 ++++++++++++++++++ .../test/resources/service_methods_test.xml | 794 ++++++++++++++++++ docs/authorization/authority.md | 37 +- .../auth/constants/AuthorizingObject.java | 23 +- .../spring/utils/NaeReflectionUtils.java | 80 +- .../BaseAuthorizationServiceAspect.java | 12 +- .../engine/auth/service/GroupServiceImpl.java | 16 + .../engine/auth/service/UserServiceImpl.java | 9 + 24 files changed, 1868 insertions(+), 161 deletions(-) create mode 100644 application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy create mode 100644 application-engine/src/test/resources/petriNets/service_methods_test.xml create mode 100644 application-engine/src/test/resources/service_methods_test.xml 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 b5fc77e5e06..f1984cfb567 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 @@ -1418,8 +1418,6 @@ class ActionDelegate extends DelegateExpando { mailService.sendMail(mailDraft) } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUserByEmail(String email) { [email : { cl -> changeUserByEmail(email, "email", cl) @@ -1442,8 +1440,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(String id) { [email : { cl -> changeUser(id, "email", cl) @@ -1466,8 +1462,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(AbstractUser user) { [email : { cl -> changeUser(user, "email", cl) @@ -1490,8 +1484,6 @@ class ActionDelegate extends DelegateExpando { ] } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUserByEmail(String email, String attribute, def cl) { Optional userOptional = userService.findUserByEmail(email, null) if (!userOptional.isPresent()) { @@ -1502,15 +1494,14 @@ class ActionDelegate extends DelegateExpando { changeUser(user, attribute, cl) } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") def changeUser(String id, String attribute, def cl) { AbstractUser user = userService.findById(id, null) changeUser(user, attribute, cl) } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_EDIT_ALL") + @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().username.equals(#arg0.username)") def changeUser(AbstractUser user, String attribute, def cl) { if (user == null) { log.error("Cannot find user.") @@ -1526,8 +1517,6 @@ class ActionDelegate extends DelegateExpando { userService.saveUser(user, null) } - @Authorize(authority = ["USER_EDIT_ALL", "ADMIN"]) - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") MessageResource inviteUser(String email) { NewUserRequest newUserRequest = new NewUserRequest() newUserRequest.email = email @@ -1536,7 +1525,6 @@ class ActionDelegate extends DelegateExpando { return inviteUser(newUserRequest) } - @Authorize(authority = ["USER_CREATE", "ADMIN"]) MessageResource inviteUser(NewUserRequest newUserRequest) { AbstractUser user = registrationService.createNewUser(newUserRequest) if (user == null) @@ -1547,7 +1535,6 @@ class ActionDelegate extends DelegateExpando { return MessageResource.successMessage("Done") } - @Authorize(authority = ["USER_DELETE", "ADMIN"]) void deleteUser(String email) { AbstractUser user = userService.findByEmail(email, null) if (user == null) { @@ -1557,7 +1544,6 @@ class ActionDelegate extends DelegateExpando { deleteUser(user) } - @Authorize(authority = ["USER_DELETE", "ADMIN"]) void deleteUser(AbstractUser user) { Pageable pageable = PageRequest.of(0, paginationProperties.getBackendPageSize()) Page tasksAssignedToUserPage = taskService.findByUser(pageable, user) @@ -1578,8 +1564,6 @@ class ActionDelegate extends DelegateExpando { userService.deleteUser(user) } - @Authorize(authority = ["USER_VIEW_ALL", "ADMIN"]) - @Authorize(authority = "USER_VIEW_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") AbstractUser findUserByEmail(String email) { Optional userOpt = userService.findUserByUsername(email, null) if (userOpt.isEmpty()) { @@ -1590,8 +1574,6 @@ class ActionDelegate extends DelegateExpando { } } - @Authorize(authority = ["USER_VIEW_ALL", "ADMIN"]) - @Authorize(authority = "USER_VIEW_SELF", expression = "@userService.getLoggedUser().email.equals(#arg0)") AbstractUser findUserById(String id) { AbstractUser user = userService.findById(id, null) if (user == null) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java index 4ae6841c4ca..e2fdcb9564b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java @@ -3,6 +3,7 @@ import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.User; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; import com.netgrif.application.engine.auth.service.interfaces.IRegistrationService; @@ -120,6 +121,8 @@ public boolean stringMatchesUserPassword(AbstractUser user, String passwordToCom @Override @Transactional + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_CREATE") public AbstractUser createNewUser(NewUserRequest newUser) { User user = (User) userService.findByEmail(newUser.email, null); if (user != null) { 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 index 0a4fb2d3c58..699bf290982 100644 --- 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 @@ -10,6 +10,7 @@ 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; @@ -29,6 +30,7 @@ @Slf4j @RestController @RequestMapping("/api/authority") +@RequiredArgsConstructor @ConditionalOnProperty( value = "nae.user.web.enabled", havingValue = "true", @@ -37,10 +39,10 @@ @Tag(name = "Authority") public class AuthorityController { - @Autowired - private AuthorityService authorityService; + private final AuthorityService authorityService; - @Authorize(authority = {"AUTHORITY_DELETE", "ADMIN"}) + @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) { @@ -54,8 +56,9 @@ public MessageResource delete(@PathVariable String name, Authentication auth) { } } - @Authorize(authority = {"AUTHORITY_CREATE", "ADMIN"}) - @Operation(description = "Delete authority", security = {@SecurityRequirement(name = "BasicAuth")}) + @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 { @@ -68,7 +71,6 @@ public ResponseEntity create(@RequestBody NewAuthorityRequest requ } } - @Authorize(authority = {"AUTHORITY_GET_ALL", "ADMIN"}) @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) { @@ -77,7 +79,6 @@ public ResponseEntity> getAll(Pageable pageable) { return ResponseEntity.ok(new PageImpl<>(authorityDtoList, pageable, authorities.getTotalElements())); } - @Authorize(authority = {"AUTHORITY_VIEW", "ADMIN"}) @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) { @@ -90,7 +91,6 @@ public ResponseEntity getOne(@PathVariable("name") String name) { } } - @Authorize(authority = {"AUTHORITY_VIEW", "ADMIN"}) @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) { 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 abde08a5391..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 @@ -55,7 +55,8 @@ public class UserController { private final RealmService realmService; private final UserFactory userFactory; - @Authorize(authority = {"ADMIN", "USER_CREATE"}) + @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"), @@ -173,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(); -// } - @Authorize(authority = {"ADMIN", "ROLE_ASSIGN_TO_USER"}) + @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 = { @@ -232,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!")); -// } -// } -// - @Authorize(authority = {"ADMIN", "AUTHORITY_VIEW"}) @Operation(summary = "Get all authorities of the system", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "X-Auth-Token")}) @@ -268,7 +210,8 @@ public ResponseEntity> getAllAuthorities() { return ResponseEntity.ok(authorityService.findAll(Pageable.unpaged()).stream().toList()); } - @Authorize(authority = {"ADMIN", "AUTHORITY_ASSIGN_TO_USER"}) + @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/elastic/web/ElasticController.java b/application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java index 54b4cfb93b8..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 @@ -80,7 +80,8 @@ public void setIndexService(IElasticIndexService indexService) { this.indexService = indexService; } - @Authorize(authority = {"ADMIN", "ELASTIC_REINDEX"}) + @Authorize(authority = "ADMIN") + @Authorize(authority = "ELASTIC_REINDEX") @Operation(summary = "Reindex specified cases", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -118,7 +119,8 @@ public MessageResource reindex(@RequestBody Map searchBody, Auth } } - @Authorize(authority = {"ADMIN", "ELASTIC_REINDEX"}) + @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 121c7ccd219..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 @@ -34,7 +34,7 @@ public class SessionManagerController { @Autowired private ISessionManagerService sessionManagerService; - @Authorize(authority = {"ADMIN"}) + @Authorize(authority = "ADMIN") @Operation(summary = "Get All logged users", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -48,7 +48,7 @@ public AllLoggedUsersResponse getAllSessions() { return new AllLoggedUsersResponse(loggedUsers); } - @Authorize(authority = {"ADMIN"}) + @Authorize(authority = "ADMIN") @Operation(summary = "Logout current user", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -63,7 +63,7 @@ public MessageLogoutResponse logoutCurrentSession(@RequestBody LogoutRequest req return new MessageLogoutResponse(true); } - @Authorize(authority = {"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 92b57388ae2..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 @@ -38,7 +38,8 @@ public GroupController(GroupService service) { this.service = service; } - @Authorize(expression = "@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..205a04b8824 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 @@ -1,5 +1,6 @@ package com.netgrif.application.engine.petrinet.service; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.files.minio.StorageConfigurationProperties; @@ -178,6 +179,8 @@ public List get(List petriNetIds) { @Override @Transactional + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_UPLOAD") public ImportPetriNetEventOutcome importPetriNet(ImportPetriNetParams importPetriNetParams) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException { validateAttributes(importPetriNetParams); @@ -438,6 +441,8 @@ public Page getAllDefault(Pageable pageable) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_DOWNLOAD") public FileSystemResource getFile(String netId, String title) { if (title == null || title.isEmpty()) { Query query = Query.query(Criteria.where("_id").is(new ObjectId(netId))); @@ -629,11 +634,15 @@ protected void addValueCriteria(Query query, Query queryTotal, Criteria criteria @Override @Transactional + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_DELETE") public void deletePetriNet(DeletePetriNetParams deletePetriNetParams) { doDeletePetriNet(deletePetriNetParams, false); } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "PROCESS_DELETE") public void forceDeletePetriNet(DeletePetriNetParams deletePetriNetParams) { doDeletePetriNet(deletePetriNetParams, true); } 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..299cddf2cae 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; @@ -115,12 +116,16 @@ public void deleteAll() { @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "ROLE_ASSIGN_TO_USER") public void assignRolesToUser(AbstractUser user, Collection processResourceIds, LoggedUser loggedUser) { assignRolesToActor(user, processResourceIds); saveUserAndReloadContext(user, loggedUser); } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "ROLE_ASSIGN_TO_GROUP") public void assignRolesToGroup(Group group, Collection requestedRolesIds) { assignRolesToActor(group, requestedRolesIds); groupService.save(group); 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 42b7f725b82..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 @@ -96,7 +96,8 @@ public static String decodeUrl(String s1) { } } - @Authorize(authority = {"ADMIN", "PROCESS_UPLOAD"}) + @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")}) @@ -127,7 +128,8 @@ public EntityModel importPetriNet( } } - @Authorize(authority = {"ADMIN", "PROCESS_VIEW_ALL"}) + @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) { @@ -184,7 +186,8 @@ public TransactionsResource getTransactions(@PathVariable("netId") String netId, return new TransactionsResource(net.getTransactions().values(), netId, locale); } - @Authorize(authority = {"ADMIN", "PROCESS_DOWNLOAD"}) + @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) { @@ -222,7 +225,8 @@ PagedModel searchElasticPetriNets(@RequestBody PetriN return resources; } - @Authorize(authority = {"ADMIN", "PROCESS_DELETE"}) + @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.", 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/TaskService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 60e327f519b..a07a287efd0 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -2,6 +2,7 @@ import com.google.common.collect.Ordering; import com.netgrif.application.engine.auth.service.GroupService; +import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.petrinet.domain.dataset.ActorFieldValue; import com.netgrif.application.engine.objects.petrinet.domain.dataset.ActorListFieldValue; @@ -135,6 +136,7 @@ public List assignTasks(List tasks, AbstractUser u } @Override + @Authorize(expression = "@taskAuthorizationService.canCallAssign(#taskParams.getUser(), #taskParams.getTaskId())") public AssignTaskEventOutcome assignTask(TaskParams taskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(taskParams); @@ -205,6 +207,7 @@ public List finishTasks(List tasks, AbstractUser u } @Override + @Authorize(expression = "@taskAuthorizationService.canCallFinish(#taskParams.getUser(), #taskParams.getTaskId())") public FinishTaskEventOutcome finishTask(TaskParams taskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(taskParams); @@ -272,6 +275,7 @@ public List cancelTasks(List tasks, AbstractUser u } @Override + @Authorize(expression = "@taskAuthorizationService.canCallCancel(#taskParams.getUser(), #taskParams.getTaskId())") public CancelTaskEventOutcome cancelTask(TaskParams taskParams) { fillAndValidateAttributes(taskParams); @@ -413,6 +417,7 @@ private Case returnTokens(Task task, Case useCase) { } @Override + @Authorize(expression = "@taskAuthorizationService.canCallDelegate(#taskParams.getUser(), #taskParams.getTaskId())") public DelegateTaskEventOutcome delegateTask(DelegateTaskParams delegateTaskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(delegateTaskParams); 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..ec09daa709f 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 @@ -3,6 +3,7 @@ import com.google.common.collect.Ordering; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.GroupService; +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.petrinet.domain.dataset.*; @@ -312,6 +313,8 @@ private boolean actorExists(ActorFieldValue actorFieldValue) { } } + @Override + @Authorize(expression = "@workflowAuthorizationService.canCallCreate(#createCaseParams.getAuthor(), #createCaseParams.getProcessId())") public CreateCaseEventOutcome createCase(CreateCaseParams createCaseParams) { fillAndValidateAttributes(createCaseParams); PetriNet petriNet = createCaseParams.getProcess(); @@ -416,6 +419,7 @@ public Page findAllByAuthor(String authorId, String petriNet, Pageable pag } @Override + @Authorize(expression = "@workflowAuthorizationService.canCallDelete(@userService.getLoggedUserFromContext(), #deleteCaseParams.getUseCaseId())") public DeleteCaseEventOutcome deleteCase(DeleteCaseParams deleteCaseParams) { fillAndValidateAttributes(deleteCaseParams); 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/WorkflowController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java index f6b55d61f4d..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 @@ -189,7 +189,7 @@ public PagedModel findAllByAuthor(@PathVariable("id") String autho return resources; } - @Authorize(authority = {"ADMIN"}) + @Authorize(authority = "ADMIN") @Operation(summary = "Reload tasks of case", description = "Caller must have the ADMIN role", security = {@SecurityRequirement(name = "BasicAuth")}) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy new file mode 100644 index 00000000000..fc0fd28141b --- /dev/null +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/action/ServiceMethodAuthorizationTest.groovy @@ -0,0 +1,60 @@ +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) + } + } +} 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 @@ + + + service_methods_test + 1.0.0 + SMT + 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 index 35e496b8008..b01923c78c7 100644 --- a/docs/authorization/authority.md +++ b/docs/authorization/authority.md @@ -1,4 +1,4 @@ -Ple# Authority System +# Authority System The Netgrif Application Engine (NAE) uses **authorities** to protect resources and operations from unauthorized access. Authorities are application-wide permissions that @@ -66,38 +66,39 @@ The application engine defines the following authorizing objects: - `USER_DELETE` — remove a user - `USER_EDIT_ALL` — edit any user - `USER_EDIT_SELF` — edit only the logged user -- `USER_VIEW_ALL` — retrieve all users -- `USER_VIEW_SELF` — retrieve 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_DELETE_ALL` — delete a group created by any user -- `GROUP_ALL_ADD_USER` — add any user to any group -- `GROUP_OWN_ADD_USER` — add a user to a group owned by the logged user -- `GROUP_ALL_REMOVE_USER` — remove any user from any group -- `GROUP_OWN_REMOVE_USER` — remove a user from a group owned by the logged user -- `GROUP_VIEW_ALL` — retrieve any group -- `GROUP_VIEW_OWN` — retrieve a group of the logged user -- `GROUP_MEMBERSHIP_SELF` — manage the logged user's own group membership +- `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_VIEW` — retrieve 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` and `USER` values. +> The enum also declares the default values as `ADMIN`, `USER`, `SYSTEMADMIN`, and `ANONYMOUS` values. ### Custom authorizing objects @@ -128,7 +129,7 @@ Newly created users receive a set of default authorities. These defaults are con per user type using scopes and concrete authority names: ```properties -nae.authority.defaultUserAuthorities=USER,PROCESS_VIEW,USER_EDIT_SELF,USER_VIEW_SELF,GROUP_VIEW_OWN,GROUP_DELETE_OWN,GROUP_MEMBERSHIP_SELF +nae.authority.defaultUserAuthorities=USER,PROCESS_VIEW,USER_EDIT_SELF,GROUP_DELETE_OWN nae.authority.defaultAnonymousAuthorities=... nae.authority.defaultAdminAuthorities=* ``` @@ -153,7 +154,7 @@ 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`, `AUTHORITY_DELETE`, and `AUTHORITY_VIEW`: + `AUTHORITY_CREATE` and `AUTHORITY_DELETE`: ```java @Authorize(authority = "AUTHORITY_DELETE") @@ -199,7 +200,7 @@ The annotation targets both **methods** and **types**, so it can be applied to: - Spring beans (e.g. `@userService`). ```groovy -@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().email.equals(#email)") +@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)") def changeUserByEmail(String email) { // ... } @@ -241,11 +242,11 @@ are combined with a logical **OR** — the user is authorized if **at least one* the `@Authorizations` container annotation. In the example below the user is authorized if they hold `USER_EDIT_ALL`, **or** if they -hold `USER_EDIT_OWN` and are editing their own account: +hold `USER_EDIT_SELF` and are editing their own account: ```groovy @Authorize(authority = "USER_EDIT_ALL") -@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().stringId.equals(#id)") +@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().stringId.equals(#id)") def changeUser(String id) { // ... } 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 index 0c43361941e..bad0dcecc19 100644 --- 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 @@ -22,23 +22,24 @@ public enum AuthorizingObject { USER_DELETE, USER_EDIT_ALL, USER_EDIT_SELF, - USER_VIEW_ALL, - USER_VIEW_SELF, GROUP_CREATE, + GROUP_CREATE_OWN, + GROUP_DELETE, GROUP_DELETE_OWN, - GROUP_DELETE_ALL, - GROUP_ALL_ADD_USER, - GROUP_OWN_ADD_USER, - GROUP_ALL_REMOVE_USER, - GROUP_OWN_REMOVE_USER, - GROUP_VIEW_ALL, - GROUP_VIEW_OWN, - GROUP_MEMBERSHIP_SELF, + 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, - AUTHORITY_VIEW, ELASTIC_REINDEX; public static List<String> stringValues() { 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/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 index be8b4bbe92b..3f7a497100b 100644 --- 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 @@ -1,6 +1,7 @@ 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; @@ -9,6 +10,7 @@ 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; @@ -115,8 +117,13 @@ protected boolean isAllowedByExpression(ProceedingJoinPoint joinPoint, String ex List<Object> args = Arrays.asList(joinPoint.getArgs()); - for (int i = 0; i < args.size(); i++) { - evaluationContext.setVariable("arg" + i, args.get(i)); + 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; @@ -128,4 +135,5 @@ protected boolean isAllowedByExpression(ProceedingJoinPoint joinPoint, String ex 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..18b9e140873 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; @@ -82,6 +83,9 @@ public void setPaginationProperties(PaginationProperties paginationProperties) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_DELETE") + @Authorize(authority = "GROUP_DELETE_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public void delete(Group group) { if (!groupRepository.existsById(group.getStringId())) { log.error("Group [{}] does not exist", group.getStringId()); @@ -190,6 +194,9 @@ public Group create(AbstractUser groupOwner) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_CREATE") + @Authorize(authority = "GROUP_CREATE_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#groupOwner.getUsername())") public Group create(String identifier, String title, AbstractUser groupOwner) { log.info("Creating default group for user: [{}]", groupOwner.getStringId()); Group group = new com.netgrif.application.engine.adapter.spring.auth.domain.Group(identifier, groupOwner.getRealmId()); @@ -245,6 +252,9 @@ public Group addUser(AbstractUser user, String groupIdentifier) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_ADD_USER") + @Authorize(authority = "GROUP_ADD_USER_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public Group addUser(AbstractUser user, Group group) { log.info("Adding user [{}] to group [{}]", user.getStringId(), group.getStringId()); user.addGroupId(group.getStringId()); @@ -260,6 +270,9 @@ public Group removeUser(AbstractUser user, String groupIdentifier) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_REMOVE_USER") + @Authorize(authority = "GROUP_REMOVE_USER_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public Group removeUser(AbstractUser user, Group group) { log.info("Removing user [{}] from group [{}]", user.getStringId(), group.getStringId()); user.removeGroupId(group.getStringId()); @@ -333,6 +346,9 @@ public Pair<Group, Group> addSubgroup(String parentGroupId, Group childGroup) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "GROUP_ADD_SUBGROUP") + @Authorize(authority = "GROUP_ADD_SUBGROUP_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#parentGroup.getOwnerUsername())") public Pair<Group, Group> addSubgroup(Group parentGroup, Group childGroup) { // TODO: maybe handle groups cycles here? if (parentGroup.getStringId().equals(childGroup.getStringId())) { 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 07a893b56f4..0bb61dd5662 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; @@ -194,6 +195,8 @@ public AbstractUser createUser(String username, String email, String firstName, } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_CREATE") public AbstractUser createUser(AbstractUser user, String realmId) { log.info("Creating user [{}] in realm [{}]", user.getUsername(), realmId); setPassword(user, user.getPassword()); @@ -219,6 +222,8 @@ public AbstractUser createUser(AbstractUser user, String realmId) { // TODO JOFO: auth methods no longer exists ... use credentials? @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_CREATE") public User createUserFromThirdParty(String username, String email, String firstName, String lastName, String realmId, String authMethod) { log.info("Creating user [{}] from third-party auth [{}] in realm [{}] without password", username, authMethod, realmId); User user = initializeNewUser(username, email, firstName, lastName, "N/A", realmId); @@ -357,6 +362,8 @@ public AbstractUser findById(String id, String realmId) { } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "USER_DELETE") public void deleteUser(AbstractUser user) { log.warn("Deleting user [{}]", user.getUsername()); String collectionName = collectionNameProvider.getCollectionNameForRealm(user.getRealmId()); @@ -471,6 +478,8 @@ protected Page<AbstractUser> searchUsersByRoleIds(Collection<ProcessResourceId> } @Override + @Authorize(authority = "ADMIN") + @Authorize(authority = "AUTHORITY_ASSIGN_TO_USER") public AbstractUser assignAuthority(String userId, String realmId, String authorityId) { AbstractUser user = findById(userId, realmId); Authority authority = authorityService.getOne(authorityId); From e044e16e00515c388f6cc350fd2dd3378b86358a Mon Sep 17 00:00:00 2001 From: renczesstefan <renczes.stefan@gmail.com> Date: Mon, 21 Sep 2026 12:10:36 +0200 Subject: [PATCH 8/9] Remove `@Authorize` annotations from methods across multiple services and refactor related authorization logic --- .../dataset/logic/action/ActionDelegate.groovy | 11 ----------- .../engine/auth/service/RegistrationService.java | 3 --- .../engine/petrinet/service/PetriNetService.java | 10 ---------- .../petrinet/service/ProcessRoleService.java | 4 ---- .../engine/workflow/service/TaskService.java | 5 ----- .../engine/workflow/service/WorkflowService.java | 3 --- .../netgrif/application/engine/TestHelper.groovy | 9 +++++++++ .../engine/auth/service/GroupServiceImpl.java | 15 --------------- .../engine/auth/service/UserServiceImpl.java | 8 -------- 9 files changed, 9 insertions(+), 59 deletions(-) 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 f1984cfb567..b12a1dc8957 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 @@ -1156,51 +1156,43 @@ class ActionDelegate extends DelegateExpando { refs.find { it.transitionId == transitionId }.stringId } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleMongoId, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.addRole(user, roleMongoId) return actualUser } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, String netId, AbstractUser user = userService.loggedUser, Pageable pageable = Pageable.unpaged()) { List<PetriNet> nets = petriNetService.getByIdentifier(netId, pageable).content nets.forEach({ net -> user = assignRole(roleId, net, user) }) return user } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, PetriNet net, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.addRole(user, net.roles.values().find { role -> role.importId == roleId }.stringId) return actualUser } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser assignRole(String roleId, String netId, Version version, AbstractUser user = userService.loggedUser) { PetriNet net = petriNetService.getPetriNet(netId, version) return assignRole(roleId, net, user) } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleMongoId, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.removeRole(user, roleMongoId) return actualUser } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, String netId, AbstractUser user = userService.loggedUser, Pageable pageable = Pageable.unpaged()) { List<PetriNet> nets = petriNetService.getByIdentifier(netId, pageable).content nets.forEach({ net -> user = removeRole(roleId, net, user) }) return user } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, PetriNet net, AbstractUser user = userService.loggedUser) { AbstractUser actualUser = userService.removeRole(user, net.roles.values().find { role -> role.importId == roleId }.stringId) return actualUser } - @Authorize(authority = ["ROLE_ASSIGN_TO_USER", "ADMIN"]) AbstractUser removeRole(String roleId, String netId, Version version, AbstractUser user = userService.loggedUser) { PetriNet net = petriNetService.getPetriNet(netId, version) return removeRole(roleId, net, user) @@ -1499,9 +1491,6 @@ class ActionDelegate extends DelegateExpando { changeUser(user, attribute, cl) } - @Authorize(authority = "ADMIN") - @Authorize(authority = "USER_EDIT_ALL") - @Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().username.equals(#arg0.username)") def changeUser(AbstractUser user, String attribute, def cl) { if (user == null) { log.error("Cannot find user.") diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java index e2fdcb9564b..4ae6841c4ca 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java @@ -3,7 +3,6 @@ import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; -import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.User; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; import com.netgrif.application.engine.auth.service.interfaces.IRegistrationService; @@ -121,8 +120,6 @@ public boolean stringMatchesUserPassword(AbstractUser user, String passwordToCom @Override @Transactional - @Authorize(authority = "ADMIN") - @Authorize(authority = "USER_CREATE") public AbstractUser createNewUser(NewUserRequest newUser) { User user = (User) userService.findByEmail(newUser.email, null); if (user != null) { 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 205a04b8824..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 @@ -1,6 +1,5 @@ package com.netgrif.application.engine.petrinet.service; -import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.files.minio.StorageConfigurationProperties; @@ -178,9 +177,6 @@ public List<PetriNet> get(List<String> petriNetIds) { } @Override - @Transactional - @Authorize(authority = "ADMIN") - @Authorize(authority = "PROCESS_UPLOAD") public ImportPetriNetEventOutcome importPetriNet(ImportPetriNetParams importPetriNetParams) throws IOException, MissingPetriNetMetaDataException, MissingIconKeyException { validateAttributes(importPetriNetParams); @@ -441,8 +437,6 @@ public Page<PetriNet> getAllDefault(Pageable pageable) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "PROCESS_DOWNLOAD") public FileSystemResource getFile(String netId, String title) { if (title == null || title.isEmpty()) { Query query = Query.query(Criteria.where("_id").is(new ObjectId(netId))); @@ -634,15 +628,11 @@ protected void addValueCriteria(Query query, Query queryTotal, Criteria criteria @Override @Transactional - @Authorize(authority = "ADMIN") - @Authorize(authority = "PROCESS_DELETE") public void deletePetriNet(DeletePetriNetParams deletePetriNetParams) { doDeletePetriNet(deletePetriNetParams, false); } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "PROCESS_DELETE") public void forceDeletePetriNet(DeletePetriNetParams deletePetriNetParams) { doDeletePetriNet(deletePetriNetParams, true); } 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 299cddf2cae..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 @@ -116,16 +116,12 @@ public void deleteAll() { @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "ROLE_ASSIGN_TO_USER") public void assignRolesToUser(AbstractUser user, Collection<ProcessResourceId> processResourceIds, LoggedUser loggedUser) { assignRolesToActor(user, processResourceIds); saveUserAndReloadContext(user, loggedUser); } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "ROLE_ASSIGN_TO_GROUP") public void assignRolesToGroup(Group group, Collection<ProcessResourceId> requestedRolesIds) { assignRolesToActor(group, requestedRolesIds); groupService.save(group); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index a07a287efd0..60e327f519b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -2,7 +2,6 @@ import com.google.common.collect.Ordering; import com.netgrif.application.engine.auth.service.GroupService; -import com.netgrif.application.engine.objects.annotations.Authorize; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.petrinet.domain.dataset.ActorFieldValue; import com.netgrif.application.engine.objects.petrinet.domain.dataset.ActorListFieldValue; @@ -136,7 +135,6 @@ public List<AssignTaskEventOutcome> assignTasks(List<Task> tasks, AbstractUser u } @Override - @Authorize(expression = "@taskAuthorizationService.canCallAssign(#taskParams.getUser(), #taskParams.getTaskId())") public AssignTaskEventOutcome assignTask(TaskParams taskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(taskParams); @@ -207,7 +205,6 @@ public List<FinishTaskEventOutcome> finishTasks(List<Task> tasks, AbstractUser u } @Override - @Authorize(expression = "@taskAuthorizationService.canCallFinish(#taskParams.getUser(), #taskParams.getTaskId())") public FinishTaskEventOutcome finishTask(TaskParams taskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(taskParams); @@ -275,7 +272,6 @@ public List<CancelTaskEventOutcome> cancelTasks(List<Task> tasks, AbstractUser u } @Override - @Authorize(expression = "@taskAuthorizationService.canCallCancel(#taskParams.getUser(), #taskParams.getTaskId())") public CancelTaskEventOutcome cancelTask(TaskParams taskParams) { fillAndValidateAttributes(taskParams); @@ -417,7 +413,6 @@ private Case returnTokens(Task task, Case useCase) { } @Override - @Authorize(expression = "@taskAuthorizationService.canCallDelegate(#taskParams.getUser(), #taskParams.getTaskId())") public DelegateTaskEventOutcome delegateTask(DelegateTaskParams delegateTaskParams) throws TransitionNotExecutableException { fillAndValidateAttributes(delegateTaskParams); 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 ec09daa709f..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 @@ -3,7 +3,6 @@ import com.google.common.collect.Ordering; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.auth.service.GroupService; -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.petrinet.domain.dataset.*; @@ -314,7 +313,6 @@ private boolean actorExists(ActorFieldValue actorFieldValue) { } @Override - @Authorize(expression = "@workflowAuthorizationService.canCallCreate(#createCaseParams.getAuthor(), #createCaseParams.getProcessId())") public CreateCaseEventOutcome createCase(CreateCaseParams createCaseParams) { fillAndValidateAttributes(createCaseParams); PetriNet petriNet = createCaseParams.getProcess(); @@ -419,7 +417,6 @@ public Page<Case> findAllByAuthor(String authorId, String petriNet, Pageable pag } @Override - @Authorize(expression = "@workflowAuthorizationService.canCallDelete(@userService.getLoggedUserFromContext(), #deleteCaseParams.getUseCaseId())") public DeleteCaseEventOutcome deleteCase(DeleteCaseParams deleteCaseParams) { fillAndValidateAttributes(deleteCaseParams); 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/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 18b9e140873..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 @@ -83,9 +83,6 @@ public void setPaginationProperties(PaginationProperties paginationProperties) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "GROUP_DELETE") - @Authorize(authority = "GROUP_DELETE_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public void delete(Group group) { if (!groupRepository.existsById(group.getStringId())) { log.error("Group [{}] does not exist", group.getStringId()); @@ -194,9 +191,6 @@ public Group create(AbstractUser groupOwner) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "GROUP_CREATE") - @Authorize(authority = "GROUP_CREATE_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#groupOwner.getUsername())") public Group create(String identifier, String title, AbstractUser groupOwner) { log.info("Creating default group for user: [{}]", groupOwner.getStringId()); Group group = new com.netgrif.application.engine.adapter.spring.auth.domain.Group(identifier, groupOwner.getRealmId()); @@ -252,9 +246,6 @@ public Group addUser(AbstractUser user, String groupIdentifier) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "GROUP_ADD_USER") - @Authorize(authority = "GROUP_ADD_USER_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public Group addUser(AbstractUser user, Group group) { log.info("Adding user [{}] to group [{}]", user.getStringId(), group.getStringId()); user.addGroupId(group.getStringId()); @@ -270,9 +261,6 @@ public Group removeUser(AbstractUser user, String groupIdentifier) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "GROUP_REMOVE_USER") - @Authorize(authority = "GROUP_REMOVE_USER_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#group.getOwnerUsername())") public Group removeUser(AbstractUser user, Group group) { log.info("Removing user [{}] from group [{}]", user.getStringId(), group.getStringId()); user.removeGroupId(group.getStringId()); @@ -346,9 +334,6 @@ public Pair<Group, Group> addSubgroup(String parentGroupId, Group childGroup) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "GROUP_ADD_SUBGROUP") - @Authorize(authority = "GROUP_ADD_SUBGROUP_OWN", expression = "@userService.getLoggedUser().getUsername().equals(#parentGroup.getOwnerUsername())") public Pair<Group, Group> addSubgroup(Group parentGroup, Group childGroup) { // TODO: maybe handle groups cycles here? if (parentGroup.getStringId().equals(childGroup.getStringId())) { 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 0bb61dd5662..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 @@ -195,8 +195,6 @@ public AbstractUser createUser(String username, String email, String firstName, } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "USER_CREATE") public AbstractUser createUser(AbstractUser user, String realmId) { log.info("Creating user [{}] in realm [{}]", user.getUsername(), realmId); setPassword(user, user.getPassword()); @@ -222,8 +220,6 @@ public AbstractUser createUser(AbstractUser user, String realmId) { // TODO JOFO: auth methods no longer exists ... use credentials? @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "USER_CREATE") public User createUserFromThirdParty(String username, String email, String firstName, String lastName, String realmId, String authMethod) { log.info("Creating user [{}] from third-party auth [{}] in realm [{}] without password", username, authMethod, realmId); User user = initializeNewUser(username, email, firstName, lastName, "N/A", realmId); @@ -362,8 +358,6 @@ public AbstractUser findById(String id, String realmId) { } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "USER_DELETE") public void deleteUser(AbstractUser user) { log.warn("Deleting user [{}]", user.getUsername()); String collectionName = collectionNameProvider.getCollectionNameForRealm(user.getRealmId()); @@ -478,8 +472,6 @@ protected Page<AbstractUser> searchUsersByRoleIds(Collection<ProcessResourceId> } @Override - @Authorize(authority = "ADMIN") - @Authorize(authority = "AUTHORITY_ASSIGN_TO_USER") public AbstractUser assignAuthority(String userId, String realmId, String authorityId) { AbstractUser user = findById(userId, realmId); Authority authority = authorityService.getOne(authorityId); From 710234885dde96502a3da9ec1f0f71212454b73b Mon Sep 17 00:00:00 2001 From: renczesstefan <renczes.stefan@gmail.com> Date: Wed, 23 Sep 2026 14:47:41 +0200 Subject: [PATCH 9/9] Extend caching with default authority caches and inject `AuthorityConfigurationProperties` into `AuthorityServiceImpl` --- .../CacheConfigurationProperties.java | 17 ++++++++++++++++- .../auth/service/AuthorityServiceImpl.java | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) 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<String> getAllCaches() { Set<String> 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/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 554f6d3ae3e..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 @@ -40,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);