diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml index 96fe6643b4..bf28473990 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -94,6 +94,10 @@ org.springframework.session spring-session-data-redis + + org.springframework.boot + spring-boot-starter-aop + org.hibernate.validator hibernate-validator diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/aspects/ContextAspect.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/aspects/ContextAspect.java new file mode 100644 index 0000000000..89121d1f63 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/aspects/ContextAspect.java @@ -0,0 +1,42 @@ +package com.appsmith.server.aspects; + +import com.appsmith.server.domains.User; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Aspect +@Component +@Slf4j +public class ContextAspect { + +// @Around("execution(reactor.core.publisher.Mono+ com.appsmith.server.services.CrudService+") + @Around("execution(reactor.core.publisher.Mono+ com.appsmith.server.services.CrudService.*(..))") + public Mono addAuthorization(ProceedingJoinPoint joinPoint) { + try { + log.debug("In the custom aspect"); + return ReactiveSecurityContextHolder.getContext() + .map(ctx -> ctx.getAuthentication()) + .map(auth -> auth.getPrincipal()) + .map(principal -> { + User user = (User) principal; + log.debug("{}", user.getAuthorities()); + if(user.getAuthorities().contains(new SimpleGrantedAuthority("read:applications"))) { + log.debug("Got the permission"); + } + return principal; + }) + .then((Mono) joinPoint.proceed()); +// return Mono.just(true); +// return ((Mono) joinPoint.proceed()); +// .subscriberContext(Context.of(UserRepository.CONTEXT_CLIENT_KEY, getClient())); + } catch (Throwable throwable) { + return Mono.error(throwable); + } + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java index c4ebb50b3b..7d3d096270 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java @@ -5,6 +5,8 @@ import com.appsmith.server.constants.Security; import com.appsmith.server.domains.LoginSource; import com.appsmith.server.domains.User; import com.appsmith.server.domains.UserState; +import com.appsmith.server.repositories.GroupRepository; +import com.appsmith.server.services.GroupService; import com.appsmith.server.services.UserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,6 +35,9 @@ public class AuthenticationSuccessHandler implements ServerAuthenticationSuccess @Autowired UserService userService; + @Autowired + GroupRepository groupRepository; + private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy(); /** @@ -48,6 +53,7 @@ public class AuthenticationSuccessHandler implements ServerAuthenticationSuccess public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) { log.debug("Login succeeded for user: {}", authentication.getPrincipal()); + if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthAuthentication = (OAuth2AuthenticationToken) authentication; return checkAndCreateUser(oauthAuthentication) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java index 91b037b5f3..c42a7e2059 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java @@ -8,6 +8,7 @@ import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; @@ -21,6 +22,7 @@ import java.util.stream.Collectors; @Getter @Setter @Configuration +@EnableAspectJAutoProxy public class CommonConfig { private String ELASTIC_THREAD_POOL_NAME = "appsmith-elastic-pool"; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomPermissionEvaluator.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomPermissionEvaluator.java new file mode 100644 index 0000000000..6ef331f6bb --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomPermissionEvaluator.java @@ -0,0 +1,28 @@ +package com.appsmith.server.configurations; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.io.Serializable; + +@Slf4j +@Component +public class CustomPermissionEvaluator implements PermissionEvaluator { + + @Override + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + log.debug("In hasPermission with permission: {}", permission); + SimpleGrantedAuthority authority = new SimpleGrantedAuthority((String) permission); + return authentication.getAuthorities().contains(authority); + } + + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { + log.debug("In hasPermission 2"); + SimpleGrantedAuthority authority = new SimpleGrantedAuthority((String) permission); + return authentication.getAuthorities().contains(authority); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomWebExpressionHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomWebExpressionHandler.java new file mode 100644 index 0000000000..a0f6b7f9cb --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CustomWebExpressionHandler.java @@ -0,0 +1,37 @@ +package com.appsmith.server.configurations; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.access.expression.SecurityExpressionOperations; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.FilterInvocation; +import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; +import org.springframework.security.web.access.expression.WebSecurityExpressionRoot; +import org.springframework.stereotype.Component; + +@Component +public class CustomWebExpressionHandler extends DefaultWebSecurityExpressionHandler { + + private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private String defaultRolePrefix = "ROLE_"; + + @Override + protected SecurityExpressionOperations createSecurityExpressionRoot( + Authentication authentication, FilterInvocation fi) { + + System.out.println("In the custom security expresssion root"); + WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi); + root.setPermissionEvaluator(getPermissionEvaluator()); + root.setTrustResolver(trustResolver); + root.setRoleHierarchy(getRoleHierarchy()); + root.setDefaultRolePrefix(this.defaultRolePrefix); + return root; + } + + @Override + protected PermissionEvaluator getPermissionEvaluator() { + return null; + } + +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java index 59f696046d..783d855c05 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java @@ -7,9 +7,15 @@ import com.appsmith.server.constants.Url; import com.appsmith.server.services.UserService; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpMethod; +import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; @@ -30,6 +36,7 @@ import java.util.Arrays; import static com.appsmith.server.constants.Url.USER_URL; @EnableWebFluxSecurity +@EnableReactiveMethodSecurity public class SecurityConfig { @Autowired @@ -130,4 +137,12 @@ public class SecurityConfig { .and().build(); } + @Bean + public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() { + DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator()); + + return expressionHandler; + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclComponent.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclComponent.java new file mode 100644 index 0000000000..9620d9982c --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclComponent.java @@ -0,0 +1,13 @@ +package com.appsmith.server.constants; + +import com.appsmith.external.models.BaseDomain; +import org.springframework.stereotype.Component; + +@Component +public class AclComponent { + + public String getPermission(Object entity) { + System.out.println("In the getPermission"); + return "read:applications"; + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclConstants.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclConstants.java index 60c89a3d7d..22fbc34409 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclConstants.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AclConstants.java @@ -17,4 +17,10 @@ public interface AclConstants { "create:users", "read:users" ); + + String READ_APPLICATION_PERMISSION = "read:applications"; + String CREATE_APPLICATION_PERMISSION = "create:applications"; + String DELETE_APPLICATION_PERMISSION = "delete:applications"; + String UPDATE_APPLICATION_PERMISSION = "update:applications"; + String PUBLISH_APPLICATION_PERMISSION = "publish:applications"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java index 234e38bf5d..c894f50c52 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/User.java @@ -59,15 +59,9 @@ public class User extends BaseDomain implements UserDetails { @Override public Collection getAuthorities() { - - if (roles == null || roles.isEmpty()) //No existing roles found. - return null; - - Collection authorities = roles.stream() - .map(role -> new SimpleGrantedAuthority(role.toString())) - .collect(Collectors.toList()); - - return authorities; + return this.getPermissions().stream() + .map(permission -> new SimpleGrantedAuthority(permission)) + .collect(Collectors.toSet()); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ApplicationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ApplicationRepository.java index 717d807f32..698f9af130 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ApplicationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ApplicationRepository.java @@ -1,7 +1,10 @@ package com.appsmith.server.repositories; import com.appsmith.server.domains.Application; +import org.springframework.data.domain.Example; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Repository @@ -10,4 +13,10 @@ public interface ApplicationRepository extends BaseRepository findByIdAndOrganizationId(String id, String orgId); Mono findByName(String name); + + @Override + Flux findAll(Example example); + + @Override + Mono findById(String id); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/UserRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/UserRepository.java index b586ff7f2a..34250bd894 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/UserRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/UserRepository.java @@ -1,6 +1,7 @@ package com.appsmith.server.repositories; import com.appsmith.server.domains.User; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Repository; import reactor.core.publisher.Mono; @@ -8,4 +9,8 @@ import reactor.core.publisher.Mono; public interface UserRepository extends BaseRepository { Mono findByEmail(String email); +// { +// System.out.println("In the custom findByEmail"); +// return Mono.empty(); +// } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationService.java index 082f1e2cd1..e4790cc579 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationService.java @@ -1,18 +1,31 @@ package com.appsmith.server.services; import com.appsmith.server.domains.Application; +import org.springframework.security.access.prepost.PreAuthorize; import reactor.core.publisher.Mono; +//@Domain("applications") public interface ApplicationService extends CrudService { + +// @Override +// @PreAuthorize("hasPermission('someValue', T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)") +// Mono getById(String id); + + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)") Mono findById(String id); + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)") Mono findByIdAndOrganizationId(String id, String organizationId); + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)") Mono findByName(String name); + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).PUBLISH_APPLICATION_PERMISSION)") Mono publish(String applicationId); + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).CREATE_APPLICATION_PERMISSION)") Mono save(Application application); + @PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).DELETE_APPLICATION_PERMISSION)") Mono archive(Application application); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CrudService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CrudService.java index e9084e217b..d77657591f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CrudService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CrudService.java @@ -13,6 +13,7 @@ public interface CrudService { Mono update(ID id, T resource); +// @PreAuthorize("hasPermission('someValue', @aclComponent.getPermission(#returnObject))") Mono getById(ID id); Mono delete(ID id); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/Domain.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/Domain.java new file mode 100644 index 0000000000..e1d7f8fd43 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/Domain.java @@ -0,0 +1,4 @@ +package com.appsmith.server.services; + +//public @interface Domain { +//} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 1174000ee7..b54d002954 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -11,6 +11,7 @@ import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.BeanCopyUtils; import com.appsmith.server.notifications.EmailSender; +import com.appsmith.server.repositories.GroupRepository; import com.appsmith.server.repositories.InviteUserRepository; import com.appsmith.server.repositories.PasswordResetTokenRepository; import com.appsmith.server.repositories.UserRepository; @@ -46,7 +47,7 @@ public class UserServiceImpl extends BaseService i private final PasswordResetTokenRepository passwordResetTokenRepository; private final PasswordEncoder passwordEncoder; private final EmailSender emailSender; - private final GroupService groupService; + private final GroupRepository groupRepository; private final InviteUserRepository inviteUserRepository; private final UserOrganizationService userOrganizationService; @@ -68,7 +69,7 @@ public class UserServiceImpl extends BaseService i PasswordResetTokenRepository passwordResetTokenRepository, PasswordEncoder passwordEncoder, EmailSender emailSender, - GroupService groupService, + GroupRepository groupRepository, InviteUserRepository inviteUserRepository, UserOrganizationService userOrganizationService) { super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); @@ -79,7 +80,7 @@ public class UserServiceImpl extends BaseService i this.passwordResetTokenRepository = passwordResetTokenRepository; this.passwordEncoder = passwordEncoder; this.emailSender = emailSender; - this.groupService = groupService; + this.groupRepository = groupRepository; this.inviteUserRepository = inviteUserRepository; this.userOrganizationService = userOrganizationService; } @@ -485,6 +486,15 @@ public class UserServiceImpl extends BaseService i .switchIfEmpty(Mono.error(new UsernameNotFoundException("Unable to find username: " + username))) // This object cast is required to ensure that we send the right object type back to Spring framework. // Doesn't work without this. - .map(user -> (UserDetails) user); + .flatMap(user -> { + Set groupSet = user.getGroupIds(); + + return groupRepository.findAllById(groupSet) + .map(group -> group.getPermissions()) + // Adding permissions from all the groups that the user is a part of + .map(permissions -> user.getPermissions().addAll(permissions)) + .collectList() + .thenReturn((UserDetails) user); + }); } } diff --git a/app/server/appsmith-server/src/main/resources/application-local.properties b/app/server/appsmith-server/src/main/resources/application-local.properties index 9ddf6b8227..3cf53d4ad1 100644 --- a/app/server/appsmith-server/src/main/resources/application-local.properties +++ b/app/server/appsmith-server/src/main/resources/application-local.properties @@ -1,5 +1,6 @@ # Appsmith Configurations appsmith.baseUri=http://localhost:8080 +spring.main.allow-bean-definition-overriding=true #Mongo properties spring.data.mongodb.database=mobtools