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:
trishaanand 2020-07-09 11:21:39 +05:30 committed by GitHub
parent 53b61b4942
commit 9eedb15620
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 183 additions and 12 deletions

View File

@ -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);

View File

@ -48,4 +48,5 @@ public class FieldName {
" \"leftColumn\": 0,\n" +
" \"children\": []\n" +
"}";
public static String ANONYMOUS_USER = "anonymousUser";
}

View File

@ -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());
}

View File

@ -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);
});
}
}

View File

@ -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();
}
}