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 6fef48bcc5b..f46d9059b4f 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.security.service.ISecurityContextService; import com.netgrif.application.engine.workflow.web.responsebodies.MessageResource; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; @@ -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 1fd5f887d09..fd658746fa6 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.*; @@ -113,10 +111,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(); @@ -296,11 +294,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); @@ -339,4 +340,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/NaeSecurityConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java index 245b7eec345..8c58da3a4b2 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,14 +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.configuration.security.*; 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.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.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.beans.factory.annotation.Autowired; @@ -17,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; @@ -41,7 +40,6 @@ @Slf4j -@Controller @Configuration @EnableWebSecurity @Order(SecurityFilterProperties.DEFAULT_FILTER_ORDER) @@ -53,21 +51,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; @@ -78,22 +64,26 @@ public class NaeSecurityConfiguration extends AbstractSecurityConfiguration { private List authenticationProviders; @Autowired - private AuthenticationManagerBuilder authenticationManagerBuilder; + private PublicAuthenticationFilter publicAuthenticationFilter; @Autowired private WebEndpointProperties webEndpointProperties; - private static final String ANONYMOUS_USER = "anonymousUser"; + @Autowired + private NetgrifHttpRequestTransformFilter netgrifHttpRequestTransformFilter; + + @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 +104,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) .addFilterAfter(actuatorRequestFilter(), BasicAuthenticationFilter.class) .authorizeHttpRequests(requestMatcherRegistry -> requestMatcherRegistry @@ -146,12 +138,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 @@ -171,28 +163,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 3d06af48fd3..94c831e4077 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 @@ -69,6 +69,9 @@ public class SecurityConfigurationProperties { */ private String[] anonymousExceptions; + + private String anonymousAuthenticationKey = "anonymousUser"; + /** * Headers settings */ @@ -431,6 +434,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 aecb717ce37..fd7563cfbcc 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.servlet.util.matcher.PathPatternRequestMatcher; +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(PathPatternRequestMatcher::pathPattern).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 com.netgrif.application.engine.adapter.spring.auth.domain.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/IJwtService.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/IJwtService.java deleted file mode 100644 index c78639c07f6..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/IJwtService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netgrif.application.engine.configuration.security.jwt; - -import com.netgrif.application.engine.objects.auth.domain.Authority; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Map; - -public interface IJwtService { - String tokenFrom(Map header, String subject, Map claims); - - Boolean isTokenExpired(String token); - - LoggedUser getLoggedUser(String token, String authority); -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtProperties.java deleted file mode 100644 index 495c2e011d3..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtProperties.java +++ /dev/null @@ -1,26 +0,0 @@ -//package com.netgrif.application.engine.configuration.security.jwt; -// -//import lombok.Data; -//import org.springframework.boot.context.properties.ConfigurationProperties; -//import org.springframework.core.io.Resource; -// -//@Data -//@ConfigurationProperties(prefix = "nae.security.jwt") -//public class JwtProperties { -// -// /** -// * Defines the validity duration for a token in milliseconds, then expiration dateTime is -// * counted using "System.currentTimeMillis() + this.expiration" -// */ -// private long expiration = 900000; -// -// /** -// * Defines path to a file that contains generated private key with certificate -// */ -// private Resource privateKey; -// -// /** -// * Defines which algorithm is used when generating JWT token -// */ -// private String algorithm = "RSA"; -//} 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 deleted file mode 100644 index 693a719e88a..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.netgrif.application.engine.configuration.security.jwt; - -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; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import com.netgrif.application.engine.auth.service.AuthorityService; -import com.netgrif.application.engine.adapter.spring.petrinet.service.ProcessRoleService; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.io.IOException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.Function; - -@Slf4j -@Service -@RequiredArgsConstructor -public class JwtService implements IJwtService { - private byte[] secret; - private final SecurityConfigurationProperties.JwtProperties properties; - private final ProcessRoleService roleService; - private final AuthorityService authorityService; - - @PostConstruct - private void resolveSecret() { - try { - PrivateKeyReader reader = new PrivateKeyReader(properties.getAlgorithm()); - secret = reader.get(properties.getPrivateKey().getFile().getPath()).getEncoded(); - } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { - log.error("Error while resolving secret key: " + e.getMessage(), e); - } - } - - @Override - public String tokenFrom(Map header, String subject, Map claims) { - log.info("Generating new JWT token."); - return Jwts - .builder() - .setHeader(header) - .setSubject(subject) - .setClaims(claims) - .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + properties.getExpiration())) - .signWith(getSignInKey(), SignatureAlgorithm.HS256) - .compact(); - } - - @Override - public Boolean isTokenExpired(String token) { - try { - return extractExpiration(token).before(new Date()); - } catch (ExpiredJwtException e) { - return false; - } - } - - @Override - 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.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; - } - - private Date extractExpiration(String token) throws ExpiredJwtException { - return extractClaim(token, Claims::getExpiration); - } - - private T extractClaim(String token, Function claimsResolver) throws ExpiredJwtException { - final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); - } - - private Claims extractAllClaims(String token) throws ExpiredJwtException { - return Jwts - .parserBuilder() - .setSigningKey(getSignInKey()) - .build() - .parseClaimsJws(token) - .getBody(); - } - - private Key getSignInKey() { - return Keys.hmacShaKeyFor(secret); - } -} 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 deleted file mode 100644 index e864e7e61f0..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.netgrif.application.engine.configuration.security.jwt; - -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; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; - -public class PrivateKeyReader { - - private final KeyFactory keyFactory; - - 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)); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); - return keyFactory.generatePrivate(spec); - } -} \ No newline at end of file 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 55ed5a35bbd..ded2c64c787 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 @@ -673,7 +673,7 @@ protected void doDeletePetriNet(DeletePetriNetParams deletePetriNetParams, boole protected 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 d4b062650c2..36771317a71 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; @@ -84,6 +86,9 @@ public class PetriNetController { @Autowired private AsyncRunner asyncRunner; + @Autowired + private UserService userService; + public static String decodeUrl(String s1) { try { if (s1 == null) @@ -126,10 +131,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) { @@ -142,41 +148,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); @@ -185,7 +197,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 + "\""); @@ -194,25 +206,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); @@ -220,7 +234,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")}) @@ -229,13 +243,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 [{}] failed: could not decode process ID from URL", processId); return MessageResource.errorMessage("Deleting Petri net " + processId + " failed!"); } - LoggedUser user = (LoggedUser) auth.getPrincipal(); + LoggedUser user = ActorTransformer.toLoggedUser(userService.getLoggedUser()); asyncRunner.execute(() -> { if (force) { this.service.forceDeletePetriNet(DeletePetriNetParams.with() @@ -252,6 +266,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/TaskAuthorizationService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java index 0a3d0d12ffb..e55496d64a4 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,26 +1,25 @@ package com.netgrif.application.engine.workflow.service; import com.netgrif.application.engine.objects.auth.domain.AbstractUser; -import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.objects.petrinet.domain.roles.RolePermission; import com.netgrif.application.engine.petrinet.domain.throwable.IllegalTaskStateException; import com.netgrif.application.engine.objects.workflow.domain.Task; import com.netgrif.application.engine.workflow.service.interfaces.ITaskAuthorizationService; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.*; @Service +@RequiredArgsConstructor public class TaskAuthorizationService extends AbstractAuthorizationService implements ITaskAuthorizationService { - @Autowired - private ITaskService taskService; + private final ITaskService taskService; @Override - public Boolean userHasAtLeastOneRolePermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { - return userHasAtLeastOneRolePermission(loggedUser, taskService.findById(taskId), permissions); + public Boolean userHasAtLeastOneRolePermission(AbstractUser user, String taskId, RolePermission... permissions) { + return userHasAtLeastOneRolePermission(user, taskService.findById(taskId), permissions); } @Override @@ -40,8 +39,8 @@ public Boolean userHasAtLeastOneRolePermission(AbstractUser user, Task task, Rol } @Override - public Boolean userHasUserListPermission(LoggedUser loggedUser, String taskId, RolePermission... permissions) { - return userHasUserListPermission(loggedUser, taskService.findById(taskId), permissions); + public Boolean userHasUserListPermission(AbstractUser user, String taskId, RolePermission... permissions) { + return userHasUserListPermission(user, taskService.findById(taskId), permissions); } @Override @@ -65,11 +64,6 @@ public Boolean userHasUserListPermission(AbstractUser user, Task task, RolePermi return checkPermissions(userPermissions, Arrays.stream(permissions).map(RolePermission::toString).toList()); } - @Override - public boolean isAssignee(LoggedUser loggedUser, String taskId) { - return isAssignee(loggedUser, taskService.findById(taskId)); - } - @Override public boolean isAssignee(AbstractUser user, String taskId) { return isAssignee(user, taskService.findById(taskId)); @@ -77,12 +71,12 @@ public boolean isAssignee(AbstractUser user, String taskId) { @Override public boolean isAssignee(AbstractUser user, Task task) { - if (!isAssigned(task)) + if (!isAssigned(task)) { return false; - else - // TODO: impersonation user.getSelfOrImpersonated().getStringId() + } else { return task.getUserId().equals(user.getStringId()) || (user.getAttributeValue("anonymous") != null && (Boolean) user.getAttributeValue("anonymous")); + } } private boolean isAssigned(String taskId) { @@ -94,66 +88,66 @@ private boolean isAssigned(Task task) { } @Override - public boolean canCallAssign(LoggedUser loggedUser, String taskId) { - // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() - if (loggedUser.isAdmin()) { + public boolean canCallAssign(AbstractUser user, String taskId) { + // TODO: impersonation user.getSelfOrImpersonated().isAdmin() + if (user.isAdmin()) { return true; } Task task = taskService.findById(taskId); // TODO: impersonation - Boolean userPerm = userHasUserListPermission(loggedUser, task, RolePermission.ASSIGN); + Boolean userPerm = userHasUserListPermission(user, task, RolePermission.ASSIGN); if (userPerm != null) { return userPerm; } // TODO: impersonation - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, task, RolePermission.ASSIGN); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, task, RolePermission.ASSIGN); return rolePerm != null && rolePerm; } @Override - public boolean canCallDelegate(LoggedUser loggedUser, String taskId) { - // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() - if (loggedUser.isAdmin()) { + public boolean canCallDelegate(AbstractUser user, String taskId) { + // TODO: impersonation user.getSelfOrImpersonated().isAdmin() + if (user.isAdmin()) { return true; } Task task = taskService.findById(taskId); // TODO: impersonation - Boolean userPerm = userHasUserListPermission(loggedUser, task, RolePermission.DELEGATE); + Boolean userPerm = userHasUserListPermission(user, task, RolePermission.DELEGATE); if (userPerm != null) { return userPerm; } // TODO: impersonation - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, task, RolePermission.DELEGATE); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, task, RolePermission.DELEGATE); return rolePerm != null && rolePerm; } @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 '%s' cannot be finished, because it is not assigned!".formatted(taskId)); } // TODO: impersonation - if (loggedUser.isAdmin()) { + if (user.isAdmin()) { return true; } Task task = taskService.findById(taskId); // TODO: impersonation - if (!isAssignee(loggedUser, task)) { + if (!isAssignee(user, task)) { return false; } // TODO: impersonation - Boolean userPerm = userHasUserListPermission(loggedUser, task, RolePermission.FINISH); + Boolean userPerm = userHasUserListPermission(user, task, RolePermission.FINISH); if (userPerm != null) { return userPerm; } // TODO: impersonation - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, task, RolePermission.FINISH); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, task, RolePermission.FINISH); return rolePerm != null && rolePerm; } @@ -166,42 +160,42 @@ private boolean canAssignedCancel(Task task) { } @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 '%s' cannot be canceled, because it is not assigned!".formatted(taskId)); } // TODO: impersonation - if (loggedUser.isAdmin()) { + if (user.isAdmin()) { return true; } Task task = taskService.findById(taskId); // TODO: impersonation - if (!isAssignee(loggedUser, task) || !canAssignedCancel(task)) { + if (!isAssignee(user, task) || !canAssignedCancel(task)) { return false; } // TODO: impersonation - Boolean userPerm = userHasUserListPermission(loggedUser, task, RolePermission.CANCEL); + Boolean userPerm = userHasUserListPermission(user, task, RolePermission.CANCEL); if (userPerm != null) { return userPerm; } // TODO: impersonation - Boolean rolePerm = userHasAtLeastOneRolePermission(loggedUser, task, RolePermission.CANCEL); + Boolean rolePerm = userHasAtLeastOneRolePermission(user, task, RolePermission.CANCEL); return rolePerm != null && rolePerm; } @Override - public boolean canCallSaveData(LoggedUser loggedUser, String taskId) { - // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() - return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); + public boolean canCallSaveData(AbstractUser user, String taskId) { + // TODO: impersonation user.getSelfOrImpersonated().isAdmin() + return user.isAdmin() || isAssignee(user, taskId); } @Override - public boolean canCallSaveFile(LoggedUser loggedUser, String taskId) { - // TODO: impersonation loggedUser.getSelfOrImpersonated().isAdmin() - return loggedUser.isAdmin() || isAssignee(loggedUser, taskId); + public boolean canCallSaveFile(AbstractUser user, String taskId) { + // TODO: impersonation user.getSelfOrImpersonated().isAdmin() + return user.isAdmin() || isAssignee(user, taskId); } private Map findUserPermissions(Task task, AbstractUser user) { 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 ae4f4aadd74..6def56b4a4e 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 @@ -23,7 +23,7 @@ public class WorkflowAuthorizationService extends AbstractAuthorizationService i private IPetriNetService petriNetService; @Override - public boolean canCallDelete(LoggedUser user, String caseId) { + public boolean canCallDelete(AbstractUser user, String caseId) { // TODO: impersonation user.getSelfOrImpersonated().isAdmin() if (user.isAdmin()) { return true; @@ -42,7 +42,7 @@ public boolean canCallDelete(LoggedUser user, String caseId) { } @Override - public boolean canCallCreate(LoggedUser user, String netId) { + public boolean canCallCreate(AbstractUser user, String netId) { // TODO: impersonation if (user.isAdmin()) { return true; @@ -53,6 +53,7 @@ public boolean canCallCreate(LoggedUser user, String netId) { return userHasAtLeastOneRolePermission(user, net, ProcessRolePermission.CREATE); } + @Override public Boolean userHasAtLeastOneRolePermission(AbstractUser user, PetriNet net, ProcessRolePermission... permissions) { Map aggregatePermissions = getAggregatePermissions(user, net.getPermissions()); 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 7aad17a23de..c04ff01ccd7 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,6 +3,8 @@ import tools.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; @@ -11,6 +13,8 @@ import com.netgrif.application.engine.workflow.params.TaskParams; 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; @@ -27,6 +31,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; @@ -49,6 +54,7 @@ import java.util.*; import java.util.stream.Collectors; +@RequiredArgsConstructor public abstract class AbstractTaskController { public static final Logger log = LoggerFactory.getLogger(TaskController.class); @@ -63,31 +69,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); @@ -106,8 +101,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(TaskParams.with() .taskId(taskId) @@ -119,8 +115,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(DelegateTaskParams.with() .delegator(loggedUser) @@ -133,9 +130,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(TaskParams.with() .taskId(taskId) @@ -151,8 +148,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(TaskParams.with() .taskId(taskId) @@ -168,53 +166,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 index 59f2bdb45bc..e69de29bb2d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java @@ -1,215 +0,0 @@ -package com.netgrif.application.engine.workflow.web; - -import tools.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 f25d6a3a7ed..00000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java +++ /dev/null @@ -1,67 +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.params.CreateCaseParams; -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(CreateCaseParams.with() - .processId(body.netId) - .title(body.title) - .color(body.color) - .author(loggedUser) - .locale(locale) - .build()); - 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 2bd0ee6a23b..6837a012aa2 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 tools.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) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #dataBody.parentTaskId)") + @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) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @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) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @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) && @taskAuthorizationService.canCallSaveFile(#auth.getPrincipal(), #requestBody.parentTaskId)") + @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 9d4615b06e4..c8b6d1e9c29 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; @@ -25,9 +27,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; @@ -45,11 +48,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; @@ -63,28 +64,27 @@ 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(CreateCaseParams.with() .processId(body.netId) @@ -95,45 +95,44 @@ public EntityModel createCase(@RequestBody CreateCaseBo .build()); 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 resources; + 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 resources; + 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); if (log.isTraceEnabled()) { log.trace("Found {} cases with id {}.", cases.getNumberOfElements(), cases.map(Case::getStringId)); } 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); @@ -149,25 +148,26 @@ public PagedModel search(@RequestBody SingleCaseSearchRequestAsLis return resources; } + @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 resources; + .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) { @@ -177,15 +177,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 resources; + return getCaseResources(pageable, assembler, cases, selfLink); } @PreAuthorize("@authorizationService.hasAuthority('ADMIN')") @@ -210,24 +209,10 @@ public MessageResource reloadTasks(@PathVariable("id") String caseId) { } } -// @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) { + public EntityModel deleteCase(@PathVariable("id") String caseId, @RequestParam(defaultValue = "false") boolean deleteSubtree) { try { caseId = URLDecoder.decode(caseId, StandardCharsets.UTF_8); DeleteCaseEventOutcome outcome; @@ -246,6 +231,7 @@ public EntityModel deleteCase(Authentication auth, @Pat } } + @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 { @@ -264,6 +250,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 { @@ -281,4 +268,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 89174162393..1c3825d3b8f 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 @@ -22,7 +22,7 @@ public CaseResource(Case content) { private void buildLinks() { add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder .methodOn(WorkflowController.class) - .createCase(new CreateCaseBody(), null, LocaleContextHolder.getLocale())) + .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 8795ba2e14c..da652eff791 100644 --- a/application-engine/src/main/resources/application-dev.yaml +++ b/application-engine/src/main/resources/application-dev.yaml @@ -12,6 +12,12 @@ netgrif: mongodb: drop: true elasticsearch: + priority: + full-text-fields: + - "title.keyword^2" + - "authorName^1" + - "authorEmail^1" + - "visualId^2" drop: true index: petriNet: ${NETGRIF_ENGINE_DATA_DATABASE_NAME:nae}_dev_petrinet @@ -35,9 +41,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/health - auth: - admin-password: password + 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 30df7a68c5d..387941fd6a5 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -85,9 +85,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/health - anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/manage/health + 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 d8679413385..b8c461d6231 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 @@ -99,19 +99,19 @@ class TestHelper { actionsCacheService.clearGlobalFunctionCache() petriNetService.evictAllCaches() - defaultRoleRunner.run() - elasticsearchRunner.run() - defaultRealmRunner.run() - anonymousRoleRunner.run() - systemUserRunner.run() - groupRunner.run() - filterRunner.run() - menuProcessRunner.run() - impersonationRunner.run() - superCreator.run() - finisherRunner.run() - } + defaultRoleRunner.run() + anonymousRoleRunner.run() + elasticsearchRunner.run() + defaultRealmRunner.run() + systemUserRunner.run() + groupRunner.run() + filterRunner.run() + menuProcessRunner.run() + impersonationRunner.run() + superCreator.run() + finisherRunner.run() } +} private void clearMongoCollections() { int attempts = 0 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 8f08b04eaf3..29c085ccc94 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 @@ -1,6 +1,8 @@ package com.netgrif.application.engine.workflow import com.netgrif.application.engine.TestHelper +import com.netgrif.application.engine.adapter.spring.auth.domain.AuthorityImpl +import com.netgrif.application.engine.adapter.spring.auth.domain.User import com.netgrif.application.engine.auth.service.AuthorityService import com.netgrif.application.engine.auth.service.UserService import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService @@ -36,6 +38,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 import tools.jackson.databind.ObjectMapper @@ -106,15 +110,21 @@ class TaskControllerTest { @BeforeEach void init() { testHelper.truncateDbs() - userService.saveUser(new com.netgrif.application.engine.adapter.spring.auth.domain.User( + def user = userService.saveUser(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 543ccebec1c..7b055142774 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java @@ -388,4 +388,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 374d6473b55..5746834fea2 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,8 @@ package com.netgrif.application.engine.objects.auth.domain; +import lombok.Getter; +import lombok.Setter; + import java.time.Duration; /** @@ -12,11 +15,36 @@ 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 + @Getter + 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 + @Getter + private static UserFactory userFactory = () -> { + throw new IllegalStateException("No UserFactory configured"); + }; + /** * Functional interface defining a factory method for creating new LoggedUser instances. */ @@ -29,17 +57,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(); - public static LoggedUserFactory getLoggedUserFactory() { - return factory; + /** + * 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"); + } } /** @@ -48,7 +86,7 @@ public static LoggedUserFactory getLoggedUserFactory() { * @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()); @@ -65,6 +103,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 d29da054933..82de57794d9 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 e8532cbbdee..8cac020a051 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 jakarta.validation.constraints.NotNull; +import java.io.Serial; +import java.io.Serializable; import java.time.Duration; import java.time.LocalDateTime; import java.util.HashSet; @@ -22,8 +25,14 @@ * authorities, and groups. */ @Data -@Document(collection = "anonym_user") -public class AnonymousUserRef { +@Document(collection = "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/LoggedUserConfiguration.java b/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java deleted file mode 100644 index 0e60422b87b..00000000000 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.netgrif.application.engine.adapter.spring.configuration; - -import com.netgrif.application.engine.adapter.spring.auth.service.DefaultLoggedUserFactory; -import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; -import jakarta.annotation.PostConstruct; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class LoggedUserConfiguration { - - @PostConstruct - public void initializeLoggedUserFactory() { - ActorTransformer.setLoggedUserFactory(new DefaultLoggedUserFactory()); - } -} diff --git a/nae-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/config/LoggedUserConfiguration.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java new file mode 100644 index 00000000000..bf5ce2a1cbb --- /dev/null +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java @@ -0,0 +1,20 @@ +package com.netgrif.application.engine.auth.config; + +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(loggedUserFactory); + ActorTransformer.setUserFactory(userFactory); + } +} 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/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java similarity index 86% rename from nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java rename to nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java index a27c227fd02..cbd527d83aa 100644 --- a/nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/service/DefaultLoggedUserFactory.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.adapter.spring.auth.service; +package com.netgrif.application.engine.auth.service; import com.netgrif.application.engine.adapter.spring.auth.domain.LoggedUserImpl; import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; @@ -12,4 +12,4 @@ public class DefaultLoggedUserFactory implements ActorTransformer.LoggedUserFact public LoggedUser create() { return new LoggedUserImpl(); } -} +} \ No newline at end of file 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..1730014575e --- /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.adapter.spring.auth.domain.User; +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 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 a67217ea88b..ea0e6db8941 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 @@ -68,6 +68,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 91ef5a86c82..6f55223f70e 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());