Updating anonymous user permissions for application and associated pages & actions when public access is modified (#57)
* On setting an application to public view, correct permissions are assigned to the application and its pages & actions. * If anonymous user is allowed a certain permission, the all users (anonymous/logged in) should be allowed the certain permission.
This commit is contained in:
parent
53b61b4942
commit
9eedb15620
|
|
@ -4,6 +4,7 @@ package com.appsmith.server.configurations;
|
|||
import com.appsmith.server.authentication.handlers.AccessDeniedHandler;
|
||||
import com.appsmith.server.authentication.handlers.CustomServerOAuth2AuthorizationRequestResolver;
|
||||
import com.appsmith.server.authentication.handlers.LogoutSuccessHandler;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.constants.Url;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.services.UserService;
|
||||
|
|
@ -161,8 +162,8 @@ public class SecurityConfig {
|
|||
|
||||
private User createAnonymousUser() {
|
||||
User user = new User();
|
||||
user.setName("anonymousUser");
|
||||
user.setEmail("anonymousUser");
|
||||
user.setName(FieldName.ANONYMOUS_USER);
|
||||
user.setEmail(FieldName.ANONYMOUS_USER);
|
||||
user.setCurrentOrganizationId("");
|
||||
user.setOrganizationIds(new HashSet<>());
|
||||
user.setIsAnonymous(true);
|
||||
|
|
|
|||
|
|
@ -48,4 +48,5 @@ public class FieldName {
|
|||
" \"leftColumn\": 0,\n" +
|
||||
" \"children\": []\n" +
|
||||
"}";
|
||||
public static String ANONYMOUS_USER = "anonymousUser";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,11 +67,16 @@ public abstract class BaseAppsmithRepositoryImpl<T extends BaseDomain> {
|
|||
.and("permission").is(permission.getValue())
|
||||
);
|
||||
|
||||
Criteria anonymousUserCriteria = Criteria.where(fieldName(QBaseDomain.baseDomain.policies))
|
||||
.elemMatch(Criteria.where("users").all(FieldName.ANONYMOUS_USER)
|
||||
.and("permission").is(permission.getValue())
|
||||
);
|
||||
|
||||
Criteria groupCriteria = Criteria.where(fieldName(QBaseDomain.baseDomain.policies))
|
||||
.elemMatch(Criteria.where("groups").all(user.getGroupIds())
|
||||
.and("permission").is(permission.getValue()));
|
||||
|
||||
return new Criteria().orOperator(userCriteria, groupCriteria);
|
||||
return new Criteria().orOperator(userCriteria, groupCriteria, anonymousUserCriteria);
|
||||
}
|
||||
|
||||
protected Criteria getIdCriteria(Object id) {
|
||||
|
|
@ -187,7 +192,8 @@ public abstract class BaseAppsmithRepositoryImpl<T extends BaseDomain> {
|
|||
Set<String> policyGroups = policy.getGroups();
|
||||
|
||||
|
||||
if (policyUsers != null && policyUsers.contains(user.getUsername())) {
|
||||
if (policyUsers != null &&
|
||||
(policyUsers.contains(user.getUsername()) || policyUsers.contains(FieldName.ANONYMOUS_USER))) {
|
||||
permissions.add(policy.getPermission());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.external.models.Policy;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Action;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.ApplicationPage;
|
||||
import com.appsmith.server.domains.Layout;
|
||||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
|
||||
import com.appsmith.server.dtos.UserHomepageDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.PolicyUtils;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.PageRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -26,7 +30,6 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.core.scheduler.Scheduler;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.text.Format;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -51,6 +54,7 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
private final SessionUserService sessionUserService;
|
||||
private final OrganizationService organizationService;
|
||||
private final UserService userService;
|
||||
private final PolicyUtils policyUtils;
|
||||
|
||||
@Autowired
|
||||
public ApplicationServiceImpl(Scheduler scheduler,
|
||||
|
|
@ -62,12 +66,14 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
PageRepository pageRepository,
|
||||
SessionUserService sessionUserService,
|
||||
OrganizationService organizationService,
|
||||
UserService userService) {
|
||||
UserService userService,
|
||||
PolicyUtils policyUtils) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.pageRepository = pageRepository;
|
||||
this.sessionUserService = sessionUserService;
|
||||
this.organizationService = organizationService;
|
||||
this.userService = userService;
|
||||
this.policyUtils = policyUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -241,8 +247,52 @@ public class ApplicationServiceImpl extends BaseService<ApplicationRepository, A
|
|||
.findById(id, MANAGE_APPLICATIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, id)))
|
||||
.flatMap(application -> {
|
||||
|
||||
if (application.getIsPublic().equals(applicationAccessDTO.getPublicAccess())) {
|
||||
// No change. The required public access is the same as current public access. Do nothing
|
||||
return Mono.just(application);
|
||||
}
|
||||
|
||||
if (application.getIsPublic() == null && applicationAccessDTO.getPublicAccess().equals(false)) {
|
||||
return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
|
||||
}
|
||||
|
||||
application.setIsPublic(applicationAccessDTO.getPublicAccess());
|
||||
return repository.save(application);
|
||||
return generateAndSetPoliciesForPublicView(application, applicationAccessDTO.getPublicAccess());
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Application> generateAndSetPoliciesForPublicView(Application application, Boolean isPublic) {
|
||||
AclPermission permission = READ_APPLICATIONS;
|
||||
|
||||
User user = new User();
|
||||
user.setName(FieldName.ANONYMOUS_USER);
|
||||
user.setEmail(FieldName.ANONYMOUS_USER);
|
||||
user.setIsAnonymous(true);
|
||||
|
||||
Map<String, Policy> applicationPolicyMap = policyUtils.generatePolicyFromPermission(Set.of(permission), user);
|
||||
Map<String, Policy> pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap, user);
|
||||
Map<String, Policy> actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap, user);
|
||||
|
||||
Flux<Page> updatedPagesFlux = policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, isPublic);
|
||||
|
||||
Flux<Action> updatedActionsFlux = updatedPagesFlux
|
||||
.flatMap(page -> policyUtils.updateWithPagePermissionsToAllItsActions(page.getId(), actionPolicyMap, isPublic));
|
||||
|
||||
return updatedActionsFlux
|
||||
.collectList()
|
||||
.thenReturn(application)
|
||||
.flatMap(app -> {
|
||||
Application updatedApplication;
|
||||
|
||||
if (isPublic) {
|
||||
updatedApplication = (Application) policyUtils.addPoliciesToExistingObject(applicationPolicyMap, (Application) application);
|
||||
} else {
|
||||
updatedApplication = (Application) policyUtils.removePoliciesFromExistingObject(applicationPolicyMap, (Application) application);
|
||||
}
|
||||
|
||||
return repository.save(updatedApplication);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,12 @@ import com.appsmith.server.domains.Application;
|
|||
import com.appsmith.server.domains.Organization;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ApplicationAccessDTO;
|
||||
import com.appsmith.server.dtos.OrganizationApplicationsDTO;
|
||||
import com.appsmith.server.dtos.UserHomepageDTO;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.repositories.PageRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -55,6 +57,9 @@ public class ApplicationServiceTest {
|
|||
@Autowired
|
||||
OrganizationService organizationService;
|
||||
|
||||
@Autowired
|
||||
PageRepository pageRepository;
|
||||
|
||||
String orgId;
|
||||
|
||||
@Before
|
||||
|
|
@ -206,11 +211,14 @@ public class ApplicationServiceTest {
|
|||
.block();
|
||||
|
||||
assertThat(applicationList.size() > 0);
|
||||
applicationList.forEach(t -> {
|
||||
assertThat(t.getId()).isNotNull();
|
||||
assertThat(t.getPolicies()).isNotEmpty();
|
||||
assertThat(t.getPolicies()).containsAll(Set.of(readAppPolicy));
|
||||
});
|
||||
applicationList
|
||||
.stream()
|
||||
.filter(t -> t.getName().equals("validGetApplications-Test"))
|
||||
.forEach(t -> {
|
||||
assertThat(t.getId()).isNotNull();
|
||||
assertThat(t.getPolicies()).isNotEmpty();
|
||||
assertThat(t.getPolicies()).containsAll(Set.of(readAppPolicy));
|
||||
});
|
||||
}
|
||||
|
||||
/* Tests for Update Application Flow */
|
||||
|
|
@ -322,4 +330,109 @@ public class ApplicationServiceTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void validMakeApplicationPublic() {
|
||||
Application application = new Application();
|
||||
application.setName("validMakeApplicationPublic-Test");
|
||||
|
||||
Policy manageAppPolicy = Policy.builder().permission(MANAGE_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
Policy readAppPolicy = Policy.builder().permission(READ_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user", FieldName.ANONYMOUS_USER))
|
||||
.build();
|
||||
|
||||
Policy managePagePolicy = Policy.builder().permission(MANAGE_PAGES.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
Policy readPagePolicy = Policy.builder().permission(READ_PAGES.getValue())
|
||||
.users(Set.of("api_user", FieldName.ANONYMOUS_USER))
|
||||
.build();
|
||||
|
||||
Application createdApplication = applicationPageService.createApplication(application, orgId).block();
|
||||
|
||||
ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO();
|
||||
applicationAccessDTO.setPublicAccess(true);
|
||||
|
||||
Mono<Application> publicAppMono = applicationService
|
||||
.changeViewAccess(createdApplication.getId(), applicationAccessDTO)
|
||||
.cache();
|
||||
|
||||
Mono<Page> pageMono = publicAppMono
|
||||
.flatMap(app -> {
|
||||
String pageId = app.getPages().get(0).getId();
|
||||
return pageRepository.findById(pageId);
|
||||
});
|
||||
|
||||
StepVerifier
|
||||
.create(Mono.zip(publicAppMono, pageMono))
|
||||
.assertNext(tuple -> {
|
||||
Application publicApp = tuple.getT1();
|
||||
Page page = tuple.getT2();
|
||||
|
||||
assertThat(publicApp.getIsPublic()).isTrue();
|
||||
assertThat(publicApp.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy));
|
||||
|
||||
// Check the child page's policies
|
||||
assertThat(page.getPolicies()).containsAll(Set.of(managePagePolicy, readPagePolicy));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithUserDetails(value = "api_user")
|
||||
public void validMakeApplicationPrivate() {
|
||||
Application application = new Application();
|
||||
application.setName("validMakeApplicationPrivate-Test");
|
||||
|
||||
Policy manageAppPolicy = Policy.builder().permission(MANAGE_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
Policy readAppPolicy = Policy.builder().permission(READ_APPLICATIONS.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
|
||||
Policy managePagePolicy = Policy.builder().permission(MANAGE_PAGES.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
Policy readPagePolicy = Policy.builder().permission(READ_PAGES.getValue())
|
||||
.users(Set.of("api_user"))
|
||||
.build();
|
||||
|
||||
Mono<Application> createApplication = applicationPageService.createApplication(application, orgId);
|
||||
|
||||
ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO();
|
||||
Mono<Application> privateAppMono = createApplication
|
||||
.flatMap(application1 -> {
|
||||
applicationAccessDTO.setPublicAccess(true);
|
||||
return applicationService.changeViewAccess(application1.getId(), applicationAccessDTO);
|
||||
})
|
||||
.flatMap(application1 -> {
|
||||
applicationAccessDTO.setPublicAccess(false);
|
||||
return applicationService.changeViewAccess(application1.getId(), applicationAccessDTO);
|
||||
})
|
||||
.cache();
|
||||
|
||||
Mono<Page> pageMono = privateAppMono
|
||||
.flatMap(app -> {
|
||||
String pageId = app.getPages().get(0).getId();
|
||||
return pageRepository.findById(pageId);
|
||||
});
|
||||
|
||||
StepVerifier
|
||||
.create(Mono.zip(privateAppMono, pageMono))
|
||||
.assertNext(tuple -> {
|
||||
Application app = tuple.getT1();
|
||||
Page page = tuple.getT2();
|
||||
|
||||
assertThat(app.getIsPublic()).isFalse();
|
||||
assertThat(app.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy));
|
||||
|
||||
// Check the child page's policies
|
||||
assertThat(page.getPolicies()).containsAll(Set.of(managePagePolicy, readPagePolicy));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user