diff --git a/application-engine/pom.xml b/application-engine/pom.xml index 2a754584877..8fd0afcce8b 100644 --- a/application-engine/pom.xml +++ b/application-engine/pom.xml @@ -544,6 +544,16 @@ jackson-module-jsonSchema ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + io.minio minio diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java index 260f705d5e4..f1c890b0b1b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java @@ -1,6 +1,9 @@ package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.auth.service.interfaces.IAuthorizationService; + +import java.util.Arrays; + import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -18,4 +21,12 @@ public boolean hasAuthority(String authority) { LoggedUser loggedUser = userService.getLoggedUserFromContext(); return loggedUser.getAuthoritySet().stream().anyMatch(it -> it.getAuthority().equals(authority)); } + + @Override + public boolean hasAnyAuthority(String... authority) { + // TODO: impersonation +// LoggedUser loggedUser = userService.getLoggedUserFromContext().getSelfOrImpersonated(); + LoggedUser loggedUser = userService.getLoggedUserFromContext(); + return loggedUser.getAuthoritySet().stream().anyMatch(it -> Arrays.stream(authority).anyMatch(a -> it.getAuthority().equals(a))); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java index 81feafc98d7..89ade3cf8a0 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java @@ -2,4 +2,6 @@ public interface IAuthorizationService { boolean hasAuthority(String authority); + + boolean hasAnyAuthority(String... authority); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthenticationController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthenticationController.java index e1f1c07b70d..63357af6bd3 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthenticationController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthenticationController.java @@ -2,6 +2,7 @@ import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.InvalidUserTokenException; @@ -131,8 +132,8 @@ public MessageResource verifyToken(@RequestBody String token) { @Operation(summary = "Verify validity of an authentication token") @GetMapping(value = "/verify", produces = MediaTypes.HAL_JSON_VALUE) - public MessageResource verifyAuthToken(Authentication auth) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); + public MessageResource verifyAuthToken() { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); return MessageResource.successMessage("Auth Token successfully verified, for user [" + loggedUser.getId() + "] " + loggedUser.getName()); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java b/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java deleted file mode 100644 index 6d472275a12..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.netgrif.application.engine.auth.web; - -import com.netgrif.application.engine.auth.service.PreferencesService; -import com.netgrif.application.engine.auth.service.UserService; -import com.netgrif.application.engine.auth.web.requestbodies.PreferencesRequest; -import com.netgrif.application.engine.auth.web.requestbodies.UserSearchRequestBody; -import com.netgrif.application.engine.auth.web.responsebodies.PreferencesResource; -import com.netgrif.application.engine.auth.web.responsebodies.User; -import com.netgrif.application.engine.objects.auth.domain.AbstractUser; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import com.netgrif.application.engine.objects.preferences.Preferences; -import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import 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.http.HttpStatus; -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; - -@Slf4j -@RestController -@ConditionalOnProperty( - value = "netgrif.engine.public.web.user-enabled", - havingValue = "true", - matchIfMissing = true -) -@Tag(name = "Public User Controller") -@RequestMapping("/api/public/user") -public class PublicUserController { - - @Autowired - private UserService userService; - - @Autowired - private PreferencesService preferencesService; - - @Operation(summary = "Get logged user", description = "Retrieves information of currently logged user") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "User retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid user data"), - @ApiResponse(responseCode = "500", description = "Internal server error") - }) - @GetMapping(value = "/me", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getLoggedUser(Authentication auth) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - AbstractUser user; - try { - user = userService.findById(loggedUser.getStringId(), loggedUser.getRealmId()); - if (user == null) { - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED).build(); - } - } catch (IllegalArgumentException e) { - log.error("Could not find user with id [{}]", loggedUser.getId(), e); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); - } - - return ResponseEntity.ok(User.createUser(user)); - } - - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "User retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid user data"), - @ApiResponse(responseCode = "500", description = "Internal server error") - }) - @Operation(summary = "Generic user search", security = {@SecurityRequirement(name = "X-Auth-Token")}) - @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> search(@RequestBody UserSearchRequestBody query, Pageable pageable, Authentication auth) { - List roles = query.getRoles() == null ? null : query.getRoles().stream().map(ProcessResourceId::new).toList(); - List negativeRoles = query.getNegativeRoles() == null ? null : query.getNegativeRoles().stream().map(ProcessResourceId::new).toList(); - Page users = userService.searchAllCoMembers(query.getFulltext(), - roles, - negativeRoles, - (LoggedUser) auth.getPrincipal(), pageable); - return ResponseEntity.ok(changeToResponse(users, pageable)); - } - - @Operation(summary = "Get logged user's preferences", security = {@SecurityRequirement(name = "X-Auth-Token")}) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Returns preferences of logged user"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - @ApiResponse(responseCode = "500", description = "Internal server error") - }) - @GetMapping(value = "/preferences", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity preferences(Authentication auth) { - String userId = ((LoggedUser) auth.getPrincipal()).getStringId(); - Preferences preferences = preferencesService.get(userId); - - if (preferences == null) { - preferences = new com.netgrif.application.engine.adapter.spring.preferences.Preferences(userId); - } - PreferencesResource preferencesResource = PreferencesResource.withPreferences(preferences); - - return ResponseEntity.ok(preferencesResource); - } - - - @Operation(summary = "Set preferences of logged user", security = {@SecurityRequirement(name = "X-Auth-Token")}) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Saves preferences of logged user"), - @ApiResponse(responseCode = "400", description = "Preferences data are invalid"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - @ApiResponse(responseCode = "500", description = "Internal server error") - }) - @PostMapping(value = "/preferences", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity savePreferences(@RequestBody PreferencesRequest preferences, Authentication auth) { - try { - String userId = ((LoggedUser) auth.getPrincipal()).getStringId(); - preferences.setUserId(userId); - preferencesService.save(preferences.toPreferences()); - return ResponseEntity.ok("User preferences saved"); - } catch (Exception e) { - log.error("Saving user preferences failed", e); - return ResponseEntity.badRequest().body("Saving user preferences failed"); - } - } - - private Page changeToResponse(Page users, Pageable pageable) { - return new PageImpl<>(changeType(users.getContent()), pageable, users.getTotalElements()); - } - - public List changeType(List users) { - return users.stream().map(User::createUser).toList(); - } - -} 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 e03c3f168ef..5c2ae27ab03 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 @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.auth.web; + package com.netgrif.application.engine.auth.web; import com.netgrif.application.engine.adapter.spring.common.web.responsebodies.ResponseMessage; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; @@ -8,10 +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.auth.domain.AbstractUser; -import com.netgrif.application.engine.objects.auth.domain.Authority; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import com.netgrif.application.engine.objects.auth.domain.Realm; +import com.netgrif.application.engine.objects.auth.domain.*; import com.netgrif.application.engine.objects.preferences.Preferences; import com.netgrif.application.engine.objects.workflow.domain.ProcessResourceId; import io.swagger.v3.oas.annotations.Operation; @@ -30,6 +27,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.*; @@ -112,10 +110,10 @@ public ResponseEntity> getAllUsers(@PathVariable String realmId, Page }) @GetMapping(value = "/me", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getLoggedUser(Authentication auth, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); + LoggedUser loggedUser = resolveAuthenticationToken(auth); AbstractUser user; try { - user = userService.findById(loggedUser.getStringId(), loggedUser.getRealmId()); + user = resolveLoggedUser(loggedUser); if (user == null) { return ResponseEntity .status(HttpStatus.UNAUTHORIZED).build(); @@ -294,11 +292,14 @@ public ResponseEntity assignAuthorityToUser(@PathVariable("real }) @GetMapping(value = "/preferences", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity preferences(Authentication auth) { - String userId = ((LoggedUser) auth.getPrincipal()).getStringId(); - Preferences preferences = preferencesService.get(userId); + LoggedUser loggedUser = resolveAuthenticationToken(auth); + if (loggedUser == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + Preferences preferences = preferencesService.get(loggedUser.getStringId()); if (preferences == null) { - preferences = new com.netgrif.application.engine.adapter.spring.preferences.Preferences(userId); + preferences = new com.netgrif.application.engine.adapter.spring.preferences.Preferences(loggedUser.getStringId()); } PreferencesResource preferencesResource = PreferencesResource.withPreferences(preferences); @@ -337,4 +338,25 @@ private boolean realmExists(String realmId) { Optional realm = realmService.getRealmById(realmId); return realm.isPresent(); } + + private LoggedUser resolveAuthenticationToken(Authentication auth) { + if (auth != null) { + return (LoggedUser) auth.getPrincipal(); + } + auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + return (LoggedUser) auth.getPrincipal(); + } + return null; + } + + private AbstractUser resolveLoggedUser(LoggedUser loggedUser) { + if (loggedUser == null) { + return null; + } + if (loggedUser.isAnonymous()) { + return ActorTransformer.toUser(loggedUser); + } + return userService.findById(loggedUser.getStringId(), loggedUser.getRealmId()); + } } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/LoggedUserConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/LoggedUserConfiguration.java index 5b0d768a100..ecb58db2cfb 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/LoggedUserConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/LoggedUserConfiguration.java @@ -1,15 +1,20 @@ package com.netgrif.application.engine.configuration; -import com.netgrif.application.engine.auth.service.DefaultLoggedUserFactory; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; @Configuration +@RequiredArgsConstructor public class LoggedUserConfiguration { + private final ActorTransformer.LoggedUserFactory loggedUserFactory; + private final ActorTransformer.UserFactory userFactory; + @PostConstruct public void initializeLoggedUserFactory() { - ActorTransformer.setLoggedUserFactory(new DefaultLoggedUserFactory()); + ActorTransformer.setLoggedUserFactory(loggedUserFactory); + ActorTransformer.setUserFactory(userFactory); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java index 17a5affe1af..bb9d25cd9e4 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java @@ -1,17 +1,16 @@ package com.netgrif.application.engine.configuration; +import com.netgrif.application.engine.adapter.spring.configuration.filters.NetgrifHttpRequestTransformFilter; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; -import com.netgrif.application.engine.objects.auth.domain.Authority; -import com.netgrif.application.engine.auth.service.AuthorityService; -import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.configuration.security.ImpersonationRequestFilter; import com.netgrif.application.engine.configuration.security.PublicAuthenticationFilter; import com.netgrif.application.engine.configuration.security.RestAuthenticationEntryPoint; import com.netgrif.application.engine.configuration.security.SecurityContextFilter; import com.netgrif.application.engine.configuration.security.filter.HostValidationRequestFilter; -import com.netgrif.application.engine.configuration.security.jwt.IJwtService; +import com.netgrif.application.engine.configuration.security.RealmFilter; import com.netgrif.application.engine.impersonation.service.interfaces.IImpersonationService; import com.netgrif.application.engine.security.service.ISecurityContextService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -20,10 +19,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AnonymousAuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -37,14 +33,12 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.ForwardedHeaderFilter; -import java.util.HashSet; import java.util.List; import static org.springframework.http.HttpMethod.OPTIONS; @Slf4j -@Controller @Configuration @EnableWebSecurity @Order(SecurityProperties.DEFAULT_FILTER_ORDER) @@ -56,21 +50,9 @@ public class NaeSecurityConfiguration extends AbstractSecurityConfiguration { @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint; - @Autowired - private AuthorityService authorityService; - - @Autowired - private IJwtService jwtService; - - @Autowired - private UserService userService; - @Autowired private SecurityConfigurationProperties securityConfigurationProperties; - @Autowired - private SecurityConfigurationProperties properties; - @Autowired private ISecurityContextService securityContextService; @@ -81,19 +63,23 @@ public class NaeSecurityConfiguration extends AbstractSecurityConfiguration { private List authenticationProviders; @Autowired - private AuthenticationManagerBuilder authenticationManagerBuilder; + private PublicAuthenticationFilter publicAuthenticationFilter; + + @Autowired + private NetgrifHttpRequestTransformFilter netgrifHttpRequestTransformFilter; - private static final String ANONYMOUS_USER = "anonymousUser"; + @Autowired + private RealmFilter realmFilter; @Bean public CorsConfigurationSource corsConfigurationSource() { - List allowedOrigins = properties.getAllowedOrigins(); + List allowedOrigins = securityConfigurationProperties.getAllowedOrigins(); CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues(); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.addExposedHeader("X-Auth-Token"); - config.addExposedHeader("X-Jwt-Token"); + config.addExposedHeader("X-Anonymous-Token"); config.setAllowCredentials(true); if (allowedOrigins == null || allowedOrigins.isEmpty()) { config.addAllowedOriginPattern("*"); @@ -114,10 +100,12 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception { .httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.authenticationEntryPoint(authenticationEntryPoint)) .addFilterBefore(new ForwardedHeaderFilter(), WebAsyncManagerIntegrationFilter.class) - .addFilterBefore(createPublicAuthenticationFilter(), BasicAuthenticationFilter.class) .addFilterAfter(createSecurityContextFilter(), BasicAuthenticationFilter.class) .addFilterAfter(impersonationRequestFilter(), BasicAuthenticationFilter.class) .addFilterAfter(hostValidationRequestFilter(), BasicAuthenticationFilter.class) + .addFilterBefore(publicAuthenticationFilter, SecurityContextFilter.class) + .addFilterBefore(netgrifHttpRequestTransformFilter, BasicAuthenticationFilter.class) + .addFilterBefore(realmFilter, PublicAuthenticationFilter.class) .authorizeHttpRequests(requestMatcherRegistry -> requestMatcherRegistry .requestMatchers(getPatterns()).permitAll() @@ -145,12 +133,12 @@ protected boolean isOpenRegistration() { @Override protected boolean isCsrfEnabled() { - return properties.isCsrf(); + return securityConfigurationProperties.isCsrf(); } @Override protected boolean isCorsEnabled() { - return properties.isCors(); + return securityConfigurationProperties.isCors(); } @Override @@ -170,28 +158,16 @@ protected Environment getEnvironment() { @Override protected SecurityConfigurationProperties getSecurityConfigProperties() { - return properties; + return securityConfigurationProperties; } - protected PublicAuthenticationFilter createPublicAuthenticationFilter() throws Exception { - Authority authority = authorityService.getOrCreate(Authority.anonymous); - return new PublicAuthenticationFilter( - (ProviderManager) authenticationManager(authenticationManagerBuilder), - new AnonymousAuthenticationProvider(ANONYMOUS_USER), - securityConfigurationProperties.getServerPatterns(), - securityConfigurationProperties.getAnonymousExceptions(), - jwtService, - userService, - authorityService - ); - } private SecurityContextFilter createSecurityContextFilter() { return new SecurityContextFilter(securityContextService); } private HostValidationRequestFilter hostValidationRequestFilter() { - return new HostValidationRequestFilter(properties); + return new HostValidationRequestFilter(securityConfigurationProperties); } private ImpersonationRequestFilter impersonationRequestFilter() { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java index a18f449ea96..60c61cb336d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java @@ -68,6 +68,9 @@ public class SecurityConfigurationProperties { */ private String[] anonymousExceptions; + + private String anonymousAuthenticationKey = "anonymousUser"; + /** * Headers settings */ @@ -418,6 +421,8 @@ public static class WebProperties { @Data public static class PublicProperties { + private boolean enabled = true; + /** * Public URL for web functionalities. */ diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java index 02791ccc93d..ad3a74772cd 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java @@ -1,127 +1,111 @@ package com.netgrif.application.engine.configuration.security; -import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl; +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUser; +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUserRef; +import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; +import com.netgrif.application.engine.adapter.spring.configuration.filters.NetgrifOncePerRequestFilter; +import com.netgrif.application.engine.adapter.spring.configuration.filters.requests.NetgrifHttpServletRequest; +import com.netgrif.application.engine.auth.service.AnonymousUserRefService; +import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; import com.netgrif.application.engine.objects.auth.domain.*; import com.netgrif.application.engine.auth.service.AuthorityService; -import com.netgrif.application.engine.auth.service.UserService; -import com.netgrif.application.engine.configuration.security.jwt.IJwtService; -import com.netgrif.application.engine.objects.auth.domain.enums.UserState; +import com.netgrif.application.engine.utils.HttpReqRespUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; -import org.bson.types.ObjectId; -import org.springframework.security.authentication.AnonymousAuthenticationProvider; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.ProviderManager; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; import java.io.IOException; -import java.util.Collections; -import java.util.Map; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Collectors; @Slf4j -public class PublicAuthenticationFilter extends OncePerRequestFilter { +@Component +public class PublicAuthenticationFilter extends NetgrifOncePerRequestFilter { - private final static String JWT_HEADER_NAME = "X-Jwt-Token"; - private final static String BEARER = "Bearer "; - private final static String USER = "user"; - private final ProviderManager authenticationManager; - private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private final String[] anonymousAccessUrls; - private final String[] exceptions; - - private final IJwtService jwtService; - private final UserService userService; + private final AnonymousUserRefService anonymousUserRefService; private final AuthorityService authorityService; - public PublicAuthenticationFilter(ProviderManager authenticationManager, AnonymousAuthenticationProvider provider, - String[] urls, String[] exceptions, IJwtService jwtService, - UserService userService, AuthorityService authorityService) { - this.authenticationManager = authenticationManager; - this.authenticationManager.getProviders().add(provider); - this.anonymousAccessUrls = urls; - this.exceptions = exceptions; - this.jwtService = jwtService; - this.userService = userService; + public PublicAuthenticationFilter(AnonymousUserRefService anonymousUserRefService, + AuthorityService authorityService, + SecurityConfigurationProperties securityConfigurationProperties) { + this.anonymousUserRefService = anonymousUserRefService; this.authorityService = authorityService; + setRequestMatcher(Arrays.stream(securityConfigurationProperties.getServerPatterns()).map(AntPathRequestMatcher::new).collect(Collectors.toSet())); } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if (isPublicApi(request.getRequestURI())) { - String jwtToken = resolveValidToken(request, response); - authenticate(request, jwtToken); - response.setHeader(JWT_HEADER_NAME, BEARER + jwtToken); - log.info("Anonymous user was authenticated."); - } - filterChain.doFilter(request, response); - } + protected void doFilterInternal(NetgrifHttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + String path = request.getRequestURI(); + log.trace("PublicAnonymousAuthFilter handling {}", path); - private void authenticate(HttpServletRequest request, String jwtToken) { - AnonymousAuthenticationToken authRequest = new AnonymousAuthenticationToken( - "anonymous-user", - jwtService.getLoggedUser(jwtToken, Authority.anonymous), - Collections.singleton((AuthorityImpl) authorityService.getOrCreate(Authority.anonymous)) - ); - authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); - Authentication authResult = this.authenticationManager.authenticate(authRequest); - SecurityContextHolder.getContext().setAuthentication(authResult); - } - - private String resolveValidToken(HttpServletRequest request, HttpServletResponse response) { - String jwtHeader = request.getHeader(JWT_HEADER_NAME); - String jwtToken = null; + Authentication current = SecurityContextHolder.getContext().getAuthentication(); + if (current != null && current.isAuthenticated()) { + log.debug("Existing authenticated principal: {}", current.getPrincipal()); + filterChain.doFilter(request, response); + return; + } - if (jwtHeader == null || !jwtHeader.startsWith(BEARER)) { - log.warn("There is no JWT token or token is invalid."); - LoggedUser loggedUser = resolveLoggedUser(jwtToken); - jwtToken = jwtService.tokenFrom(Collections.emptyMap(), loggedUser.getUsername(), Map.of(USER, loggedUser)); - } else { - jwtToken = jwtHeader.replace(BEARER, ""); + Realm realm = HttpReqRespUtils.extractRealmFromRequest(request); + if (realm == null) { + log.debug("Realm unavailable for public request; skipping anon auth"); + filterChain.doFilter(request, response); + return; } - if (jwtService.isTokenExpired(jwtToken)) { - LoggedUser loggedUser = resolveLoggedUser(jwtToken); - jwtToken = jwtService.tokenFrom(Collections.emptyMap(), loggedUser.getUsername(), Map.of(USER, loggedUser)); + log.debug("Loaded realm {} (publicAccess={})", realm.getName(), realm.isPublicAccess()); + if (!realm.isPublicAccess()) { + log.debug("Public access disabled for realm {}; skipping anon auth", realm.getName()); + filterChain.doFilter(request, response); + return; } - return jwtToken; - } - private LoggedUser resolveLoggedUser(String existingToken) { - LoggedUser loggedUser; - if (existingToken != null) { - loggedUser = jwtService.getLoggedUser(existingToken, Authority.anonymous); - } else { - loggedUser = createAnonymousUser(); + try { + log.trace("Performing anonymous public auth for realm {}", realm.getName()); + Optional refOpt = anonymousUserRefService.getRef(realm.getName()); + if (refOpt.isEmpty()) { + log.debug("AnonymousUserRef missing for realm {}; skipping anon auth", realm.getName()); + filterChain.doFilter(request, response); + return; + } + AnonymousUserRef ref = refOpt.get(); + Authority anonAuthority = authorityService.getOrCreate(Authority.anonymous); + log.debug("Using AnonymousUserRef id {} for realm {}", ref.getId(), realm.getName()); + + AnonymousUser anonymousUser = new AnonymousUser(ref, anonAuthority); + LoggedUserImpl userDetails = (LoggedUserImpl) ActorTransformer.toLoggedUser(anonymousUser); + userDetails.setSessionTimeout(realm.getPublicSessionTimeout()); + log.debug("Created anonymous user details for user: {}", userDetails.getUsername()); + + AnonymousAuthenticationToken token = new AnonymousAuthenticationToken( + "engine", + userDetails, + userDetails.getAuthorities() + ); + token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(token); + log.debug("Anonymous public auth succeeded for realm {}", realm.getName()); + } catch (Exception ex) { + log.debug("Anonymous public auth failed for realm {}: {}", realm.getName(), ex.getMessage(), ex); } - loggedUser.setPassword("N/A"); - return loggedUser; - } - private LoggedUser createAnonymousUser() { - User anonymousUser = new User(); - anonymousUser.setState(UserState.ACTIVE); - anonymousUser = (User) userService.saveUser(anonymousUser, null); - return ActorTransformer.toLoggedUser(anonymousUser); + filterChain.doFilter(request, response); } - private boolean isPublicApi(String path) { - for (String url : this.anonymousAccessUrls) { - if (path.matches(url.replace("*", ".*?"))) { - for (String ex : this.exceptions) { - if (path.matches(ex.replace("*", ".*?"))) { - return false; - } - } - return true; - } - } - return false; + @Bean + public FilterRegistrationBean publicAnonymousAuthFilterFilterRegistrationBean(PublicAuthenticationFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setEnabled(false); + return registration; } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java new file mode 100644 index 00000000000..917a04d4e23 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java @@ -0,0 +1,118 @@ +package com.netgrif.application.engine.configuration.security; + +import com.fasterxml.jackson.databind.JsonNode; +import com.netgrif.application.engine.adapter.spring.configuration.filters.NetgrifOncePerRequestFilter; +import com.netgrif.application.engine.adapter.spring.configuration.filters.requests.NetgrifHttpServletRequest; +import com.netgrif.application.engine.auth.service.RealmService; +import com.netgrif.application.engine.objects.auth.domain.Realm; +import com.netgrif.application.engine.utils.HttpReqRespUtils; +import com.netgrif.application.engine.utils.HttpRequestParamConstants; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Optional; + +/** + * This filter extracts a realm from the request. It's saved by {@link NetgrifHttpServletRequest#addAdditionalParameter(String, Object)} + * under the key {@link HttpRequestParamConstants#REALM}. Realm can be null if none is found. + * */ +@Slf4j +@Component +public class RealmFilter extends NetgrifOncePerRequestFilter { + + private final String REALM_ID_HEADER = "X-Realm-ID"; + private final String REALM_ID_BODY = "realmId"; + private final String REALM_NAME_BODY = "realName"; + + private final RealmService realmService; + + public RealmFilter(RealmService realmService) { + this.realmService = realmService; + } + + @Override + protected void doFilterInternal(NetgrifHttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + log.debug("Trying to get realm id from the HTTP request..."); + + Optional realmOpt = findRealmInHeaders(request); + + if (realmOpt.isEmpty()) { + realmOpt = findRealmInBody(request); + } + + if (realmOpt.isEmpty()) { + log.debug("Realm could not be found in the request. Using the default realm if available."); + realmOpt = realmService.getDefaultRealm(); + } else { + log.debug("Realm was successfully found in the request"); + } + + if (realmOpt.isEmpty()) { + log.debug("No realm could be found. Continuing without realm."); + filterChain.doFilter(request, response); + return; + } + + log.trace("Selected realm: [{}]", realmOpt.get().getName()); + + request.addAdditionalParameter(HttpRequestParamConstants.REALM, realmOpt.get()); + + filterChain.doFilter(request, response); + } + + @Bean + public FilterRegistrationBean realmFilterFilterRegistrationBean(RealmFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setEnabled(false); + return registration; + } + + /** + * Finds realm by realm id in headers by {@link #REALM_ID_HEADER} + * */ + private Optional findRealmInHeaders(HttpServletRequest request) { + String realmId = request.getHeader(REALM_ID_HEADER); + if (realmId == null || realmId.isBlank()) { + return Optional.empty(); + } + return realmService.getRealmById(realmId); + } + + /** + * Finds realm by realm id {@link #REALM_ID_BODY} or realm name {@link #REALM_NAME_BODY} in request body + * */ + private Optional findRealmInBody(NetgrifHttpServletRequest request) { + JsonNode requestBodyJson = HttpReqRespUtils.extractBodyFromRequest(request); + if (requestBodyJson == null) { + return Optional.empty(); + } + + JsonNode realmIdNode = requestBodyJson.get(REALM_ID_BODY); + Optional realmOpt = Optional.empty(); + + if (isJsonNodeValueEmpty(realmIdNode)) { + realmOpt = realmService.getRealmById(String.valueOf(realmIdNode.textValue())); + } + + if (realmOpt.isEmpty()) { + JsonNode realmNameNode = requestBodyJson.get(REALM_NAME_BODY); + if (isJsonNodeValueEmpty(realmNameNode)) { + realmOpt = realmService.getRealmByName(realmNameNode.textValue()); + } + } + + return realmOpt; + } + + private static boolean isJsonNodeValueEmpty(JsonNode node) { + return node != null && node.textValue() != null && !node.textValue().isBlank(); + } +} \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java index 693a719e88a..e81c7e2ab7d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.configuration.security.jwt; +import com.fasterxml.jackson.databind.ObjectMapper; import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; import com.netgrif.application.engine.objects.auth.domain.Attribute; @@ -10,6 +11,7 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.jackson.io.JacksonSerializer; import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -34,12 +36,14 @@ public class JwtService implements IJwtService { private final SecurityConfigurationProperties.JwtProperties properties; private final ProcessRoleService roleService; private final AuthorityService authorityService; + private ObjectMapper objectMapper; @PostConstruct private void resolveSecret() { + configureObjectMapper(); try { PrivateKeyReader reader = new PrivateKeyReader(properties.getAlgorithm()); - secret = reader.get(properties.getPrivateKey().getFile().getPath()).getEncoded(); + secret = reader.get(properties.getPrivateKey()).getEncoded(); } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { log.error("Error while resolving secret key: " + e.getMessage(), e); } @@ -56,6 +60,7 @@ public String tokenFrom(Map header, String subject, Map(objectMapper)) .compact(); } @@ -73,12 +78,13 @@ public LoggedUser getLoggedUser(String token, String authority) { LinkedHashMap userMap = (LinkedHashMap) extractAllClaims(token).get("user"); LoggedUser user = new LoggedUserImpl(); - user.setId(userMap.get("id").toString()); - user.setUsername(userMap.get("username").toString()); - user.setPassword(userMap.get("password").toString()); + user.setId((String) userMap.get("stringId")); + user.setUsername((String) userMap.get("username")); + user.setFirstName((String) userMap.get("firstName")); + user.setMiddleName((String) userMap.get("middleName")); + user.setLastName((String) userMap.get("lastName")); user.setAuthoritySet(Collections.singleton(authorityService.getOrCreate(authority))); user.setProcessRoles(Collections.singleton(roleService.getAnonymousRole())); - user.setFirstName(userMap.get("firstName").toString()); user.getAttributes().put("anonymous", new Attribute<>(true, false)); return user; @@ -105,4 +111,9 @@ private Claims extractAllClaims(String token) throws ExpiredJwtException { private Key getSignInKey() { return Keys.hmacShaKeyFor(secret); } + + private void configureObjectMapper() { + objectMapper = new ObjectMapper(); + objectMapper.findAndRegisterModules(); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java index e864e7e61f0..b4b570f76ea 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java @@ -1,8 +1,10 @@ package com.netgrif.application.engine.configuration.security.jwt; +import org.apache.commons.io.IOUtils; +import org.springframework.core.io.Resource; + +import java.io.FileInputStream; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -17,8 +19,9 @@ public PrivateKeyReader(String algorithm) throws NoSuchAlgorithmException { keyFactory = KeyFactory.getInstance(algorithm); } - public PrivateKey get(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { - byte[] keyBytes = Files.readAllBytes(Paths.get(filename)); + public PrivateKey get(Resource resource) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + FileInputStream fileInputStream = new FileInputStream(resource.getFile()); + byte[] keyBytes = IOUtils.toByteArray(fileInputStream); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); return keyFactory.generatePrivate(spec); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetAuthorizationService.java index ab5f17ad13e..1d74ef1dfc2 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetAuthorizationService.java @@ -1,13 +1,13 @@ package com.netgrif.application.engine.petrinet.service; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; +import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetAuthorizationService; import org.springframework.stereotype.Service; @Service public class PetriNetAuthorizationService implements IPetriNetAuthorizationService { @Override - public boolean canCallProcessDelete(LoggedUser loggedUser, String processId) { - return loggedUser.isAdmin(); + public boolean canCallProcessDelete(AbstractUser user, String processId) { + return user.isAdmin(); } } 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 882821231aa..d68ec559dac 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 @@ -668,7 +668,7 @@ protected void deletePetriNet(String processId, LoggedUser loggedUser, boolean f private Criteria getProcessRolesCriteria(LoggedUser user) { return new Criteria().orOperator(user.getProcessRoles().stream() - .map(role -> Criteria.where("permissions." + role).exists(true)).toArray(Criteria[]::new)); + .map(role -> Criteria.where("permissions." + role.getStringId()).exists(true)).toArray(Criteria[]::new)); } @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetAuthorizationService.java index 1621d9609d5..de5678f27e5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetAuthorizationService.java @@ -1,9 +1,9 @@ package com.netgrif.application.engine.petrinet.service.interfaces; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; +import com.netgrif.application.engine.objects.auth.domain.AbstractUser; public interface IPetriNetAuthorizationService { - boolean canCallProcessDelete(LoggedUser loggedUser, String processId); + boolean canCallProcessDelete(AbstractUser user, String processId); } 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 67ac0cc5370..5b86b6646c4 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 @@ -2,9 +2,11 @@ import com.netgrif.application.engine.AsyncRunner; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; +import com.netgrif.application.engine.auth.service.UserService; 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.auth.domain.ActorTransformer; 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; @@ -82,6 +84,9 @@ public class PetriNetController { @Autowired private AsyncRunner asyncRunner; + @Autowired + private UserService userService; + public static String decodeUrl(String s1) { try { if (s1 == null) @@ -120,10 +125,11 @@ public EntityModel importPetriNet( } } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @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) { - LoggedUser user = (LoggedUser) auth.getPrincipal(); + public ResponseEntity> getAll(@RequestParam(value = "indentifier", required = false) String identifier, @RequestParam(value = "version", required = false) String version, Pageable pageable, Locale locale) { + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); if (identifier != null && version == null) { return ResponseEntity.ok(service.getReferencesByIdentifier(identifier, user, locale, pageable)); } else if (identifier == null && version != null) { @@ -136,41 +142,47 @@ public ResponseEntity> getAll(@RequestParam(value = "ind } } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get process by id", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) - public PetriNetReferenceResource getOne(@PathVariable("id") String id, Authentication auth, Locale locale) { + @GetMapping(value = {"/{id}", "/public/{id}"}, produces = MediaTypes.HAL_JSON_VALUE) + public PetriNetReferenceResource getOne(@PathVariable("id") String id, Locale locale) { return new PetriNetReferenceResource(IPetriNetService.transformToReference(service.getPetriNet(decodeUrl(id)), locale)); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get process by identifier and version", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{identifier}/{version}", produces = MediaTypes.HAL_JSON_VALUE) - public PetriNetReferenceResource getOne(@PathVariable("identifier") String identifier, @PathVariable("version") String version, Authentication auth, Locale locale) { + @GetMapping(value = {"/{identifier}/{version}", "/public/{identifier}/{version}"}, produces = MediaTypes.HAL_JSON_VALUE) + public PetriNetReferenceResource getOne(@PathVariable("identifier") String identifier, @PathVariable("version") String version, Locale locale) { String resolvedIdentifier = Base64.isBase64(identifier) ? new String(Base64.decodeBase64(identifier)) : identifier; - return new PetriNetReferenceResource(service.getReference(resolvedIdentifier, converter.convert(version), (LoggedUser) auth.getPrincipal(), locale)); + return new PetriNetReferenceResource(service.getReference(resolvedIdentifier, converter.convert(version), ActorTransformer.toLoggedUser(userService.getLoggedUserFromContext()), locale)); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get transitions of processes", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/transitions", produces = MediaTypes.HAL_JSON_VALUE) - public TransitionReferencesResource getTransitionReferences(@RequestParam List ids, Authentication auth, Locale locale) { + @GetMapping(value = {"/transitions", "/public/transitions"}, produces = MediaTypes.HAL_JSON_VALUE) + public TransitionReferencesResource getTransitionReferences(@RequestParam List ids, Locale locale) { ids.forEach(id -> id = decodeUrl(id)); - return new TransitionReferencesResource(service.getTransitionReferences(ids, (LoggedUser) auth.getPrincipal(), locale)); + return new TransitionReferencesResource(service.getTransitionReferences(ids, ActorTransformer.toLoggedUser(userService.getLoggedUserFromContext()), locale)); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get data fields of transitions", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/data", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + @PostMapping(value = {"/data", "/public/data"}, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public DataFieldReferencesResource getDataFieldReferences(@RequestBody List referenceBody, Locale locale) { return new DataFieldReferencesResource(service.getDataFieldReferences(referenceBody, locale)); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get roles of process", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{netId}/roles", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/{netId}/roles", "/public/{netId}/roles"}, produces = MediaTypes.HAL_JSON_VALUE) public ProcessRolesResource getRoles(@PathVariable("netId") String netId, Locale locale) { netId = decodeUrl(netId); return new ProcessRolesResource(roleService.findAllByNetStringId(netId), service.getPetriNet(decodeUrl(netId)).getPermissions(), netId, locale); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get transactions of process", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{netId}/transactions", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/{netId}/transactions", "/public/{netId}/transactions"}, produces = MediaTypes.HAL_JSON_VALUE) public TransactionsResource getTransactions(@PathVariable("netId") String netId, Locale locale) { PetriNet net = service.getPetriNet(decodeUrl(netId)); return new TransactionsResource(net.getTransactions().values(), netId, locale); @@ -179,7 +191,7 @@ public TransactionsResource getTransactions(@PathVariable("netId") String netId, @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") @Operation(summary = "Download process model", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/{netId}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public FileSystemResource getNetFile(@PathVariable("netId") String netId, @RequestParam(value = "title", required = false) String title, Authentication auth, HttpServletResponse response) { + public FileSystemResource getNetFile(@PathVariable("netId") String netId, @RequestParam(value = "title", required = false) String title, HttpServletResponse response) { FileSystemResource fileResource = service.getFile(decodeUrl(netId), decodeUrl(title)); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileResource.getFilename() + Importer.FILE_EXTENSION + "\""); @@ -188,25 +200,27 @@ public FileSystemResource getNetFile(@PathVariable("netId") String netId, @Reque return fileResource; } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Search processes", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/search", produces = MediaTypes.HAL_JSON_VALUE) + @PostMapping(value = {"/search", "/public/search"}, produces = MediaTypes.HAL_JSON_VALUE) public @ResponseBody - ResponseEntity> searchPetriNets(@RequestBody PetriNetSearch criteria, Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - LoggedUser user = (LoggedUser) auth.getPrincipal(); + ResponseEntity> searchPetriNets(@RequestBody PetriNetSearch criteria, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); Page nets = service.search(criteria, user, pageable, locale); return ResponseEntity.ok(new PageImpl<>(nets.stream().map(PetriNetReferenceResource::new).toList(), pageable, nets.getTotalElements())); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Search elastic processes", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/search_elastic", produces = MediaTypes.HAL_JSON_VALUE) public @ResponseBody - PagedModel searchElasticPetriNets(@RequestBody PetriNetSearch criteria, Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - LoggedUser user = (LoggedUser) auth.getPrincipal(); + PagedModel searchElasticPetriNets(@RequestBody PetriNetSearch criteria, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); // TODO: add Merge Filters and its operations Page nets = elasticService.search(criteria, user, pageable, locale, false); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PetriNetController.class) - .searchElasticPetriNets(criteria, auth, pageable, assembler, locale)).withRel("search_elastic"); + .searchElasticPetriNets(criteria, pageable, assembler, locale)).withRel("search_elastic"); // TODO doriesit linky pista ich zakomentoval PagedModel resources = assembler.toModel(nets, new PetriNetReferenceResourceAssembler(), selfLink); @@ -214,7 +228,7 @@ PagedModel searchElasticPetriNets(@RequestBody PetriN return resources; } - @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(#auth.getPrincipal(), #processId)") + @PreAuthorize("@petriNetAuthorizationService.canCallProcessDelete(@userService.getLoggedUser(), #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")}) @@ -223,13 +237,13 @@ PagedModel searchElasticPetriNets(@RequestBody PetriN @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements") }) @DeleteMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) - public MessageResource deletePetriNet(@PathVariable("id") String processId, @RequestParam(required = false) boolean force, Authentication auth) { + public MessageResource deletePetriNet(@PathVariable("id") String processId, @RequestParam(required = false) boolean force) { String decodedProcessId = decodeUrl(processId); if (Objects.equals(decodedProcessId, "")) { log.error("Deleting Petri net [" + processId + "] failed: could not decode process ID from URL"); return MessageResource.errorMessage("Deleting Petri net " + processId + " failed!"); } - LoggedUser user = (LoggedUser) auth.getPrincipal(); + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); asyncRunner.execute(() -> { if (force) { service.forceDeletePetriNet(decodedProcessId, user); @@ -240,6 +254,7 @@ public MessageResource deletePetriNet(@PathVariable("id") String processId, @Req return MessageResource.successMessage("Petri net " + decodedProcessId + " is being deleted"); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get net by case id", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/case/{id}", produces = MediaTypes.HAL_JSON_VALUE) public PetriNetImportReference getOne(@PathVariable("id") String caseId) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PublicPetriNetController.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PublicPetriNetController.java deleted file mode 100644 index ab2ce973c53..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PublicPetriNetController.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.netgrif.application.engine.petrinet.web; - -import com.netgrif.application.engine.auth.service.UserService; -import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; -import com.netgrif.application.engine.petrinet.domain.version.StringToVersionConverter; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; -import com.netgrif.application.engine.petrinet.web.responsebodies.*; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.codec.binary.Base64; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PagedResourcesAssembler; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.MediaTypes; -import org.springframework.hateoas.PagedModel; -import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.Locale; - -import static com.netgrif.application.engine.petrinet.web.PetriNetController.decodeUrl; - -@Slf4j -@RestController -@ConditionalOnProperty( - value = "netgrif.engine.security.web.public-web.petri-net-enabled", - havingValue = "true", - matchIfMissing = true -) -@Tag(name = "Public PetriNet Controller") -@RequestMapping({"/api/public/petrinet"}) -public class PublicPetriNetController { - - private final IPetriNetService petriNetService; - - private final ProcessRoleService roleService; - - private final UserService userService; - - private final StringToVersionConverter converter; - - public PublicPetriNetController(IPetriNetService petriNetService, UserService userService, StringToVersionConverter converter, ProcessRoleService roleService) { - this.petriNetService = petriNetService; - this.converter = converter; - this.userService = userService; - this.roleService = roleService; - } - - @GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) - @Operation(summary = "Get process by id") - public PetriNetReferenceResource getOne(@PathVariable("id") String id, Locale locale) { - return new PetriNetReferenceResource(IPetriNetService.transformToReference(this.petriNetService.getPetriNet(decodeUrl(id)), locale)); - } - - @Operation(summary = "Get process by identifier and version") - @GetMapping(value = "/{identifier}/{version}", produces = MediaTypes.HAL_JSON_VALUE) - @ResponseBody - public PetriNetReferenceResource getOne(@PathVariable("identifier") String identifier, @PathVariable("version") String version, Locale locale) { - String resolvedIdentifier = Base64.isBase64(identifier) ? new String(Base64.decodeBase64(identifier)) : identifier; - return new PetriNetReferenceResource(this.petriNetService.getReference(resolvedIdentifier, this.converter.convert(version), ActorTransformer.toLoggedUser(userService.getLoggedUser()), locale)); - } - - @Operation(summary = "Search processes") - @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel searchPetriNets(@RequestBody PetriNetSearch criteria, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - Page nets = petriNetService.search(criteria, ActorTransformer.toLoggedUser(userService.getLoggedUser()), pageable, locale); -// Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PublicPetriNetController.class) -// .searchPetriNets(criteria, pageable, assembler, locale)).withRel("search"); -// PagedModel resources = assembler.toModel(nets, new PetriNetReferenceResourceAssembler(), selfLink); -// PetriNetReferenceResourceAssembler.buildLinks(resources); - return PagedModel.of(nets.stream().map(PetriNetReferenceResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), nets.getTotalElements())); - } - - @Operation(summary = "Get roles of process") - @GetMapping(value = "/{netId}/roles", produces = MediaTypes.HAL_JSON_VALUE) - public ProcessRolesResource getRoles(@PathVariable("netId") String netId, Locale locale) { - netId = decodeUrl(netId); - return new ProcessRolesResource(roleService.findAllByNetStringId(netId), petriNetService.getPetriNet(netId).getPermissions(), netId, locale); - } - - @Operation(summary = "Get transactions of process") - @GetMapping(value = "/{netId}/transactions", produces = MediaTypes.HAL_JSON_VALUE) - public TransactionsResource getTransactions(@PathVariable("netId") String netId, Locale locale) { - PetriNet net = petriNetService.getPetriNet(decodeUrl(netId)); - return new TransactionsResource(net.getTransactions().values(), netId, locale); - } - - @Operation(summary = "Get data fields of transitions") - @PostMapping(value = "/data", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public DataFieldReferencesResource getDataFieldReferences(@RequestBody List referenceBody, Locale locale) { - return new DataFieldReferencesResource(petriNetService.getDataFieldReferences(referenceBody, locale)); - } - - @Operation(summary = "Get transitions of processes") - @GetMapping(value = "/transitions", produces = MediaTypes.HAL_JSON_VALUE) - public TransitionReferencesResource getTransitionReferences(@RequestParam List ids, Locale locale) { - ids.forEach(PetriNetController::decodeUrl); - return new TransitionReferencesResource(petriNetService.getTransitionReferences(ids, ActorTransformer.toLoggedUser(userService.getLoggedUser()), locale)); - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/PetriNetReferenceResource.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/PetriNetReferenceResource.java index 9bc3f8e23b0..6966842cee6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/PetriNetReferenceResource.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/PetriNetReferenceResource.java @@ -1,6 +1,5 @@ package com.netgrif.application.engine.petrinet.web.responsebodies; -import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference; import com.netgrif.application.engine.petrinet.web.PetriNetController; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; @@ -14,11 +13,11 @@ public PetriNetReferenceResource(PetriNetReference content) { private void buildLinks() { add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(PetriNetController.class).getOne(getContent().getStringId(), null, null)) + .methodOn(PetriNetController.class).getOne(getContent().getStringId(), null)) .withSelfRel()); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(PetriNetController.class).getOne(getContent().getIdentifier(), getContent().getVersion(), null, null)) + .methodOn(PetriNetController.class).getOne(getContent().getIdentifier(), getContent().getVersion(), null)) .withRel("identifier")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder @@ -30,7 +29,7 @@ private void buildLinks() { .withRel("transaction")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(PetriNetController.class).getNetFile(getContent().getStringId(), getContent().getTitle(), null, null)) + .methodOn(PetriNetController.class).getNetFile(getContent().getStringId(), getContent().getTitle(), null)) .withRel("file")); } } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/TransitionReferencesResource.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/TransitionReferencesResource.java index 171ea104f23..f26325326d1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/TransitionReferencesResource.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/TransitionReferencesResource.java @@ -17,6 +17,6 @@ public TransitionReferencesResource(Iterable content) { private void buildLinks() { add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PetriNetController.class) - .getTransitionReferences(new ArrayList<>(), null, null)).withSelfRel()); + .getTransitionReferences(new ArrayList<>(), null)).withSelfRel()); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java index e116d4dc74d..bfbe64ddbf2 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.startup.runner; import com.netgrif.application.engine.auth.service.RealmService; +import com.netgrif.application.engine.configuration.properties.SecurityConfigurationProperties; import com.netgrif.application.engine.startup.ApplicationEngineStartupRunner; import com.netgrif.application.engine.startup.annotation.RunnerOrder; import com.netgrif.application.engine.objects.auth.domain.Realm; @@ -19,6 +20,8 @@ public class DefaultRealmRunner implements ApplicationEngineStartupRunner { private final RealmService realmService; + private final SecurityConfigurationProperties.WebProperties webProperties; + @Override public void run(ApplicationArguments args) throws Exception { if (realmService.getDefaultRealm().isPresent()) { @@ -29,6 +32,9 @@ public void run(ApplicationArguments args) throws Exception { createRequest.setDescription("Default realm"); createRequest.setAdminRealm(true); createRequest.setDefaultRealm(true); - realmService.createRealm(createRequest); + Realm realm = realmService.createRealm(createRequest); + if (webProperties.getPublicWeb().isEnabled()) { + realmService.enableAnonymUser(realm); + } } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpReqRespUtils.java b/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpReqRespUtils.java new file mode 100644 index 00000000000..00e198c527e --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpReqRespUtils.java @@ -0,0 +1,68 @@ +package com.netgrif.application.engine.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.netgrif.application.engine.adapter.spring.configuration.filters.requests.NetgrifHttpServletRequest; +import com.netgrif.application.engine.auth.domain.NetgrifAuthenticationToken; +import com.netgrif.application.engine.objects.auth.domain.Realm; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + + +public class HttpReqRespUtils { + + private static final String[] IP_HEADER_CANDIDATES = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR" + }; + + public static String getClientIpAddressIfServletRequestExists() { + + if (RequestContextHolder.getRequestAttributes() == null) { + return "0.0.0.0"; + } + + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + return getClientIpAddressIfServletRequestExists(request); + } + + public static String getClientIpAddressIfServletRequestExists(HttpServletRequest request) { + for (String header : IP_HEADER_CANDIDATES) { + String ipList = request.getHeader(header); + if (ipList != null && !ipList.isEmpty() && !"unknown".equalsIgnoreCase(ipList)) { + return ipList.split(",")[0]; + } + } + + return request.getRemoteAddr(); + } + + public static HttpServletRequest getRequestIfExists() { + if (RequestContextHolder.getRequestAttributes() == null) { + return null; + } + + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + + public static Realm extractRealmFromRequest(NetgrifHttpServletRequest request) { + return (Realm) request.getAdditionalParameter(HttpRequestParamConstants.REALM); + } + + public static JsonNode extractBodyFromRequest(NetgrifHttpServletRequest request) { + return (JsonNode) request.getAdditionalParameter(HttpRequestParamConstants.REQUEST_BODY); + } + + public static NetgrifAuthenticationToken extractAuthReqTokenFromRequest(NetgrifHttpServletRequest request) { + return (NetgrifAuthenticationToken) request.getAdditionalParameter(HttpRequestParamConstants.AUTH_REQ_TOKEN); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpRequestParamConstants.java b/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpRequestParamConstants.java new file mode 100644 index 00000000000..9d4a73d08e9 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/utils/HttpRequestParamConstants.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.utils; + +public class HttpRequestParamConstants { + public static final String REQUEST_BODY = "request_body"; + public static final String REALM = "realm"; + public static final String AUTH_REQ_TOKEN = "auth_req_token"; +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/AbstractAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/AbstractAuthorizationService.java index a24222a4929..268ce0ab043 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/AbstractAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/AbstractAuthorizationService.java @@ -31,7 +31,7 @@ protected Map getAggregatePermissions(AbstractUser user, Map> role, Map aggregatePermissions) { + private void aggregatePermission(Set userProcessRoleIDs, Map.Entry> role, Map aggregatePermissions) { if (userProcessRoleIDs.contains(role.getKey())) { for (Map.Entry permission : role.getValue().entrySet()) { if (aggregatePermissions.containsKey(permission.getKey())) { 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 3ed448f14d1..a4898c9f74d 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 @@ -1,14 +1,14 @@ package com.netgrif.application.engine.workflow.service; -import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; -import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.objects.auth.domain.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; import com.netgrif.application.engine.workflow.service.interfaces.ITaskAuthorizationService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -16,17 +16,14 @@ import java.util.Map; @Service +@RequiredArgsConstructor public class TaskAuthorizationService extends AbstractAuthorizationService implements ITaskAuthorizationService { - @Autowired - ITaskService taskService; - - @Autowired - UserService userService; + private final ITaskService taskService; @Override - public Boolean userHasAtLeastOneRolePermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { - return userHasAtLeastOneRolePermission(userService.transformToUser((LoggedUserImpl) loggedUser), taskService.findById(taskId), permissions); + public Boolean userHasAtLeastOneRolePermission(AbstractUser user, String taskId, RolePermission... permissions) { + return userHasAtLeastOneRolePermission(user, taskService.findById(taskId), permissions); } @Override @@ -46,8 +43,8 @@ public Boolean userHasAtLeastOneRolePermission(AbstractUser user, Task task, Rol } @Override - public Boolean userHasUserListPermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { - return userHasUserListPermission(userService.transformToUser((LoggedUserImpl) loggedUser), taskService.findById(taskId), permissions); + public Boolean userHasUserListPermission(AbstractUser user, String taskId, RolePermission... permissions) { + return userHasUserListPermission(user, taskService.findById(taskId), permissions); } @Override @@ -74,11 +71,6 @@ public Boolean userHasUserListPermission(AbstractUser user, Task task, RolePermi return Arrays.stream(permissions).anyMatch(permission -> hasPermission(userPermissions.get(permission.toString()))); } - @Override - public boolean isAssignee(LoggedUser loggedUser, String taskId) { - return isAssignee(userService.transformToUser((LoggedUserImpl) loggedUser), taskService.findById(taskId)); - } - @Override public boolean isAssignee(AbstractUser user, String taskId) { return isAssignee(user, taskService.findById(taskId)); @@ -86,50 +78,43 @@ public boolean isAssignee(AbstractUser user, String taskId) { @Override public boolean isAssignee(AbstractUser user, Task task) { - if (!isAssigned(task)) + if (!isAssigned(task)) { return false; - else + } else { // TODO: impersonation // return task.getUserId().equals(user.getSelfOrImpersonated().getStringId()) || (Boolean) user.getAttributeValue("anonymous"); return task.getUserId().equals(user.getStringId()) || (Boolean) user.getAttributeValue("anonymous"); - } - - private boolean isAssigned(String taskId) { - return isAssigned(taskService.findById(taskId)); - } - - private boolean isAssigned(Task task) { - return task.getUserId() != null; + } } @Override - public boolean canCallAssign(LoggedUser loggedUser, String taskId) { - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, taskId, RolePermission.ASSIGN); - Boolean userPerm = userHasUserListPermission(loggedUser, taskId, RolePermission.ASSIGN); + public boolean canCallAssign(AbstractUser user, String taskId) { + Boolean rolePerm = userHasAtLeastOneRolePermission(user, taskId, RolePermission.ASSIGN); + Boolean userPerm = userHasUserListPermission(user, taskId, RolePermission.ASSIGN); // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); - return loggedUser.isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); + return user.isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); } @Override - public boolean canCallDelegate(LoggedUser loggedUser, String taskId) { - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, taskId, RolePermission.DELEGATE); - Boolean userPerm = userHasUserListPermission(loggedUser, taskId, RolePermission.DELEGATE); + public boolean canCallDelegate(AbstractUser user, String taskId) { + Boolean rolePerm = userHasAtLeastOneRolePermission(user, taskId, RolePermission.DELEGATE); + Boolean userPerm = userHasUserListPermission(user, taskId, RolePermission.DELEGATE); // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); - return loggedUser.isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); + return user.isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); } @Override - public boolean canCallFinish(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException { + public boolean canCallFinish(AbstractUser user, String taskId) throws IllegalTaskStateException { if (!isAssigned(taskId)) throw new IllegalTaskStateException("Task with ID '" + taskId + "' cannot be finished, because it is not assigned!"); - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, taskId, RolePermission.FINISH); - Boolean userPerm = userHasUserListPermission(loggedUser, taskId, RolePermission.FINISH); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, taskId, RolePermission.FINISH); + Boolean userPerm = userHasUserListPermission(user, taskId, RolePermission.FINISH); // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(loggedUser, taskId)); - return loggedUser.isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(loggedUser, taskId)); + return user.isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(user, taskId)); } private boolean canAssignedCancel(AbstractUser user, String taskId) { @@ -143,28 +128,36 @@ private boolean canAssignedCancel(AbstractUser user, String taskId) { } @Override - public boolean canCallCancel(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException { + public boolean canCallCancel(AbstractUser user, String taskId) throws IllegalTaskStateException { if (!isAssigned(taskId)) throw new IllegalTaskStateException("Task with ID '" + taskId + "' cannot be canceled, because it is not assigned!"); - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, taskId, RolePermission.CANCEL); - Boolean userPerm = userHasUserListPermission(loggedUser, taskId, RolePermission.CANCEL); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, taskId, RolePermission.CANCEL); + Boolean userPerm = userHasUserListPermission(user, taskId, RolePermission.CANCEL); // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(loggedUser, taskId)) && canAssignedCancel(userService.transformToUser((LoggedUserImpl) loggedUser), taskId); - return loggedUser.isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(loggedUser, taskId)) && canAssignedCancel(userService.transformToUser((LoggedUserImpl) loggedUser), taskId); + return user.isAdmin() || ((userPerm == null ? (rolePerm != null && rolePerm) : userPerm) && isAssignee(user, taskId)) && canAssignedCancel(user, taskId); } @Override - public boolean canCallSaveData(LoggedUser loggedUser, String taskId) { + public boolean canCallSaveData(AbstractUser user, String taskId) { // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || isAssignee(loggedUser, taskId); - return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); + return user.isAdmin() || isAssignee(user, taskId); } @Override - public boolean canCallSaveFile(LoggedUser loggedUser, String taskId) { + public boolean canCallSaveFile(AbstractUser user, String taskId) { // TODO: impersonation // return loggedUser.getSelfOrImpersonated().isAdmin() || isAssignee(loggedUser, taskId); - return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); + return user.isAdmin() || isAssignee(user, taskId); + } + + private boolean isAssigned(String taskId) { + return isAssigned(taskService.findById(taskId)); + } + + private boolean isAssigned(Task task) { + return task.getUserId() != null; } } 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 cc81367878a..b59eb374af1 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 @@ -984,7 +984,7 @@ public SetDataEventOutcome getMainOutcome(Map outco } protected AbstractUser getUserFromLoggedUser(LoggedUser loggedUser) { - AbstractUser user = userService.findById(loggedUser.getStringId(), loggedUser.getRealmId()); + AbstractUser user = ActorTransformer.toUser(loggedUser); // TODO: impersonation // AbstractUser fromLogged = userService.transformToUser((LoggedUserImpl) loggedUser); // user.setImpersonated(fromLogged.getImpersonated()); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java index 707e33353dc..4891ceabe2a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java @@ -1,8 +1,6 @@ package com.netgrif.application.engine.workflow.service; -import com.netgrif.application.engine.auth.service.UserService; 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.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRolePermission; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; @@ -24,26 +22,23 @@ public class WorkflowAuthorizationService extends AbstractAuthorizationService i @Autowired private IPetriNetService petriNetService; - @Autowired - private UserService userService; - @Override - public boolean canCallDelete(LoggedUser user, String caseId) { + public boolean canCallDelete(AbstractUser user, String caseId) { Case requestedCase = workflowService.findOne(caseId); // TODO: impersonation // Boolean rolePerm = userHasAtLeastOneRolePermission(userService.transformToUser((LoggedUserImpl) user.getSelfOrImpersonated()), requestedCase.getPetriNet(), ProcessRolePermission.DELETE); // Boolean userPerm = userHasUserListPermission(userService.transformToUser((LoggedUserImpl) user.getSelfOrImpersonated()), requestedCase, ProcessRolePermission.DELETE); // return user.getSelfOrImpersonated().isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); - Boolean rolePerm = userHasAtLeastOneRolePermission(userService.transformToUser(user), requestedCase.getPetriNet(), ProcessRolePermission.DELETE); - Boolean userPerm = userHasUserListPermission(userService.transformToUser(user), requestedCase, ProcessRolePermission.DELETE); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, requestedCase.getPetriNet(), ProcessRolePermission.DELETE); + Boolean userPerm = userHasUserListPermission(user, requestedCase, ProcessRolePermission.DELETE); return user.isAdmin() || (userPerm == null ? (rolePerm != null && rolePerm) : userPerm); } @Override - public boolean canCallCreate(LoggedUser user, String netId) { + public boolean canCallCreate(AbstractUser user, String netId) { PetriNet net = petriNetService.getPetriNet(netId); // TODO: impersonation - return user.isAdmin() || userHasAtLeastOneRolePermission(userService.transformToUser(user), net, ProcessRolePermission.CREATE); + return user.isAdmin() || userHasAtLeastOneRolePermission(user, net, ProcessRolePermission.CREATE); } @Override 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..0edd405291b 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 @@ -7,30 +7,28 @@ import com.netgrif.application.engine.objects.workflow.domain.Task; public interface ITaskAuthorizationService { - Boolean userHasAtLeastOneRolePermission(LoggedUser loggedUser, String taskId, RolePermission... permissions); + Boolean userHasAtLeastOneRolePermission(AbstractUser user, String taskId, RolePermission... permissions); Boolean userHasAtLeastOneRolePermission(AbstractUser user, Task task, RolePermission... permissions); - Boolean userHasUserListPermission(LoggedUser loggedUser, String taskId, RolePermission... permissions); + Boolean userHasUserListPermission(AbstractUser user, 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 user, String taskId); - boolean canCallDelegate(LoggedUser loggedUser, String taskId); + boolean canCallDelegate(AbstractUser user, String taskId); - boolean canCallFinish(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException; + boolean canCallFinish(AbstractUser user, String taskId) throws IllegalTaskStateException; - boolean canCallCancel(LoggedUser loggedUser, String taskId) throws IllegalTaskStateException; + boolean canCallCancel(AbstractUser user, String taskId) throws IllegalTaskStateException; - boolean canCallSaveData(LoggedUser loggedUser, String taskId); + boolean canCallSaveData(AbstractUser user, String taskId); - boolean canCallSaveFile(LoggedUser loggedUser, String taskId); + boolean canCallSaveFile(AbstractUser user, String taskId); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java index f99f950a31e..712cabab6a1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java @@ -8,9 +8,9 @@ public interface IWorkflowAuthorizationService { - boolean canCallDelete(LoggedUser user, String caseId); + boolean canCallDelete(AbstractUser user, String caseId); - boolean canCallCreate(LoggedUser user, String netId); + boolean canCallCreate(AbstractUser user, String netId); Boolean userHasAtLeastOneRolePermission(AbstractUser user, PetriNet net, ProcessRolePermission... permissions); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java index 43d3ce9b7d3..f6e06e5e5fe 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java @@ -3,12 +3,16 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService; import com.netgrif.application.engine.elastic.web.requestbodies.singleaslist.SingleElasticTaskSearchRequestAsList; import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; import com.netgrif.application.engine.objects.petrinet.domain.dataset.FieldType; import com.netgrif.application.engine.objects.petrinet.domain.dataset.localised.LocalisedField; +import com.netgrif.application.engine.workflow.web.requestbodies.TaskSearchRequest; +import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.TaskSearchCaseRequest; import com.netgrif.application.engine.workflow.web.responsebodies.LocalisedTaskResource; import com.netgrif.application.engine.objects.petrinet.domain.throwable.TransitionNotExecutableException; import com.netgrif.application.engine.workflow.domain.IllegalArgumentWithChangedFieldsException; @@ -25,6 +29,7 @@ import com.netgrif.application.engine.workflow.web.requestbodies.file.FileFieldRequest; import com.netgrif.application.engine.workflow.web.requestbodies.singleaslist.SingleTaskSearchRequestAsList; import com.netgrif.application.engine.workflow.web.responsebodies.*; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.i18n.LocaleContextHolder; @@ -47,6 +52,7 @@ import java.util.*; import java.util.stream.Collectors; +@RequiredArgsConstructor public abstract class AbstractTaskController { public static final Logger log = LoggerFactory.getLogger(TaskController.class); @@ -61,32 +67,20 @@ public abstract class AbstractTaskController { private final UserService userService; - public AbstractTaskController(ITaskService taskService, - IDataService dataService, - IElasticTaskService searchService, - IWorkflowService workflowService, - UserService userService) { - this.taskService = taskService; - this.dataService = dataService; - this.searchService = searchService; - this.workflowService = workflowService; - this.userService = userService; - } - - - public PagedModel getAll(Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - Page page = taskService.getAll(loggedUser, pageable, locale); + public PagedModel getAll(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); + Page page = taskService.search(Collections.emptyList(), pageable, loggedUser, locale, Boolean.FALSE); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getAll(auth, pageable, assembler, locale)).withRel("all"); + .getAll(pageable, assembler, locale)).withRel("all"); PagedModel resources = assembler.toModel(page, new TaskResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); return PagedModel.of(page.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), page.getTotalElements())); } public PagedModel getAllByCases(List cases, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - Page page = taskService.findByCases(pageable, cases); - + List requests = cases.stream().map(c -> TaskSearchRequest.from().useCase(List.of(TaskSearchCaseRequest.builder().id(c).build())).build()).toList(); + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); + Page page = taskService.search(requests, pageable, loggedUser, locale, false); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) .getAllByCases(cases, pageable, assembler, locale)).withRel("case"); PagedModel resources = assembler.toModel(page, new TaskResourceAssembler(locale), selfLink); @@ -105,8 +99,9 @@ public LocalisedTaskResource getOne(String taskId, Locale locale) { return new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(task, locale)); } - public EntityModel assign(LoggedUser loggedUser, String taskId, Locale locale) { + public EntityModel assign(String taskId, Locale locale) { try { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); return EventOutcomeWithMessageResource.successMessage("LocalisedTask " + taskId + " assigned to " + loggedUser.getName(), LocalisedEventOutcomeFactory.from(taskService.assignTask(loggedUser, taskId), locale)); } catch (TransitionNotExecutableException e) { @@ -115,8 +110,9 @@ public EntityModel assign(LoggedUser loggedUser, String } } - public EntityModel delegate(LoggedUser loggedUser, String taskId, String delegatedId, Locale locale) { + public EntityModel delegate(String taskId, String delegatedId, Locale locale) { try { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); return EventOutcomeWithMessageResource.successMessage("LocalisedTask " + taskId + " assigned to [" + delegatedId + "]", LocalisedEventOutcomeFactory.from(taskService.delegateTask(loggedUser, delegatedId, taskId), locale)); } catch (Exception e) { @@ -125,9 +121,9 @@ public EntityModel delegate(LoggedUser loggedUser, Stri } } - public EntityModel finish(LoggedUser loggedUser, String taskId, Locale locale) { - + public EntityModel finish(String taskId, Locale locale) { try { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); return EventOutcomeWithMessageResource.successMessage("LocalisedTask " + taskId + " finished", LocalisedEventOutcomeFactory.from(taskService.finishTask(loggedUser, taskId), locale)); } catch (Exception e) { @@ -140,8 +136,9 @@ public EntityModel finish(LoggedUser loggedUser, String } } - public EntityModel cancel(LoggedUser loggedUser, String taskId, Locale locale) { + public EntityModel cancel(String taskId, Locale locale) { try { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); return EventOutcomeWithMessageResource.successMessage("LocalisedTask " + taskId + " canceled", LocalisedEventOutcomeFactory.from(taskService.cancelTask(loggedUser, taskId), locale)); } catch (Exception e) { @@ -154,53 +151,56 @@ public EntityModel cancel(LoggedUser loggedUser, String } } - public PagedModel getMy(Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - Page page = taskService.findByUser(pageable, userService.transformToUser(((LoggedUserImpl) auth.getPrincipal()))); + public PagedModel getMy(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + Page page = taskService.findByUser(pageable, userService.getLoggedUser()); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getMy(auth, pageable, assembler, locale)).withRel("my"); + .getMy(pageable, assembler, locale)).withRel("my"); PagedModel resources = assembler.toModel(page, new TaskResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); return PagedModel.of(page.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), page.getTotalElements())); } - public PagedModel getMyFinished(Pageable pageable, Authentication auth, PagedResourcesAssembler assembler, Locale locale) { - Page page = taskService.findByUser(pageable, userService.transformToUser(((LoggedUserImpl) auth.getPrincipal()))); + public PagedModel getMyFinished(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + Page page = taskService.findByUser(pageable, userService.getLoggedUser()); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getMyFinished(pageable, auth, assembler, locale)).withRel("finished"); + .getMyFinished(pageable, assembler, locale)).withRel("finished"); PagedModel resources = assembler.toModel(page, new TaskResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); return PagedModel.of(page.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), page.getTotalElements())); } - public PagedModel search(Authentication auth, Pageable pageable, SingleTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - Page tasks = taskService.search(searchBody.getList(), pageable, (LoggedUser) auth.getPrincipal(), locale, operation == MergeFilterOperation.AND); - Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .search(auth, pageable, searchBody, operation, assembler, locale)).withRel("search"); - PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); - ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); - return PagedModel.of(tasks.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), tasks.getTotalElements())); - } - - public PagedModel searchPublic(LoggedUser loggedUser, Pageable pageable, SingleTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + public PagedModel search(Pageable pageable, SingleTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); Page tasks = taskService.search(searchBody.getList(), pageable, loggedUser, locale, operation == MergeFilterOperation.AND); - Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PublicTaskController.class) - .searchPublic(loggedUser, pageable, searchBody, operation, assembler, locale)).withRel("search"); + Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) + .search(pageable, searchBody, operation, assembler, locale)).withRel("search"); PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); return PagedModel.of(tasks.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), tasks.getTotalElements())); } - public PagedModel searchElastic(Authentication auth, Pageable pageable, SingleElasticTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - Page tasks = searchService.search(searchBody.getList(), (LoggedUser) auth.getPrincipal(), pageable, locale, operation == MergeFilterOperation.AND); +// public PagedModel searchPublic(LoggedUser loggedUser, Pageable pageable, SingleTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { +// Page tasks = taskService.search(searchBody.getList(), pageable, loggedUser, locale, operation == MergeFilterOperation.AND); +// Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(PublicTaskController.class) +// .searchPublic(loggedUser, pageable, searchBody, operation, assembler, locale)).withRel("search"); +// PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); +// ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); +// return PagedModel.of(tasks.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), tasks.getTotalElements())); +// } + + public PagedModel searchElastic(Pageable pageable, SingleElasticTaskSearchRequestAsList searchBody, MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); + Page tasks = searchService.search(searchBody.getList(), loggedUser, pageable, locale, operation == MergeFilterOperation.AND); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .searchElastic(auth, pageable, searchBody, operation, assembler, locale)).withRel("search_es"); + .searchElastic(pageable, searchBody, operation, assembler, locale)).withRel("search_es"); PagedModel resources = assembler.toModel(tasks, new TaskResourceAssembler(locale), selfLink); ResourceLinkAssembler.addLinks(resources, Task.class, selfLink.getRel().toString()); return PagedModel.of(tasks.stream().map(t -> new LocalisedTaskResource(new com.netgrif.application.engine.workflow.web.responsebodies.Task(t, locale))).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), tasks.getTotalElements())); } - public CountResponse count(SingleElasticTaskSearchRequestAsList query, MergeFilterOperation operation, Authentication auth, Locale locale) { - long count = searchService.count(query.getList(), (LoggedUser) auth.getPrincipal(), locale, operation == MergeFilterOperation.AND); + public CountResponse count(SingleElasticTaskSearchRequestAsList query, MergeFilterOperation operation, Locale locale) { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); + long count = searchService.count(query.getList(), loggedUser, locale, operation == MergeFilterOperation.AND); return CountResponse.taskCount(count); } 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 deleted file mode 100644 index 932db377fe0..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.netgrif.application.engine.workflow.web; - -import com.fasterxml.jackson.databind.node.ObjectNode; -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; -import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; - -import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessage; -import com.netgrif.application.engine.workflow.service.interfaces.IDataService; -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; -import com.netgrif.application.engine.workflow.web.requestbodies.file.FileFieldRequest; -import com.netgrif.application.engine.workflow.web.requestbodies.singleaslist.SingleTaskSearchRequestAsList; -import com.netgrif.application.engine.workflow.web.responsebodies.LocalisedTaskResource; -import com.netgrif.application.engine.workflow.web.responsebodies.TaskReference; -import com.netgrif.application.engine.objects.workflow.domain.Task; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.core.io.Resource; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PagedResourcesAssembler; -import org.springframework.hateoas.EntityModel; -import org.springframework.hateoas.MediaTypes; -import org.springframework.hateoas.PagedModel; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Locale; - -@Slf4j -@RestController -@Tag(name = "Public Task Controller") -@ConditionalOnProperty( - value = "netgrif.engine.security.web.public-web.task-enabled", - havingValue = "true", - matchIfMissing = true -) -@RequestMapping({"/api/public/task"}) -public class PublicTaskController extends AbstractTaskController { - - final UserService userService; - private final ITaskService taskService; - private final IDataService dataService; - - public PublicTaskController(ITaskService taskService, - IDataService dataService, - IWorkflowService workflowService, - UserService userService) { - super(taskService, dataService, null, workflowService, userService); - this.taskService = taskService; - this.dataService = dataService; - this.userService = userService; - } - - @Override - @GetMapping(value = "/case/{id}", produces = "application/json;charset=UTF-8") - @Operation(summary = "Get tasks of the case") - public List getTasksOfCase(@PathVariable("id") String caseId, Locale locale) { - return this.taskService.findAllByCase(caseId, locale); - } - - @PreAuthorize("@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( - responseCode = "200", - description = "OK" - ), @ApiResponse( - responseCode = "403", - description = "Caller doesn't fulfill the authorisation requirements" - )}) - public EntityModel assign(@PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); - return super.assign(loggedUser, taskId, locale); - } - - @PreAuthorize("@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( - responseCode = "200", - description = "OK" - ), @ApiResponse( - responseCode = "403", - description = "Caller doesn't fulfill the authorisation requirements" - )}) - public EntityModel finish(@PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); - return super.finish(loggedUser, taskId, locale); - } - - @PreAuthorize("@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( - responseCode = "200", - description = "OK" - ), @ApiResponse( - responseCode = "403", - description = "Caller doesn't fulfill the authorisation requirements" - )}) - public EntityModel cancel(@PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); - return super.cancel(loggedUser, taskId, locale); - } - - @Override - @GetMapping(value = "/{id}/data", produces = MediaTypes.HAL_JSON_VALUE) - @Operation(summary = "Get all task data") - public EntityModel getData(@PathVariable("id") String taskId, Locale locale) { - return super.getData(taskId, locale); - } - - @Override - @PreAuthorize("@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( - responseCode = "200", - description = "OK" - ), @ApiResponse( - responseCode = "403", - description = "Caller doesn't fulfill the authorisation requirements" - )}) - public EntityModel setData(@PathVariable("id") String taskId, @RequestBody ObjectNode dataBody, Locale locale) { - return super.setData(taskId, dataBody, locale); - } - - @PreAuthorize("@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) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - }) - public EntityModel saveFile(Authentication auth, @PathVariable("id") String taskId, @RequestPart(value = "data") FileFieldRequest dataBody, @RequestPart(value = "file") MultipartFile multipartFile, Locale locale){ - return super.saveFile(taskId, multipartFile, dataBody, locale); - } - - @Override - @Operation(summary = "Download task file field value") - @GetMapping(value = "/{id}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity getFile(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { - return super.getFile(taskId, fieldId); - } - - @PreAuthorize("@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) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - }) - public EntityModel deleteFile(@PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { - return super.deleteFile(requestBody.getParentTaskId(), requestBody.getFieldId()); - } - - @Override - @Operation(summary = "Download preview for file field value") - @GetMapping(value = "/{id}/file_preview", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity getFilePreview(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { - return super.getFilePreview(taskId, fieldId); - } - - @Override - @PreAuthorize("@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) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - }) - public EntityModel saveFiles(@PathVariable("id") String taskId, @RequestPart(value = "files") MultipartFile[] multipartFiles, @RequestPart(value = "data") FileFieldRequest requestBody) { - return super.saveFiles(taskId, multipartFiles, requestBody); - } - - @Override - @Operation(summary = "Download one file from tasks file list field value") - @GetMapping(value = "/{id}/file/named", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public ResponseEntity getNamedFile(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId, @RequestParam("fileName") String fileName) throws FileNotFoundException { - return super.getNamedFile(taskId, fieldId, fileName); - } - - @PreAuthorize("@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) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK"), - @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), - }) - public EntityModel deleteNamedFile(@PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { - return super.deleteNamedFile(requestBody.getParentTaskId(), requestBody.getFieldId(), requestBody.getFileName()); - } - - @Operation(summary = "Generic task search on Mongo database") - @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel search(Pageable pageable, @RequestBody SingleTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - return super.searchPublic(ActorTransformer.toLoggedUser(userService.getLoggedUser()), pageable, searchBody, operation, assembler, locale); - } -} 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 deleted file mode 100644 index 0683e405e4b..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.netgrif.application.engine.workflow.web; - -import com.netgrif.application.engine.eventoutcomes.LocalisedEventOutcomeFactory; -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; -import com.netgrif.application.engine.objects.workflow.domain.eventoutcomes.caseoutcomes.CreateCaseEventOutcome; -import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessage; -import com.netgrif.application.engine.workflow.domain.eventoutcomes.response.EventOutcomeWithMessageResource; -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; -import com.netgrif.application.engine.workflow.web.requestbodies.CreateCaseBody; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.hateoas.EntityModel; -import org.springframework.hateoas.MediaTypes; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Locale; - -@Slf4j -@RestController -@ConditionalOnProperty( - value = "netgrif.engine.security.web.public-web.case-enabled", - havingValue = "true", - matchIfMissing = true -) -@RequestMapping({"/api/public"}) -@Tag(name = "Public Workflow Controller") -public class PublicWorkflowController { - - private final IWorkflowService workflowService; - - private final UserService userService; - - public PublicWorkflowController(IWorkflowService workflowService, UserService userService) { - this.userService = userService; - this.workflowService = workflowService; - } - - @PreAuthorize("@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) { - LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); - try { - CreateCaseEventOutcome outcome = this.workflowService.createCase(body.netId, body.title, body.color, loggedUser, locale); - return EventOutcomeWithMessageResource.successMessage("Case created successfully", - LocalisedEventOutcomeFactory.from(outcome, locale)); - } catch (Exception e) { - log.error("Creating case failed:" + e.getMessage(), e); - return EventOutcomeWithMessageResource.errorMessage("Creating case failed: " + e.getMessage()); - } - } -} 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 2de3c52fed3..9427306bd89 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 @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.netgrif.application.engine.auth.service.UserService; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService; import com.netgrif.application.engine.elastic.web.requestbodies.singleaslist.SingleElasticTaskSearchRequestAsList; import com.netgrif.application.engine.workflow.domain.MergeFilterOperation; @@ -21,6 +20,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -33,7 +34,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -58,52 +58,55 @@ public TaskController(ITaskService taskService, IElasticTaskService searchService, IWorkflowService workflowService, UserService userService) { - super(taskService, dataService, searchService, workflowService, userService); + super(taskService, dataService, workflowService, searchService, userService); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get all tasks", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel getAll(Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - return super.getAll(auth, pageable, assembler, locale); + public PagedModel getAll(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + return super.getAll(pageable, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get all tasks by cases", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/case", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + @PostMapping(value = {"/case", "/public/case"}, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel getAllByCases(@RequestBody List cases, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { return super.getAllByCases(cases, pageable, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get tasks of the case", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/case/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = {"/case/{id}", "/public/case/{id}"}, produces = MediaType.APPLICATION_JSON_VALUE) public List getTasksOfCase(@PathVariable("id") String caseId, Locale locale) { return super.getTasksOfCase(caseId, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get task by id", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/{id}", produces = MediaTypes.HAL_JSON_VALUE) public LocalisedTaskResource getOne(@PathVariable("id") String taskId, Locale locale) { return super.getOne(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallAssign(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallAssign(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Assign task", description = "Caller must be able to perform the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/assign/{id}", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/assign/{id}", "/public/assign/{id}"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel assign(Authentication auth, @PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - return super.assign(loggedUser, taskId, locale); + public EntityModel assign(@PathVariable("id") String taskId, Locale locale) { + return super.assign(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallDelegate(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN') && @taskAuthorizationService.canCallDelegate(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Delegate task", description = "Caller must be able to delegate the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) @@ -112,161 +115,167 @@ public EntityModel assign(Authentication auth, @PathVar @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel delegate(Authentication auth, @PathVariable("id") String taskId, @RequestBody String delegatedId, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - return super.delegate(loggedUser, taskId, delegatedId, locale); + public EntityModel delegate(@PathVariable("id") String taskId, @RequestBody String delegatedId, Locale locale) { + return super.delegate(taskId, delegatedId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallFinish(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallFinish(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Finish task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/finish/{id}", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/finish/{id}", "/public/finish/{id}"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel finish(Authentication auth, @PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - return super.finish(loggedUser, taskId, locale); + public EntityModel finish(@PathVariable("id") String taskId, Locale locale) { + return super.finish(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallCancel(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallCancel(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Cancel task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/cancel/{id}", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/cancel/{id}", "/public/cancel/{id}"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel cancel(Authentication auth, @PathVariable("id") String taskId, Locale locale) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); - return super.cancel(loggedUser, taskId, locale); + public EntityModel cancel(@PathVariable("id") String taskId, Locale locale) { + return super.cancel(taskId, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get all tasks assigned to logged user", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/my", produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel getMy(Authentication auth, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { - return super.getMy(auth, pageable, assembler, locale); + public PagedModel getMy(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + return super.getMy(pageable, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get all finished tasks by logged user", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/my/finished", produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel getMyFinished(Pageable pageable, Authentication auth, PagedResourcesAssembler assembler, Locale locale) { - return super.getMyFinished(pageable, auth, assembler, locale); + public PagedModel getMyFinished(Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + return super.getMyFinished(pageable, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Generic task search on Mongo database", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel search(Authentication auth, Pageable pageable, @RequestBody SingleTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - return super.search(auth, pageable, searchBody, operation, assembler, locale); + public PagedModel search(Pageable pageable, @RequestBody SingleTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + return super.search(pageable, searchBody, operation, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Generic task search on Elasticsearch database", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/search_es", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel searchElastic(Authentication auth, Pageable pageable, @RequestBody SingleElasticTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { - return super.searchElastic(auth, pageable, searchBody, operation, assembler, locale); + public PagedModel searchElastic(Pageable pageable, @RequestBody SingleElasticTaskSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, PagedResourcesAssembler assembler, Locale locale) { + return super.searchElastic(pageable, searchBody, operation, assembler, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Count tasks by provided criteria", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/count", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public CountResponse count(@RequestBody SingleElasticTaskSearchRequestAsList query, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Authentication auth, Locale locale) { - return super.count(query, operation, auth, locale); + public CountResponse count(@RequestBody SingleElasticTaskSearchRequestAsList query, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Locale locale) { + return super.count(query, operation, locale); } @Override + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Get all task data", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{id}/data", produces = MediaTypes.HAL_JSON_VALUE) + @GetMapping(value = {"/{id}/data", "/public/{id}/data"}, produces = MediaTypes.HAL_JSON_VALUE) public EntityModel getData(@PathVariable("id") String taskId, Locale locale) { return super.getData(taskId, locale); } - @PreAuthorize("@taskAuthorizationService.canCallSaveData(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallSaveData(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Set task data", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/{id}/data", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = {"/{id}/data", "/public/{id}/data"}, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel setData(Authentication auth, @PathVariable("id") String taskId, @RequestBody ObjectNode dataBody, Locale locale) { + public EntityModel setData(@PathVariable("id") String taskId, @RequestBody ObjectNode dataBody, Locale locale) { return super.setData(taskId, dataBody, locale); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallSaveFile(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Upload file into the task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @PostMapping(value = "/{id}/file", produces = MediaTypes.HAL_JSON_VALUE) + @PostMapping(value = {"/{id}/file", "/public/{id}/file"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel saveFile(Authentication auth, @PathVariable("id") String taskId, @RequestPart(value = "data") FileFieldRequest dataBody, @RequestPart(value = "file") MultipartFile multipartFile, Locale locale) { + public EntityModel saveFile(@PathVariable("id") String taskId, @RequestPart(value = "data") FileFieldRequest dataBody, @RequestPart(value = "file") MultipartFile multipartFile, Locale locale) { return super.saveFile(taskId, multipartFile, dataBody, locale); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Download task file field value", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{id}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @GetMapping(value = {"/{id}/file", "/public/{id}/file"}, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getFile(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { return super.getFile(taskId, fieldId); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallSaveFile(@userService.getLoggedUser(), #taskId)") @Operation(summary = "Remove file from the task", description = "Caller must be assigned to the task, or must be an ADMIN", security = {@SecurityRequirement(name = "BasicAuth")}) - @DeleteMapping(value = "/{id}/file", produces = MediaTypes.HAL_JSON_VALUE) + @DeleteMapping(value = {"/{id}/file", "/public/{id}/file"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel deleteFile(Authentication auth, @PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { + public EntityModel deleteFile(@PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { return super.deleteFile(requestBody.getParentTaskId(), requestBody.getFieldId()); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallSaveFile(@userService.getLoggedUser(), #taskId)") @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")}) - @PostMapping(value = "/{id}/files", produces = MediaTypes.HAL_JSON_VALUE) + @PostMapping(value = {"/{id}/files", "/public/{id}/files"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel saveFiles(Authentication auth, @PathVariable("id") String taskId, @RequestPart(value = "data") FileFieldRequest requestBody, @RequestPart(value = "files") MultipartFile[] multipartFiles) { + public EntityModel saveFiles(@PathVariable("id") String taskId, @RequestPart(value = "data") FileFieldRequest requestBody, @RequestPart(value = "files") MultipartFile[] multipartFiles) { return super.saveFiles(taskId, multipartFiles, requestBody); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Download one file from tasks file list field value", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{id}/file/named", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @GetMapping(value = {"/{id}/file/named", "/public/{id}/file/named"}, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getNamedFile(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId, @RequestParam("fileName") String fileName) throws FileNotFoundException { return super.getNamedFile(taskId, fieldId, fileName); } - @PreAuthorize("@taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #taskId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @taskAuthorizationService.canCallSaveFile(@userService.getLoggedUser(), #taskId)") @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")}) - @DeleteMapping(value = "/{id}/file/named", produces = MediaTypes.HAL_JSON_VALUE) + @DeleteMapping(value = {"/{id}/file/named", "/public/{id}/file/named"}, produces = MediaTypes.HAL_JSON_VALUE) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"), }) - public EntityModel deleteNamedFile(Authentication auth, @PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { + public EntityModel deleteNamedFile(@PathVariable("id") String taskId, @RequestBody FileFieldRequest requestBody) { return super.deleteNamedFile(requestBody.getParentTaskId(), requestBody.getFieldId(), requestBody.getFileName()); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')") @Operation(summary = "Download preview for file field value", security = {@SecurityRequirement(name = "BasicAuth")}) - @GetMapping(value = "/{id}/file_preview", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + @GetMapping(value = {"/{id}/file_preview", "/public/{id}/file_preview"}, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getFilePreview(@PathVariable("id") String taskId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { return super.getFilePreview(taskId, fieldId); } 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 bad76f64af2..5c23caec8e9 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,7 @@ package com.netgrif.application.engine.workflow.web; +import com.netgrif.application.engine.auth.service.UserService; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; 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; @@ -23,9 +25,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.io.InputStreamResource; @@ -43,11 +46,9 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.io.FileNotFoundException; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Locale; @@ -61,93 +62,92 @@ matchIfMissing = true ) @Tag(name = "Workflow") +@RequiredArgsConstructor public class WorkflowController { private static final Logger log = LoggerFactory.getLogger(WorkflowController.class.getName()); - @Autowired - private IWorkflowService workflowService; + private final IWorkflowService workflowService; - @Autowired - private ITaskService taskService; + private final ITaskService taskService; - @Autowired - private IElasticCaseService elasticCaseService; + private final IElasticCaseService elasticCaseService; - @Autowired - private IDataService dataService; + private final IDataService dataService; + private final UserService userService; - @PreAuthorize("@workflowAuthorizationService.canCallCreate(#auth.getPrincipal(), #body.netId)") + + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS') && @workflowAuthorizationService.canCallCreate(@userService.getLoggedUser(), #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) { - LoggedUser loggedUser = (LoggedUser) auth.getPrincipal(); + @PostMapping(value = {"/case", "/public/case"}, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) + public EntityModel createCase(@RequestBody CreateCaseBody body, Locale locale) { + LoggedUser loggedUser = ActorTransformer.toLoggedUser(userService.getLoggedUser()); try { CreateCaseEventOutcome outcome = workflowService.createCase(body.netId, body.title, body.color, loggedUser, locale); return EventOutcomeWithMessageResource.successMessage("Case with id " + outcome.getCase().getStringId() + " was created succesfully", LocalisedEventOutcomeFactory.from(outcome, locale)); - } catch (Exception e) { // TODO: 5. 2. 2017 change to custom exception + } catch (Exception e) { log.error("Creating case failed:", e); return EventOutcomeWithMessageResource.errorMessage("Creating case failed" + e.getMessage()); } } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get all cases of the system, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/all", produces = MediaTypes.HAL_JSON_VALUE) public PagedModel getAll(Pageable pageable, PagedResourcesAssembler assembler) { Page cases = workflowService.getAll(pageable); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) .getAll(pageable, assembler)).withRel("all"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); + return getCaseResources(pageable, assembler, cases, selfLink); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Generic case search with QueryDSL predicate, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search2", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel search2(@QuerydslPredicate(root = Case.class) Predicate predicate, Pageable pageable, PagedResourcesAssembler assembler) { Page cases = workflowService.search(predicate, pageable); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) .search2(predicate, pageable, assembler)).withRel("search2"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); + return getCaseResources(pageable, assembler, cases, selfLink); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Generic case search on Elasticsearch database, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel search(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Pageable pageable, PagedResourcesAssembler assembler, Authentication auth, Locale locale) { - LoggedUser user = (LoggedUser) auth.getPrincipal(); + public PagedModel search(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); Page cases = elasticCaseService.search(searchBody.getList(), user, pageable, locale, operation == MergeFilterOperation.AND); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .search(searchBody, operation, pageable, assembler, auth, locale)).withRel("search"); + .search(searchBody, operation, pageable, assembler, locale)).withRel("search"); PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); ResourceLinkAssembler.addLinks(resources, ElasticCase.class, selfLink.getRel().toString()); return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Generic case search on Mongo database, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/search_mongo", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaTypes.HAL_JSON_VALUE) - public PagedModel searchMongo(@RequestBody Map searchBody, Pageable pageable, Authentication auth, PagedResourcesAssembler assembler, Locale locale) { - Page cases = workflowService.search(searchBody, pageable, (LoggedUser) auth.getPrincipal(), locale); + public PagedModel searchMongo(@RequestBody Map searchBody, Pageable pageable, PagedResourcesAssembler assembler, Locale locale) { + Page cases = workflowService.search(searchBody, pageable, ActorTransformer.toLoggedUser(userService.getLoggedUser()), locale); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .searchMongo(searchBody, pageable, auth, assembler, locale)).withRel("search"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); + .searchMongo(searchBody, pageable, assembler, locale)).withRel("search"); + return getCaseResources(pageable, assembler, cases, selfLink); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get count of the cases", security = {@SecurityRequirement(name = "BasicAuth")}) @PostMapping(value = "/case/count", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public CountResponse count(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Authentication auth, Locale locale) { - long count = elasticCaseService.count(searchBody.getList(), (LoggedUser) auth.getPrincipal(), locale, operation == MergeFilterOperation.AND); + public CountResponse count(@RequestBody SingleCaseSearchRequestAsList searchBody, @RequestParam(defaultValue = "OR") MergeFilterOperation operation, Locale locale) { + long count = elasticCaseService.count(searchBody.getList(), ActorTransformer.toLoggedUser(userService.getLoggedUser()), locale, operation == MergeFilterOperation.AND); return CountResponse.caseCount(count); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get case by id", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/case/{id}", produces = MediaTypes.HAL_JSON_VALUE) public CaseResource getOne(@PathVariable("id") String caseId) { @@ -157,15 +157,14 @@ public CaseResource getOne(@PathVariable("id") String caseId) { return new CaseResource(aCase); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Get all cases by user that created them, paginated", security = {@SecurityRequirement(name = "BasicAuth")}) @RequestMapping(value = "/case/author/{id}", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaTypes.HAL_JSON_VALUE) public PagedModel findAllByAuthor(@PathVariable("id") String authorId, @RequestBody String petriNet, PagedResourcesAssembler assembler, Pageable pageable) { Page cases = workflowService.findAllByAuthor(authorId, petriNet, pageable); Link selfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) .findAllByAuthor(authorId, petriNet, assembler, pageable)).withRel("author"); - PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); - ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); - return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); + return getCaseResources(pageable, assembler, cases, selfLink); } @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") @@ -179,51 +178,32 @@ public PagedModel findAllByAuthor(@PathVariable("id") String autho }) public MessageResource reloadTasks(@PathVariable("id") String caseId) { try { - caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8.name()); + caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8); Case aCase = workflowService.findOne(caseId); taskService.reloadTasks(aCase); - return MessageResource.successMessage("Task reloaded in case [" + caseId + "]"); } catch (Exception e) { - log.error("Reloading tasks of case [" + caseId + "] failed:", e); + log.error("Reloading tasks of case [{}] failed:", caseId, e); return MessageResource.errorMessage("Reloading tasks in case " + caseId + " has failed!"); } } -// @Deprecated -// @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") -// @Operation(summary = "Get all case data", security = {@SecurityRequirement(name = "BasicAuth")}) -// @GetMapping(value = "/case/{id}/data", produces = MediaTypes.HAL_JSON_VALUE) -// public DataFieldsResource getAllCaseData(@PathVariable("id") String caseId, Locale locale) { -// try { -// caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8.name()); -// return new DataFieldsResource(workflowService.getData(caseId), locale); -// } catch (UnsupportedEncodingException e) { -// log.error("Getting all case data of [" + caseId + "] failed:", e); -// return new DataFieldsResource(new ArrayList<>(), locale); -// } -// } - - @PreAuthorize("@workflowAuthorizationService.canCallDelete(#auth.getPrincipal(), #caseId)") + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN') && @workflowAuthorizationService.canCallDelete(@userService.getLoggedUser(), #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) { - try { - caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8.name()); - DeleteCaseEventOutcome outcome; - if (deleteSubtree) { - outcome = workflowService.deleteSubtreeRootedAt(caseId); - } else { - outcome = workflowService.deleteCase(caseId); - } - return EventOutcomeWithMessageResource.successMessage("Case " + caseId + " was deleted", - LocalisedEventOutcomeFactory.from(outcome, LocaleContextHolder.getLocale())); - } catch (UnsupportedEncodingException e) { - log.error("Deleting case [" + caseId + "] failed:", e); - return EventOutcomeWithMessageResource.errorMessage("Deleting case " + caseId + " has failed!"); + public EntityModel deleteCase(@PathVariable("id") String caseId, @RequestParam(defaultValue = "false") boolean deleteSubtree) { + caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8); + DeleteCaseEventOutcome outcome; + if (deleteSubtree) { + outcome = workflowService.deleteSubtreeRootedAt(caseId); + } else { + outcome = workflowService.deleteCase(caseId); } + return EventOutcomeWithMessageResource.successMessage("Case " + caseId + " was deleted", + LocalisedEventOutcomeFactory.from(outcome, LocaleContextHolder.getLocale())); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Download case file field value", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/case/{id}/file", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getFile(@PathVariable("id") String caseId, @RequestParam("fieldId") String fieldId) throws FileNotFoundException { @@ -242,6 +222,7 @@ public ResponseEntity getFile(@PathVariable("id") String caseId, @Requ .body(new InputStreamResource(fileFieldInputStream.getInputStream())); } + @PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')") @Operation(summary = "Download one file from cases file list field value", security = {@SecurityRequirement(name = "BasicAuth")}) @GetMapping(value = "/case/{id}/file/named", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public ResponseEntity getFileByName(@PathVariable("id") String caseId, @RequestParam("fieldId") String fieldId, @RequestParam("fileName") String name) throws FileNotFoundException { @@ -259,4 +240,11 @@ public ResponseEntity getFileByName(@PathVariable("id") String caseId, .headers(headers) .body(new InputStreamResource(fileFieldInputStream.getInputStream())); } + + @NotNull + private PagedModel getCaseResources(Pageable pageable, PagedResourcesAssembler assembler, Page cases, Link selfLink) { + PagedModel resources = assembler.toModel(cases, new CaseResourceAssembler(), selfLink); + ResourceLinkAssembler.addLinks(resources, Case.class, selfLink.getRel().toString()); + return PagedModel.of(cases.stream().map(CaseResource::new).toList(), new PagedModel.PageMetadata(pageable.getPageSize(), pageable.getPageNumber(), cases.getTotalElements())); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java index 65f48794c15..62abfe58c5f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java @@ -5,12 +5,14 @@ import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.PetriNet; import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.TaskSearchCaseRequest; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; import java.util.Map; +@Builder(builderMethodName = "from") @NoArgsConstructor @AllArgsConstructor public class TaskSearchRequest implements Serializable { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/taskSearch/TaskSearchCaseRequest.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/taskSearch/TaskSearchCaseRequest.java index c031c48208b..e1e9107f56e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/taskSearch/TaskSearchCaseRequest.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/taskSearch/TaskSearchCaseRequest.java @@ -1,10 +1,12 @@ package com.netgrif.application.engine.workflow.web.requestbodies.taskSearch; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.NoArgsConstructor; import java.io.Serializable; +@Builder @NoArgsConstructor @AllArgsConstructor public class TaskSearchCaseRequest implements Serializable { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/CaseResource.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/CaseResource.java index dcd0a65f416..8502ddff959 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/CaseResource.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/CaseResource.java @@ -19,6 +19,6 @@ public CaseResource(Case content) { private void buildLinks() { add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder - .methodOn(WorkflowController.class).createCase(new CreateCaseBody(), null, LocaleContextHolder.getLocale())).withRel("create")); + .methodOn(WorkflowController.class).createCase(new CreateCaseBody(), LocaleContextHolder.getLocale())).withRel("create")); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java index 36b0ce5d636..680ff1f2519 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java @@ -26,13 +26,13 @@ private void buildLinks() { add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) .getOne(task.getStringId(), null)).withSelfRel()); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .assign((Authentication) null, task.getStringId(), null)).withRel("assign")); + .assign(task.getStringId(), null)).withRel("assign")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .delegate((Authentication) null, task.getStringId(), null, null)).withRel("delegate")); + .delegate(task.getStringId(), null, null)).withRel("delegate")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .finish((Authentication) null, task.getStringId(), null)).withRel("finish")); + .finish(task.getStringId(), null)).withRel("finish")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .cancel((Authentication) null, task.getStringId(), null)).withRel("cancel")); + .cancel(task.getStringId(), null)).withRel("cancel")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) .getData(task.getStringId(), null)).withRel("data")); add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/ResourceLinkAssembler.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/ResourceLinkAssembler.java index 401b8306bbb..83e3da8fa96 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/ResourceLinkAssembler.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/ResourceLinkAssembler.java @@ -21,25 +21,25 @@ public static void addLinks(PagedModel pagedResources, Class type, String selfRe private static void addTasksLinks(PagedModel pagedResources, String selfRel) { if (!selfRel.equalsIgnoreCase("all")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getAll(null, null, null, null)).withRel("all")); + .getAll(null, null, null)).withRel("all")); if (!selfRel.equalsIgnoreCase("case")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) .getAllByCases(null, null, null, null)).withRel("case")); if (!selfRel.equalsIgnoreCase("my")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getMy(null, null, null, null)).withRel("my")); + .getMy(null, null, null)).withRel("my")); if (!selfRel.equalsIgnoreCase("finished")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .getMyFinished(null, null, null, null)).withRel("finished")); + .getMyFinished(null, null, null)).withRel("finished")); if (!selfRel.equalsIgnoreCase("search")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .search(null, null, null, null, null, null)).withRel("search")); + .search(null, null, null, null, null)).withRel("search")); if (!selfRel.equalsIgnoreCase("search_es")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .searchElastic(null, null, null, null, null, null)).withRel("search_es")); + .searchElastic(null, null, null, null, null)).withRel("search_es")); if (!selfRel.equalsIgnoreCase("count")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(TaskController.class) - .count(null, null, null, null)).withRel("count")); + .count(null, null, null)).withRel("count")); } private static void addCasesLinks(PagedModel pagedResources, String selfRel) { @@ -48,10 +48,10 @@ private static void addCasesLinks(PagedModel pagedResources, String selfRel) { .getAll(null, null)).withRel("all")); if (!selfRel.equalsIgnoreCase("search")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .search(null, MergeFilterOperation.OR, null, null, null, null)).withRel("search")); + .search(null, MergeFilterOperation.OR, null, null, null)).withRel("search")); if (!selfRel.equalsIgnoreCase("count")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) - .count(null, MergeFilterOperation.OR, null, null)).withRel("count")); + .count(null, MergeFilterOperation.OR, null)).withRel("count")); if (!selfRel.equalsIgnoreCase("author")) pagedResources.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(WorkflowController.class) .findAllByAuthor(null, "", null, null)).withRel("author")); diff --git a/application-engine/src/main/resources/application-dev.yaml b/application-engine/src/main/resources/application-dev.yaml index 13937c1cc4c..65456e12f96 100644 --- a/application-engine/src/main/resources/application-dev.yaml +++ b/application-engine/src/main/resources/application-dev.yaml @@ -28,7 +28,7 @@ netgrif: swagger: enabled: true security: - server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs,/v3/api-docs/**,/swagger-ui.html,/swagger-ui/**,/api/public/**,/manage/** + server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs,/v3/api-docs/**,/swagger-ui.html,/swagger-ui/**,/api/petrinet/public/**,/api/workflow/public/**,/api/task/public/**,/manage/**,/api/users/me,/api/users/preferences management: health: ldap: diff --git a/application-engine/src/main/resources/application.yaml b/application-engine/src/main/resources/application.yaml index d95cf455887..7211e116112 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -84,9 +84,8 @@ netgrif: jwt: expiration: 900000 algorithm: RSA - private-key: file:src/main/resources/certificates/private.der - server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/api/public/**,/v3/api-docs/public,/manage/** - anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/manage/** + private-key: classpath:certificates/private.der + server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs/public,/manage/**,/api/users/me,/api/users/preferences,/api/petrinet/public/**,/api/workflow/public/**,/api/task/public/** providers: NetgrifBasicAuthenticationProvider swagger: enabled: false 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 f6921558abe..f87c730a27d 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 @@ -97,9 +97,9 @@ class TestHelper { petriNetService.evictAllCaches() defaultRoleRunner.run() + anonymousRoleRunner.run() elasticsearchRunner.run() defaultRealmRunner.run() - anonymousRoleRunner.run() systemUserRunner.run() groupRunner.run() filterRunner.run() diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy index d8e270522ab..2bb207d7842 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy @@ -94,7 +94,7 @@ class PetriNetControllerTest { def auths = importHelper.createAuthorities(["user": Authority.user, "admin": Authority.admin]) - def simpleUser = importHelper.createUser(new User(firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), + def simpleUser = importHelper.createUser(new User(username: "simple", firstName: "Role", lastName: "User", email: USER_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("user")] as Authority[], // [] as Group[], [] as ProcessRole[]) @@ -102,7 +102,7 @@ class PetriNetControllerTest { userAuth = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(simpleUser), "password", [auths.get("user")] as List) userAuth.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())) - def adminUser = importHelper.createUser(new User(firstName: "Admin", lastName: "User", email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), + def adminUser = importHelper.createUser(new User(username: "admin", firstName: "Admin", lastName: "User", email: ADMIN_EMAIL, password: "password", state: UserState.ACTIVE), [auths.get("admin")] as Authority[], // [] as Group[], [] as ProcessRole[]) diff --git a/application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy b/application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy index 8fd652ee551..85dd207f6e9 100644 --- a/application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy +++ b/application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy @@ -3,9 +3,11 @@ package com.netgrif.application.engine.workflow import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService +import com.netgrif.application.engine.objects.auth.domain.AbstractUser import com.netgrif.application.engine.objects.auth.domain.ActorTransformer import com.netgrif.application.engine.objects.auth.domain.Authority import com.netgrif.application.engine.objects.auth.domain.User @@ -19,6 +21,7 @@ import com.netgrif.application.engine.objects.workflow.domain.DataField import com.netgrif.application.engine.objects.workflow.domain.Task import com.netgrif.application.engine.petrinet.service.ProcessRoleService import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService +import com.netgrif.application.engine.security.service.SecurityContextService import com.netgrif.application.engine.startup.ImportHelper import com.netgrif.application.engine.startup.runner.SuperCreatorRunner import com.netgrif.application.engine.utils.FullPageRequest @@ -37,6 +40,8 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.domain.Page import org.springframework.mock.web.MockMultipartFile +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContextHolder import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.junit.jupiter.SpringExtension @@ -102,15 +107,21 @@ class TaskControllerTest { @BeforeEach void init() { testHelper.truncateDbs() - userService.saveUser(new User( + def user = new User( firstName: "Dummy", lastName: "Netgrif", username: DUMMY_USER_MAIL, email: DUMMY_USER_MAIL, password: "superAdminPassword", state: UserState.ACTIVE, - authoritySet: [authorityService.getOrCreate(Authority.user)] as Set, - processRoles: [] as Set), null) + authoritySet: [authorityService.getOrCreate(Authority.user), authorityService.getOrCreate(Authority.admin)] as Set, + processRoles: [] as Set) + + user = userService.saveUser(user) + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(ActorTransformer.toLoggedUser(user), ActorTransformer.toLoggedUser(user).getPassword(), ActorTransformer.toLoggedUser(user).getAuthoritySet() as Set); + SecurityContextHolder.getContext().setAuthentication(token) + importNets() } diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java index 2b844d7d0e4..fc171e38c66 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java @@ -308,4 +308,15 @@ public void removeGroupId(String groupId) { public boolean isAdmin() { return this.authoritySet.stream().anyMatch(it -> it.getName().equals(Authority.admin)); } + + + /** + * Checks if the actor has anonymous authority. + * This indicates whether the actor is granted permissions for anonymous access. + * + * @return true if the actor has anonymous authority, false otherwise + */ + public boolean isAnonymous() { + return authoritySet.stream().anyMatch(it -> it.getName().equals(Authority.anonymous)); + } } \ No newline at end of file diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java index d1d69778a64..ca9018c970b 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java @@ -1,5 +1,7 @@ package com.netgrif.application.engine.objects.auth.domain; +import lombok.Setter; + import java.time.Duration; /** @@ -12,11 +14,34 @@ public class ActorTransformer { /** * Factory for creating new LoggedUser instances. * Default implementation throws IllegalStateException if no factory is configured. + * The lambda expression provides a no-op factory that throws an exception to indicate + * that no valid LoggedUserFactory has been set. + *

+ * -- SETTER -- + * Sets the factory used for creating LoggedUser instances. + * + * @param f the LoggedUserFactory implementation to be used */ - private static LoggedUserFactory factory = () -> { + @Setter + private static LoggedUserFactory loggedUserFactory = () -> { throw new IllegalStateException("No LoggedUserFactory configured"); }; + + /** + * Factory for creating new AbstractUser instances. + * Default implementation throws IllegalStateException if no factory is configured. + * The lambda expression provides a no-op factory that throws an exception to indicate + * that no valid UserFactory has been set. + *

+ * -- SETTER -- + * Sets the factory used for creating AbstractUser instances. + **/ + @Setter + private static UserFactory userFactory = () -> { + throw new IllegalStateException("No UserFactory configured"); + }; + /** * Functional interface defining a factory method for creating new LoggedUser instances. */ @@ -29,12 +54,27 @@ public interface LoggedUserFactory { LoggedUser create(); } - /** - * Sets the factory used for creating LoggedUser instances. - * @param f the LoggedUserFactory implementation to be used - */ - public static void setLoggedUserFactory(LoggedUserFactory f) { - factory = f; + @FunctionalInterface + public interface UserFactory { + /** + * Creates a new AbstractUser instance. + * @return newly created AbstractUser instance + */ + AbstractUser create(); + + + /** + * Creates a new AbstractUser instance based on the provided LoggedUser instance. + * By default, this method throws an IllegalStateException, and must be explicitly + * implemented by the concrete UserFactory implementation. + * + * @param loggedUser the LoggedUser containing user details + * @return newly created AbstractUser instance representing the same user + * @throws IllegalStateException if the method is not implemented + */ + default AbstractUser create(LoggedUser loggedUser) { + throw new IllegalStateException("Method is not implemented"); + } } /** @@ -43,7 +83,7 @@ public static void setLoggedUserFactory(LoggedUserFactory f) { * @return new LoggedUser instance with copied user data */ public static LoggedUser toLoggedUser(AbstractUser user) { - LoggedUser loggedUser = factory.create(); + LoggedUser loggedUser = loggedUserFactory.create(); loggedUser.setId(user.getStringId()); loggedUser.setRealmId(user.getRealmId()); loggedUser.setUsername(user.getUsername()); @@ -58,6 +98,28 @@ public static LoggedUser toLoggedUser(AbstractUser user) { return loggedUser; } + /** + * Converts a LoggedUser into an AbstractUser by copying all relevant user information. + * + * @param loggedUser the LoggedUser to transform + * @return a new AbstractUser instance with copied user data + */ + public static AbstractUser toUser(LoggedUser loggedUser) { + AbstractUser user = userFactory.create(loggedUser); + user.setId(loggedUser.getStringId()); + user.setRealmId(loggedUser.getRealmId()); + user.setUsername(loggedUser.getUsername()); + user.setEmail(loggedUser.getEmail()); + user.setFirstName(loggedUser.getFirstName()); + user.setMiddleName(loggedUser.getMiddleName()); + user.setLastName(loggedUser.getLastName()); + user.setAuthoritySet(loggedUser.getAuthoritySet()); + user.setProcessRoles(loggedUser.getProcessRoles()); + user.setAttributes(loggedUser.getAttributes()); + user.setGroupIds(loggedUser.getGroupIds()); + return user; + } + /** * Transforms an AbstractUser into a LoggedUser with additional session-related information. * @param user the AbstractUser to transform 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..c60f9f9a3ab 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 @@ -42,7 +42,7 @@ public abstract class Authority implements Serializable { /** * Constant representing the anonymous user role. */ - public static final String anonymous = "ANONYMOUS_USER"; + public static final String anonymous = "ANONYMOUS"; /** * MongoDB ObjectId of the authority. diff --git a/nae-spring-core-adapter/pom.xml b/nae-spring-core-adapter/pom.xml index 283ab6a5ac5..a17a561b5f5 100644 --- a/nae-spring-core-adapter/pom.xml +++ b/nae-spring-core-adapter/pom.xml @@ -89,6 +89,22 @@ ${spring.boot.version} provided + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-validation + provided + + + org.jetbrains + annotations + 23.0.0 + compile + org.springframework.boot spring-boot-starter-data-mongodb diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java index c641917e0c9..7682d7bbb2a 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java @@ -29,6 +29,22 @@ public class AnonymousUser extends AbstractUser { */ private final Map> attributes = new HashMap<>(); + public AnonymousUser(AnonymousUserRef ref) { + this.id = new ObjectId(ref.getId()); + this.realmId = ref.getRealmId(); + this.username = "anonymous@" + this.realmId; + this.firstName = ref.getDisplayName(); + this.lastName = ""; + + this.authoritySet = new HashSet<>(); + if (ref.getAuthorities() != null && !ref.getAuthorities().isEmpty()) { + this.authoritySet.addAll(ref.getAuthorities()); + } + + this.processRoles = ref.getProcessRoles() != null ? new HashSet<>(ref.getProcessRoles()) : new HashSet<>(); + this.groupIds = ref.getGroupIds() != null ? new HashSet<>(ref.getGroupIds()) : new HashSet<>(); + } + /** * Constructs a new anonymous user with specified reference and authority. * @@ -36,21 +52,13 @@ public class AnonymousUser extends AbstractUser { * @param anonymousAuthority the authority to be assigned if no specific authorities are provided */ public AnonymousUser(AnonymousUserRef ref, Authority anonymousAuthority) { - this.id = new ObjectId(); - this.realmId = ref.getRealmId(); - this.username = "anonymous@" + this.realmId; - this.firstName = ref.getDisplayName(); - this.lastName = ""; - + this(ref); this.authoritySet = new HashSet<>(); if (ref.getAuthorities() != null && !ref.getAuthorities().isEmpty()) { this.authoritySet.addAll(ref.getAuthorities()); } else { this.authoritySet.add(anonymousAuthority); } - - this.processRoles = ref.getProcessRoles() != null ? new HashSet<>(ref.getProcessRoles()) : new HashSet<>(); - this.groupIds = ref.getGroupIds() != null ? new HashSet<>(ref.getGroupIds()) : new HashSet<>(); } /** diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java index 663e913e7d5..f40d7ce09aa 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java @@ -6,11 +6,14 @@ import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import lombok.Data; import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import javax.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import java.time.Duration; import java.time.LocalDateTime; import java.util.HashSet; @@ -23,7 +26,13 @@ */ @Data @Document(collection = "anonym_user") -public class AnonymousUserRef { +public class AnonymousUserRef implements Serializable { + + /** + * Serial version UID for ensuring compatibility during deserialization. + */ + @Serial + private static final long serialVersionUID = 1239812903890129012L; /** * Unique identifier for the anonymous user. @@ -64,17 +73,30 @@ public class AnonymousUserRef { * Set of process roles assigned to this anonymous user. * @see ProcessRole */ + /** + * Set of process roles associated with this anonymous user. + * These roles determine task permissions and access levels. + * + * @see ProcessRole + */ private Set processRoles = new HashSet<>(); /** * Set of group identifiers this anonymous user belongs to. */ + /** + * Set of unique identifiers representing the groups this user belongs to. + */ private Set groupIds = new HashSet<>(); /** * Duration after which the anonymous user session times out. * Default value is 30 minutes. */ + /** + * Duration representing the session timeout for this anonymous user. + * This field is transient and not persisted in the database. + */ private transient Duration sessionTimeout = Duration.ofMinutes(30); /** @@ -83,6 +105,11 @@ public class AnonymousUserRef { * @see Group */ @BsonIgnore + /** + * Set of groups this anonymous user is associated with. + * This field is ignored during database persistence. + * @see Group + */ private Set groups = new HashSet<>(); /** @@ -101,4 +128,10 @@ public AnonymousUserRef(String realmId) { this.realmId = realmId; this.createdAt = LocalDateTime.now(); } + + public AnonymousUserRef(String realmId, Set authorities, Set processRoles) { + this(realmId); + this.authorities = authorities; + this.processRoles = processRoles; + } } \ No newline at end of file diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifHttpRequestTransformFilter.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifHttpRequestTransformFilter.java new file mode 100644 index 00000000000..9653a0e250e --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifHttpRequestTransformFilter.java @@ -0,0 +1,39 @@ +package com.netgrif.application.engine.adapter.spring.configuration.filters; + + +import com.netgrif.application.engine.adapter.spring.configuration.filters.requests.NetgrifHttpServletRequest; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * This filter wraps the {@link HttpServletRequest} object into {@link NetgrifHttpServletRequest} object + * */ +@Slf4j +@Component +public class NetgrifHttpRequestTransformFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + HttpServletRequest transformedRequest = new NetgrifHttpServletRequest(request); + log.debug("Http request was transformed to Netgrif http request wrapper"); + filterChain.doFilter(transformedRequest, response); + } + + @Bean + public FilterRegistrationBean netgrifHttpRequestTransformFilterFilterRegistrationBean(NetgrifHttpRequestTransformFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); + registration.setEnabled(false); + return registration; + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java new file mode 100644 index 00000000000..6d599f641eb --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java @@ -0,0 +1,56 @@ +package com.netgrif.application.engine.adapter.spring.configuration.filters; + +import com.netgrif.application.engine.adapter.spring.configuration.filters.requests.NetgrifHttpServletRequest; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Set; + +/** + * Proxy filter class to cast request object to {@link NetgrifHttpServletRequest} for further use + * */ +@Setter +@Slf4j +public abstract class NetgrifOncePerRequestFilter extends OncePerRequestFilter { + + /** + * If initialized, the filter will be applied only if the request path is matched. Otherwise, it will just continue to the next filter. + * */ + protected Set requestMatcher; + + protected abstract void doFilterInternal(NetgrifHttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException; + + @Override + protected void doFilterInternal(@NotNull HttpServletRequest request, + @NotNull HttpServletResponse response, + @NotNull FilterChain filterChain) throws ServletException, IOException { + if (requestNotMatches(request)) { + log.trace("Request did not match the required URIs: {}", this.requestMatcher); + filterChain.doFilter(request, response); + return; + } + + NetgrifHttpServletRequest typedRequest = (NetgrifHttpServletRequest) request; + doFilterInternal(typedRequest, response, filterChain); + } + + protected boolean requestNotMatches(@NotNull HttpServletRequest request) { + if (requestMatcher == null) { + return false; + } + boolean result = false; + for (RequestMatcher matcher : requestMatcher) { + result |= matcher.matches(request); + } + return !result; + } +} diff --git a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java new file mode 100644 index 00000000000..2f14d248c93 --- /dev/null +++ b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java @@ -0,0 +1,29 @@ +package com.netgrif.application.engine.adapter.spring.configuration.filters.requests; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.util.HashMap; +import java.util.Map; + +public class NetgrifHttpServletRequest extends HttpServletRequestWrapper { + + private final Map additionalParams; + + public NetgrifHttpServletRequest(HttpServletRequest request) { + this(request, new HashMap<>()); + } + + public NetgrifHttpServletRequest(HttpServletRequest request, Map additionalParams) { + super(request); + this.additionalParams = additionalParams; + } + + public Object getAdditionalParameter(String name) { + return this.additionalParams.get(name); + } + + public void addAdditionalParameter(String name, Object value) { + this.additionalParams.put(name, value); + } +} diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/AuthBeansConfiguration.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/AuthBeansConfiguration.java index df6cd8901a4..b6ffb508300 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/AuthBeansConfiguration.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/AuthBeansConfiguration.java @@ -43,4 +43,10 @@ public PreferencesService preferencesService() { public UserFactory userFactory() { return new UserFactoryImpl(); } + + @Bean + @ConditionalOnMissingBean(AnonymousUserRefService.class) + public AnonymousUserRefService anonymousUserRefService() { + return new AnonymousUserRefServiceImpl(); + } } diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java index f74f7df7ce7..06c773719b4 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java @@ -1,22 +1,30 @@ package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUserRef; +import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.auth.repository.AnonymousUserRefRepository; +import com.netgrif.application.engine.objects.auth.domain.Authority; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Optional; +import java.util.Set; -@Service public class AnonymousUserRefServiceImpl implements AnonymousUserRefService { @Autowired private AnonymousUserRefRepository repository; + @Autowired + private AuthorityService authorityService; + + @Autowired + private ProcessRoleService processRoleService; + @Override public AnonymousUserRef getOrCreateRef(String realmId) { return repository.findByRealmId(realmId) - .orElseGet(() -> repository.save(new AnonymousUserRef(realmId))); + .orElseGet(() -> repository.save(new AnonymousUserRef(realmId, Set.of(authorityService.getOrCreate(Authority.anonymous)), Set.of(processRoleService.getAnonymousRole())))); } @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java similarity index 100% rename from application-engine/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java rename to nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java new file mode 100644 index 00000000000..22c3d10dbf0 --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java @@ -0,0 +1,39 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUser; +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUserRef; +import com.netgrif.application.engine.objects.auth.domain.AbstractUser; +import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; +import com.netgrif.application.engine.objects.auth.domain.LoggedUser; +import com.netgrif.application.engine.objects.auth.domain.User; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class DefaultUserFactory implements ActorTransformer.UserFactory { + + private final AnonymousUserRefService anonymousUserRefService; + + @Override + public AbstractUser create() { + return new User(); + } + + @Override + public AbstractUser create(LoggedUser loggedUser) { + if (!loggedUser.isAnonymous() || loggedUser.getRealmId() == null) { + return create(); + } + Optional refOpt = anonymousUserRefService.getRef(loggedUser.getRealmId()); + if (refOpt.isEmpty()) { + throw new IllegalStateException("Anonymous user reference not found for realm [%s]".formatted(loggedUser.getRealmId())); + } + return new AnonymousUser(refOpt.get()); + } +} + diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java index b38c2c00d90..160897a4cba 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java @@ -67,6 +67,7 @@ public Realm createRealm(Realm createRequest) { com.netgrif.application.engine.adapter.spring.auth.domain.Realm realm = new com.netgrif.application.engine.adapter.spring.auth.domain.Realm(createRequest.getName()); realm.setDescription(createRequest.getDescription()); realm.setAdminRealm(createRequest.isAdminRealm()); + realm.setPublicAccess(createRequest.isPublicAccess()); if (createRequest.isDefaultRealm() && getDefaultRealm().isEmpty()) { realm.setDefaultRealm(true); 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 7444183b29b..691369e0625 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 @@ -1,5 +1,7 @@ package com.netgrif.application.engine.auth.service; +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUser; +import com.netgrif.application.engine.adapter.spring.auth.domain.AnonymousUserRef; import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; import com.netgrif.application.engine.adapter.spring.utils.PaginationProperties; import com.netgrif.application.engine.adapter.spring.workflow.service.FilterImportExportService; @@ -59,6 +61,8 @@ public class UserServiceImpl implements UserService { private RealmService realmService; + private AnonymousUserRefService anonymousUserRefService; + @Getter private PaginationProperties paginationProperties; @@ -120,6 +124,11 @@ public void setRealmService(RealmService realmService) { this.realmService = realmService; } + @Autowired + public void setAnonymousUserRefService(AnonymousUserRefService anonymousUserRefService) { + this.anonymousUserRefService = anonymousUserRefService; + } + @Override public AbstractUser saveUser(AbstractUser user, String realmId) { user.setRealmId(realmId); @@ -340,7 +349,11 @@ public AbstractUser findById(String id, String realmId) { log.debug("Finding user by ID [{}]", id); String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId); Optional userOpt = userRepository.findById(new ObjectId(id), mongoTemplate, collectionName); - return userOpt.orElse(null); + if (userOpt.isPresent()) { + return userOpt.get(); + } + Optional anonymousUserRefOptional = anonymousUserRefService.getRef(realmId); + return anonymousUserRefOptional.map(anonymousUserRef -> new AnonymousUser(anonymousUserRef, authorityService.getOrCreate(Authority.anonymous))).orElse(null); } @Override @@ -476,8 +489,13 @@ public AbstractUser getLoggedOrSystem() { @Override public AbstractUser getLoggedUser() { LoggedUser loggedUser = getLoggedUserFromContext(); - Optional userOptional = findUserByUsername(loggedUser.getUsername(), loggedUser.getRealmId()); - AbstractUser user = userOptional.orElseThrow(() -> new IllegalArgumentException("User with username [%s] in realm [%s] is not present in the system.".formatted(loggedUser.getUsername(), loggedUser.getRealmId()))); + AbstractUser user; + if (loggedUser.isAnonymous()) { + user = ActorTransformer.toUser(loggedUser); + } else { + Optional userOptional = findUserByUsername(loggedUser.getUsername(), loggedUser.getRealmId()); + user = userOptional.orElseThrow(() -> new IllegalArgumentException("User with username [%s] in realm [%s] is not present in the system.".formatted(loggedUser.getUsername(), loggedUser.getRealmId()))); + } // TODO: impersonation // if (loggedUser.isImpersonating()) { // IUser impersonated = transformToUser((LoggedUserImpl) loggedUser.getImpersonated());