Allowing user to define multiple AclPermissions for a given permission

Also adding proper Acl permissions on ApplicationService and PageService functions
This commit is contained in:
Arpit Mohan 2020-02-18 15:45:12 +05:30
parent 9f3197792a
commit bd1c390402
14 changed files with 148 additions and 75 deletions

View File

@ -0,0 +1,71 @@
package com.appsmith.server.aspects;
import com.appsmith.server.domains.User;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.services.AclEntity;
import com.appsmith.server.services.AclPermission;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
@Aspect
@Component
@Slf4j
public class AclAspect {
@Around("execution(reactor.core.publisher.Mono+ com.appsmith.server.services.CrudService.*(..))")
public Mono<?> checkAuthorization(ProceedingJoinPoint joinPoint) {
try {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.filter(principal -> {
User user = (User) principal;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.debug("Checking the authorization for user: {} for function: {}", user.getEmail(), method.getName());
// Get the auth permission to be applied
AclPermission aclPermissionAnnotation = AnnotationUtils.findAnnotation(method, AclPermission.class);
if (aclPermissionAnnotation == null) {
// There are no permission annotations on the method. Continue
return true;
}
String[] authPermission = aclPermissionAnnotation.values();
// Get the entity on which permission needs to be applied
String authEntity = joinPoint.getTarget().getClass().getAnnotation(AclEntity.class).value();
Set<GrantedAuthority> actualPermissions = new HashSet<>();
if (authPermission != null && authPermission.length > 0) {
for (int i = 0; i < authPermission.length; i++) {
actualPermissions.add(new SimpleGrantedAuthority(authPermission[i] + ":" + authEntity));
}
}
log.debug("Permissions required to execute function: {} are: {}", method.getName(), actualPermissions);
return user.getAuthorities().containsAll(actualPermissions);
})
// If the user is not authorized, the filter will not emit. Hence, we return "Unauthorized"
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.UNAUTHORIZED_ACCESS)))
// The user is authorized to proceed to function execution
.then((Mono<?>) joinPoint.proceed());
} catch (Throwable throwable) {
return Mono.error(throwable);
}
}
}

View File

@ -1,42 +0,0 @@
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);
}
}
}

View File

@ -136,13 +136,4 @@ public class SecurityConfig {
.logoutSuccessHandler(new LogoutSuccessHandler(objectMapper))
.and().build();
}
@Bean
public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
return expressionHandler;
}
}

View File

@ -18,9 +18,11 @@ public interface AclConstants {
"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";
String READ_PERMISSION = "read";
String CREATE_PERMISSION = "create";
String DELETE_PERMISSION = "delete";
String UPDATE_PERMISSION = "update";
String PUBLISH_PERMISSION = "publish";
String ARCHIVE_PERMISSION = "archive";
}

View File

@ -0,0 +1,6 @@
package com.appsmith.server.constants;
public interface Entity {
String APPLICATIONS = "applications";
String PAGES = "pages";
}

View File

@ -13,8 +13,8 @@ public interface Url {
String DATASOURCE_URL = BASE_URL + VERSION + "/datasources";
String ACTION_URL = BASE_URL + VERSION + "/actions";
String USER_URL = BASE_URL + VERSION + "/users";
String APPLICATION_URL = BASE_URL + VERSION + "/applications";
String PAGE_URL = BASE_URL + VERSION + "/pages";
String APPLICATION_URL = BASE_URL + VERSION + "/" + Entity.APPLICATIONS;
String PAGE_URL = BASE_URL + VERSION + "/" + Entity.PAGES;
String PROPERTY_URL = BASE_URL + VERSION + "/properties";
String CONFIG_URL = BASE_URL + VERSION + "/configs";
String TEAM_URL = BASE_URL + VERSION + "/teams";

View File

@ -0,0 +1,16 @@
package com.appsmith.server.services;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AclEntity {
String value();
}

View File

@ -0,0 +1,18 @@
package com.appsmith.server.services;
import org.checkerframework.framework.qual.InheritedAnnotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AclPermission {
String[] values();
}

View File

@ -1,31 +1,26 @@
package com.appsmith.server.services;
import com.appsmith.server.constants.AclConstants;
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<Application, String> {
// @Override
// @PreAuthorize("hasPermission('someValue', T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)")
// Mono<Application> getById(String id);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)")
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Application> findById(String id);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)")
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Application> findByIdAndOrganizationId(String id, String organizationId);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).READ_APPLICATION_PERMISSION)")
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Application> findByName(String name);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).PUBLISH_APPLICATION_PERMISSION)")
@AclPermission(values = AclConstants.PUBLISH_PERMISSION)
Mono<Boolean> publish(String applicationId);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).CREATE_APPLICATION_PERMISSION)")
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Application> save(Application application);
@PreAuthorize("hasPermission(#user, T(com.appsmith.server.constants.AclConstants).DELETE_APPLICATION_PERMISSION)")
@AclPermission(values = {AclConstants.ARCHIVE_PERMISSION, AclConstants.DELETE_PERMISSION})
Mono<Application> archive(Application application);
}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.services;
import com.appsmith.server.constants.Entity;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationPage;
@ -28,6 +29,7 @@ import java.util.List;
@Slf4j
@Service
@AclEntity(Entity.APPLICATIONS)
public class ApplicationServiceImpl extends BaseService<ApplicationRepository, Application, String> implements ApplicationService {
private final SessionUserService sessionUserService;

View File

@ -1,20 +1,25 @@
package com.appsmith.server.services;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.server.constants.AclConstants;
import org.springframework.util.MultiValueMap;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CrudService<T extends BaseDomain, ID> {
@AclPermission(values = AclConstants.READ_PERMISSION)
Flux<T> get(MultiValueMap<String, String> params);
@AclPermission(values = AclConstants.CREATE_PERMISSION)
Mono<T> create(T resource);
@AclPermission(values = AclConstants.UPDATE_PERMISSION)
Mono<T> update(ID id, T resource);
// @PreAuthorize("hasPermission('someValue', @aclComponent.getPermission(#returnObject))")
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<T> getById(ID id);
@AclPermission(values = AclConstants.DELETE_PERMISSION)
Mono<T> delete(ID id);
}

View File

@ -1,4 +0,0 @@
package com.appsmith.server.services;
//public @interface Domain {
//}

View File

@ -1,5 +1,6 @@
package com.appsmith.server.services;
import com.appsmith.server.constants.AclConstants;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.Page;
import com.appsmith.server.dtos.PageNameIdDTO;
@ -8,23 +9,33 @@ import reactor.core.publisher.Mono;
public interface PageService extends CrudService<Page, String> {
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Page> findById(String pageId);
@AclPermission(values = AclConstants.READ_PERMISSION)
Flux<Page> findByApplicationId(String applicationId);
@AclPermission(values = {AclConstants.CREATE_PERMISSION, AclConstants.UPDATE_PERMISSION})
Mono<Page> save(Page page);
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Page> findByIdAndLayoutsId(String pageId, String layoutId);
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Page> findByName(String name);
@AclPermission(values = AclConstants.DELETE_PERMISSION)
Mono<Void> deleteAll();
@AclPermission(values = AclConstants.READ_PERMISSION)
Flux<PageNameIdDTO> findNamesByApplicationId(String applicationId);
@AclPermission(values = AclConstants.READ_PERMISSION)
Layout createDefaultLayout();
@AclPermission(values = AclConstants.READ_PERMISSION)
Flux<PageNameIdDTO> findNamesByApplicationName(String applicationName);
@AclPermission(values = AclConstants.READ_PERMISSION)
Mono<Page> findByNameAndApplicationId(String name, String applicationId);
}

View File

@ -1,6 +1,7 @@
package com.appsmith.server.services;
import com.appsmith.server.constants.AnalyticsEvents;
import com.appsmith.server.constants.Entity;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.server.domains.Application;
@ -27,6 +28,7 @@ import java.util.List;
@Service
@Slf4j
@AclEntity(Entity.PAGES)
public class PageServiceImpl extends BaseService<PageRepository, Page, String> implements PageService {
private final ApplicationService applicationService;