Adding the user policy object that has permissions for ARN objects.
Now we need to parse the ARN and match it to the policy in the PreAuthorize & Custom repository functions.
This commit is contained in:
parent
bd5424095a
commit
e078382b94
|
|
@ -1,5 +1,8 @@
|
|||
package com.appsmith.server.configurations;
|
||||
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.helpers.AclHelper;
|
||||
import com.appsmith.server.services.AclEntity;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
|
@ -7,6 +10,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
|
@ -15,8 +19,19 @@ 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);
|
||||
AclEntity aclEntity = targetDomainObject.getClass().getAnnotation(AclEntity.class);
|
||||
// Create the ARN
|
||||
String arn = AclHelper.createArn(aclEntity, (User) authentication.getPrincipal(), null);
|
||||
String authorityToCheck = AclHelper.concatenatePermissionWithArn((String) permission, arn);
|
||||
log.debug("Got authority to check: {}", authorityToCheck);
|
||||
|
||||
boolean result = authentication.getAuthorities().stream()
|
||||
.anyMatch(auth -> auth.getAuthority().equals(authorityToCheck)
|
||||
|| auth.getAuthority().matches(authorityToCheck)
|
||||
|| authorityToCheck.matches(auth.getAuthority())
|
||||
);
|
||||
log.debug("Got hasPermission result: {}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -138,4 +138,12 @@ public class SecurityConfig {
|
|||
.logoutSuccessHandler(new LogoutSuccessHandler(objectMapper))
|
||||
.and().build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||
DefaultMethodSecurityExpressionHandler expressionHandler =
|
||||
new DefaultMethodSecurityExpressionHandler();
|
||||
expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
|
||||
return expressionHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,56 +69,7 @@ public class SoftDeleteMongoQueryLookupStrategy implements QueryLookupStrategy {
|
|||
}
|
||||
ReactivePartTreeMongoQuery partTreeQuery = (ReactivePartTreeMongoQuery) repositoryQuery;
|
||||
|
||||
return new SoftDeletePartTreeMongoQuery(method, partTreeQuery);
|
||||
return new SoftDeletePartTreeMongoQuery(method, partTreeQuery, this.mongoOperations, EXPRESSION_PARSER, evaluationContextProvider);
|
||||
}
|
||||
|
||||
private Criteria notDeleted() {
|
||||
return new Criteria().orOperator(
|
||||
where("deleted").exists(false),
|
||||
where("deleted").is(false)
|
||||
);
|
||||
}
|
||||
|
||||
private class SoftDeletePartTreeMongoQuery extends ReactivePartTreeMongoQuery {
|
||||
private ReactivePartTreeMongoQuery reactivePartTreeQuery;
|
||||
private Method method;
|
||||
|
||||
SoftDeletePartTreeMongoQuery(Method method, ReactivePartTreeMongoQuery reactivePartTreeMongoQuery) {
|
||||
super((ReactiveMongoQueryMethod) reactivePartTreeMongoQuery.getQueryMethod(),
|
||||
mongoOperations, EXPRESSION_PARSER, evaluationContextProvider);
|
||||
this.reactivePartTreeQuery = reactivePartTreeMongoQuery;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||
// SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
// User userPrincipal = ReactiveSecurityContextHolder.getContext()
|
||||
// .switchIfEmpty(Mono.error(new Exception("no context")))
|
||||
// .map(ctx -> ctx.getAuthentication())
|
||||
// .map(auth -> auth.getPrincipal())
|
||||
// .map(principal -> {
|
||||
// if (principal instanceof User) {
|
||||
// return (User) principal;
|
||||
// }
|
||||
// return new User();
|
||||
// }).block();
|
||||
AclPermission aclPermission = method.getAnnotation(AclPermission.class);
|
||||
if (aclPermission != null) {
|
||||
log.debug("Got principal: {}", aclPermission.principal());
|
||||
}
|
||||
Query query = super.createQuery(accessor);
|
||||
return withNotDeleted(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
|
||||
Query query = super.createCountQuery(accessor);
|
||||
return withNotDeleted(query);
|
||||
}
|
||||
|
||||
private Query withNotDeleted(Query query) {
|
||||
return query.addCriteria(notDeleted());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package com.appsmith.server.configurations.mongo;
|
||||
|
||||
import com.appsmith.server.services.AclPermission;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
|
||||
import org.springframework.data.mongodb.repository.query.MongoQueryMethod;
|
||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryMethod;
|
||||
import org.springframework.data.mongodb.repository.query.ReactivePartTreeMongoQuery;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
@Slf4j
|
||||
public class SoftDeletePartTreeMongoQuery extends ReactivePartTreeMongoQuery {
|
||||
private ReactivePartTreeMongoQuery reactivePartTreeQuery;
|
||||
private Method method;
|
||||
|
||||
SoftDeletePartTreeMongoQuery(Method method, ReactivePartTreeMongoQuery reactivePartTreeMongoQuery,
|
||||
ReactiveMongoOperations mongoOperations,
|
||||
SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super((ReactiveMongoQueryMethod) reactivePartTreeMongoQuery.getQueryMethod(),
|
||||
mongoOperations, expressionParser, evaluationContextProvider);
|
||||
this.reactivePartTreeQuery = reactivePartTreeMongoQuery;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||
// SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
// User userPrincipal = ReactiveSecurityContextHolder.getContext()
|
||||
// .switchIfEmpty(Mono.error(new Exception("no context")))
|
||||
// .map(ctx -> ctx.getAuthentication())
|
||||
// .map(auth -> auth.getPrincipal())
|
||||
// .map(principal -> {
|
||||
// if (principal instanceof User) {
|
||||
// return (User) principal;
|
||||
// }
|
||||
// return new User();
|
||||
// }).block();
|
||||
AclPermission aclPermission = method.getAnnotation(AclPermission.class);
|
||||
if (aclPermission != null) {
|
||||
log.debug("Got principal: {}", aclPermission.principal());
|
||||
}
|
||||
MongoQueryMethod mongoQueryMethod = this.getQueryMethod();
|
||||
Query query = super.createQuery(accessor);
|
||||
return withNotDeleted(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
|
||||
Query query = super.createCountQuery(accessor);
|
||||
return withNotDeleted(query);
|
||||
}
|
||||
|
||||
private Query withNotDeleted(Query query) {
|
||||
return query.addCriteria(notDeleted());
|
||||
}
|
||||
|
||||
private Criteria notDeleted() {
|
||||
return new Criteria().orOperator(
|
||||
where("deleted").exists(false),
|
||||
where("deleted").is(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Arn {
|
||||
|
||||
String base = "arn:appsmith";
|
||||
|
||||
String organizationId;
|
||||
|
||||
String entity;
|
||||
|
||||
String entityId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return base + ":" + organizationId + ":" + entity + ":" + entityId;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import org.springframework.data.mongodb.core.index.CompoundIndex;
|
|||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
|
|
@ -25,7 +26,7 @@ public class Group extends BaseDomain {
|
|||
@NotNull
|
||||
private String organizationId;
|
||||
|
||||
/*
|
||||
/**
|
||||
* This is a list of name of permissions. We will query with permission collection by name
|
||||
* This is because permissions are global in nature. They are not specific to a particular org/team.
|
||||
*/
|
||||
|
|
@ -33,6 +34,11 @@ public class Group extends BaseDomain {
|
|||
|
||||
private Boolean isDefault = false;
|
||||
|
||||
/**
|
||||
* These are the policies attached to the group. All users who are part of this group will inherit these policies
|
||||
*/
|
||||
Set<Policy> policies = new HashSet<>();
|
||||
|
||||
/**
|
||||
* If the display name is null or empty, then just return the actual group name. This is just to ensure that
|
||||
* the client is never sent an empty group name for displaying on the UI.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/*
|
||||
TODO: Create a PolicyTemplate that will store all complex policies like "publishApp" which requires mulitple permissions
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class Policy implements Serializable {
|
||||
|
||||
Set<String> permissions;
|
||||
|
||||
Set<String> entities;
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.appsmith.server.domains;
|
||||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.server.helpers.AclHelper;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
|
@ -13,7 +15,9 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -57,9 +61,26 @@ public class User extends BaseDomain implements UserDetails {
|
|||
// During evaluation a union of the group permissions and user-specific permissions will take effect.
|
||||
private Set<String> permissions = new HashSet<>();
|
||||
|
||||
private Set<Policy> policies = new HashSet<>();
|
||||
|
||||
@JsonIgnore
|
||||
@Transient
|
||||
Set<String> flatPermissions = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.getPermissions().stream()
|
||||
// TODO: Also extract the policies from associated groups
|
||||
if (this.flatPermissions != null) {
|
||||
for (Policy policy : this.policies) {
|
||||
for (String entity : policy.getEntities()) {
|
||||
for (String permission : policy.getPermissions()) {
|
||||
flatPermissions.add(AclHelper.concatenatePermissionWithArn(permission, entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.getFlatPermissions().stream()
|
||||
.map(permission -> new SimpleGrantedAuthority(permission))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.appsmith.server.helpers;
|
||||
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.services.AclEntity;
|
||||
|
||||
public class AclHelper {
|
||||
|
||||
private static final String ARN_PREFIX = "arn:appsmith:";
|
||||
|
||||
public static final String createArn(AclEntity aclEntity, User principal, String id) {
|
||||
StringBuilder arnBuilder = new StringBuilder(ARN_PREFIX)
|
||||
.append(principal.getCurrentOrganizationId())
|
||||
.append(":").append(aclEntity.value());
|
||||
arnBuilder = (id != null) ? arnBuilder.append(":").append(id) : arnBuilder.append(":*");
|
||||
|
||||
return arnBuilder.toString();
|
||||
}
|
||||
|
||||
public static final String concatenatePermissionWithArn(String permission, String arn) {
|
||||
return permission + "::" + arn;
|
||||
}
|
||||
|
||||
public static final String extractArnFromString(String arn) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,6 @@ import java.util.Set;
|
|||
@Repository
|
||||
public interface ActionRepository extends BaseRepository<Action, String> {
|
||||
|
||||
Mono<Action> findById(String id);
|
||||
|
||||
Mono<Action> findByNameAndPageId(String name, String pageId);
|
||||
|
||||
Flux<Action> findByPageId(String pageId);
|
||||
|
|
|
|||
|
|
@ -10,26 +10,9 @@ import org.springframework.stereotype.Repository;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@NoRepositoryBean
|
||||
@Repository
|
||||
@AclEntity("applications")
|
||||
public interface ApplicationRepository extends BaseRepository<Application, String> {
|
||||
|
||||
default Mono<Application> findByIdAndOrganizationId(String id, String orgId) {
|
||||
System.out.println("In the custom implementation");
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(ctx -> ctx.getAuthentication())
|
||||
.map(auth -> auth.getPrincipal())
|
||||
.flatMap(principal -> {
|
||||
System.out.println("Got principal: " + principal);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
public interface ApplicationRepository extends BaseRepository<Application, String>, CustomApplicationRepository {
|
||||
|
||||
Mono<Application> findByName(String name);
|
||||
|
||||
// @Override
|
||||
// Flux<Application> findAll(Example example);
|
||||
|
||||
@Override
|
||||
Mono<Application> findById(String id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package com.appsmith.server.repositories;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
@Component
|
||||
public class ApplicationRepositoryImpl extends BaseRepositoryImpl<Application, String> implements ApplicationRepository {
|
||||
|
||||
// public ApplicationRepositoryImpl(@NonNull MongoEntityInformation<Application, String> entityInformation,
|
||||
// @NonNull ReactiveMongoOperations mongoOperations) {
|
||||
// super(entityInformation, mongoOperations);
|
||||
// }
|
||||
|
||||
// TODO: Not implemented yet
|
||||
@Override
|
||||
public Mono<Application> findByIdAndOrganizationId(String id, String orgId) {
|
||||
Query query = new Query();
|
||||
return Mono.empty() ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Application> findByName(String name) {
|
||||
Query query = new Query();
|
||||
query.addCriteria(notDeleted());
|
||||
|
||||
Annotation[] annotations = entityInformation.getJavaType().getAnnotations();
|
||||
return mongoOperations.query(entityInformation.getJavaType())
|
||||
.inCollection(entityInformation.getCollectionName())
|
||||
.matching(query)
|
||||
.one();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.appsmith.server.repositories;
|
||||
|
||||
import com.appsmith.server.domains.Application;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface CustomApplicationRepository {
|
||||
Mono<Application> findByIdAndOrganizationId(String id, String orgId);
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.appsmith.server.repositories;
|
||||
|
||||
import com.appsmith.server.constants.Entity;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.services.AclEntity;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
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.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
@Component
|
||||
public class CustomApplicationRepositoryImpl implements CustomApplicationRepository {
|
||||
|
||||
private final ReactiveMongoOperations mongoOperations;
|
||||
private final ReactiveMongoTemplate mongoTemplate;
|
||||
|
||||
@Autowired
|
||||
public CustomApplicationRepositoryImpl(@NonNull ReactiveMongoOperations mongoOperations,
|
||||
ReactiveMongoTemplate mongoTemplate) {
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
protected Criteria notDeleted() {
|
||||
return new Criteria().orOperator(
|
||||
where("deleted").exists(false),
|
||||
where("deleted").is(false)
|
||||
);
|
||||
}
|
||||
|
||||
protected Criteria userAcl(User user, String permission) {
|
||||
Set<String> flatPermissions = user.getFlatPermissions();
|
||||
String entity = Entity.APPLICATIONS;
|
||||
// flatPermissions.stream()
|
||||
// .filter(flatPerm -> )
|
||||
return new Criteria().orOperator(
|
||||
where("acl.users").all(user.getUsername()),
|
||||
where("acl.groups").all(user.getGroupIds())
|
||||
);
|
||||
}
|
||||
|
||||
protected Criteria getIdCriteria(Object id) {
|
||||
return where("id").is(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Application> findByIdAndOrganizationId(String id, String orgId) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(ctx -> ctx.getAuthentication())
|
||||
.flatMap(auth -> {
|
||||
User user = (User) auth.getPrincipal();
|
||||
Query query = new Query(getIdCriteria(id));
|
||||
query.addCriteria(where("organizationId").is(orgId));
|
||||
query.addCriteria(new Criteria().andOperator(notDeleted(), userAcl(user, "read")));
|
||||
|
||||
return mongoOperations.query(Application.class)
|
||||
.matching(query)
|
||||
.one();
|
||||
});
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public Mono<Application> findByName(String name) {
|
||||
// Query query = new Query();
|
||||
// return Mono.empty();
|
||||
// }
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.appsmith.server.services;
|
|||
|
||||
import com.appsmith.external.models.BaseDomain;
|
||||
import com.appsmith.server.constants.AclConstants;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
@ -17,6 +18,7 @@ public interface CrudService<T extends BaseDomain, ID> {
|
|||
@AclPermission(values = AclConstants.UPDATE_PERMISSION)
|
||||
Mono<T> update(ID id, T resource);
|
||||
|
||||
@PreAuthorize("hasPermission(#this.this, 'read')")
|
||||
@AclPermission(values = AclConstants.READ_PERMISSION)
|
||||
Mono<T> getById(ID id);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user