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 a814a1710a..f1341e71bc 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 @@ -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); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java index 3145cc8d74..a23f3a4911 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java @@ -48,4 +48,5 @@ public class FieldName { " \"leftColumn\": 0,\n" + " \"children\": []\n" + "}"; + public static String ANONYMOUS_USER = "anonymousUser"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java index a108278193..d4774137b9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java @@ -67,11 +67,16 @@ public abstract class BaseAppsmithRepositoryImpl { .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 { Set 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()); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index 9c6a3e5224..8c5b05d908 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java @@ -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 { + + 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 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 applicationPolicyMap = policyUtils.generatePolicyFromPermission(Set.of(permission), user); + Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap, user); + Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap, user); + + Flux updatedPagesFlux = policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, isPublic); + + Flux 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); + }); + + } } \ No newline at end of file diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java index 582aa2a45a..eb4ce4a006 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java @@ -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 publicAppMono = applicationService + .changeViewAccess(createdApplication.getId(), applicationAccessDTO) + .cache(); + + Mono 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 createApplication = applicationPageService.createApplication(application, orgId); + + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + Mono 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 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(); + } + }