diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java index 0052e0dec3..04fa782436 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java @@ -2,6 +2,7 @@ package com.appsmith.server.acl; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Page; import com.appsmith.server.domains.User; @@ -49,7 +50,11 @@ public enum AclPermission { MANAGE_ACTIONS("manage:actions", Action.class), READ_ACTIONS("read:actions", Action.class), - EXECUTE_ACTIONS("execute:actions", Action.class); + EXECUTE_ACTIONS("execute:actions", Action.class), + + MANAGE_DATASOURCES("manage:datasources", Datasource.class), + READ_DATASOURCES("read:datasources", Datasource.class), + EXECUTE_DATASOURCES("execute:datasources", Datasource.class); private String value; private Class entity; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java index 8a1375105d..34b46d27d9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/PolicyGenerator.java @@ -17,8 +17,10 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; +import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; import static com.appsmith.server.acl.AclPermission.MANAGE_USERS; @@ -28,6 +30,7 @@ import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIO import static com.appsmith.server.acl.AclPermission.PUBLISH_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES; import static com.appsmith.server.acl.AclPermission.READ_ORGANIZATIONS; import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static com.appsmith.server.acl.AclPermission.READ_USERS; @@ -63,11 +66,10 @@ public class PolicyGenerator { createUserPolicyGraph(); createOrganizationPolicyGraph(); + createDatasourcePolicyGraph(); createApplicationPolicyGraph(); createPagePolicyGraph(); createActionPolicyGraph(); - - log.debug("Successfully created the createGraph & lateralGraph"); } /** @@ -89,6 +91,15 @@ public class PolicyGenerator { lateralGraph.addEdge(MANAGE_ORGANIZATIONS, ORGANIZATION_PUBLISH_APPLICATIONS); } + private void createDatasourcePolicyGraph() { + hierarchyGraph.addEdge(ORGANIZATION_MANAGE_APPLICATIONS, MANAGE_DATASOURCES); + hierarchyGraph.addEdge(ORGANIZATION_READ_APPLICATIONS, READ_DATASOURCES); + + lateralGraph.addEdge(MANAGE_DATASOURCES, READ_DATASOURCES); + lateralGraph.addEdge(MANAGE_DATASOURCES, EXECUTE_DATASOURCES); + lateralGraph.addEdge(READ_DATASOURCES, EXECUTE_DATASOURCES); + } + private void createApplicationPolicyGraph() { hierarchyGraph.addEdge(ORGANIZATION_MANAGE_APPLICATIONS, MANAGE_APPLICATIONS); hierarchyGraph.addEdge(ORGANIZATION_READ_APPLICATIONS, READ_APPLICATIONS); @@ -131,17 +142,21 @@ public class PolicyGenerator { * @param policy * @param aclPermission * @param user + * @param destinationEntity * @return */ - public Set getChildPolicies(Policy policy, AclPermission aclPermission, User user) { + public Set getChildPolicies(Policy policy, AclPermission aclPermission, User user, Class destinationEntity) { // Check the hierarchy graph to derive child permissions that must be given to this // document Set childPolicySet = new HashSet<>(); Set edges = hierarchyGraph.outgoingEdgesOf(aclPermission); for (DefaultEdge edge : edges) { AclPermission childPermission = hierarchyGraph.getEdgeTarget(edge); - childPolicySet.add(Policy.builder().permission(childPermission.getValue()) - .users(policy.getUsers()).build()); + + if (childPermission.getEntity().equals(destinationEntity)) { + childPolicySet.add(Policy.builder().permission(childPermission.getValue()) + .users(policy.getUsers()).build()); + } // Get the lateral permissions that must be applied given the child permission // This is applied at a user level and not from the parent object. Hence only the @@ -152,13 +167,13 @@ public class PolicyGenerator { return childPolicySet; } - public Set getAllChildPolicies(User user, Set policySet, Class entity) { + public Set getAllChildPolicies(User user, Set policySet, Class inheritingEntity, Class destinationEntity) { return policySet.stream() .map(policy -> { AclPermission aclPermission = AclPermission - .getPermissionByValue(policy.getPermission(), entity); + .getPermissionByValue(policy.getPermission(), inheritingEntity); // Get all the child policies for the given policy and aclPermission - return getChildPolicies(policy, aclPermission, user); + return getChildPolicies(policy, aclPermission, user, destinationEntity); }).flatMap(Collection::stream) .collect(Collectors.toSet()); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java index 052dbcd59c..0c8ee91772 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java @@ -5,15 +5,16 @@ import com.appsmith.server.constants.Url; import com.appsmith.server.domains.Datasource; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.DatasourceService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; +@Slf4j @RestController @RequestMapping(Url.DATASOURCE_URL) public class DatasourceController extends BaseController { @@ -25,23 +26,8 @@ public class DatasourceController extends BaseController> testDatasource(@RequestBody Datasource datasource) { - Mono datasourceMono; - - if (datasource.getId() != null) { - datasourceMono = service.getById(datasource.getId()); - } else { - datasourceMono = Mono.just(datasource); - } - - return datasourceMono - .flatMap(service::validateDatasource) - .flatMap(datasource1 -> { - if (CollectionUtils.isEmpty(datasource1.getInvalids())) { - return service.testDatasource(datasource1); - } else { - return Mono.just(new DatasourceTestResult(datasource1.getInvalids())); - } - }) + log.debug("Going to test the datasource with name: {} and id: {}", datasource.getName(), datasource.getId()); + return service.testDatasource(datasource) .map(testResult -> new ResponseDTO<>(HttpStatus.OK.value(), testResult, null)); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java index b70676d0bf..8cc2b01a2c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/RestApiImportController.java @@ -45,6 +45,7 @@ public class RestApiImportController { @RequestParam RestApiImporterType type, @RequestParam String pageId, @RequestParam String name, + @RequestParam String organizationId, @RequestHeader(name = "Origin", required = false) String originHeader ) { log.debug("Going to import API"); @@ -58,7 +59,7 @@ public class RestApiImportController { throw new IllegalStateException("Unexpected value: " + type); } - return service.importAction(input, pageId, name) + return service.importAction(input, pageId, name, organizationId) .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java index 1b38c21bcb..7d04873210 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java @@ -6,11 +6,13 @@ import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.PolicyGenerator; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Page; import com.appsmith.server.domains.User; import com.appsmith.server.repositories.ActionRepository; import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.PageRepository; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; @@ -37,15 +39,18 @@ public class PolicyUtils { private final ApplicationRepository applicationRepository; private final PageRepository pageRepository; private final ActionRepository actionRepository; + private final DatasourceRepository datasourceRepository; public PolicyUtils(PolicyGenerator policyGenerator, ApplicationRepository applicationRepository, PageRepository pageRepository, - ActionRepository actionRepository) { + ActionRepository actionRepository, + DatasourceRepository datasourceRepository) { this.policyGenerator = policyGenerator; this.applicationRepository = applicationRepository; this.pageRepository = pageRepository; this.actionRepository = actionRepository; + this.datasourceRepository = datasourceRepository; } public T addPoliciesToExistingObject(Map policyMap, T obj) { @@ -112,18 +117,35 @@ public class PolicyUtils { .collect(Collectors.toMap(Policy::getPermission, Function.identity())); } - public Map generateApplicationPoliciesFromOrganizationPolicies(Map orgPolicyMap, User user) { + public Map generateChildrenPoliciesFromOrganizationPolicies(Map orgPolicyMap, User user, Class destinationEntity) { Set extractedInterestingPolicySet = new HashSet<>(orgPolicyMap.values()) .stream() .filter(policy -> policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) || policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue())) .collect(Collectors.toSet()); - return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Organization.class) + return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Organization.class, destinationEntity) .stream() .collect(Collectors.toMap(Policy::getPermission, Function.identity())); } + public Flux updateWithNewPoliciesToDatasourcesByOrgId(String orgId, Map newPoliciesMap, boolean addPolicyToObject) { + + return datasourceRepository + .findAllByOrganizationId(orgId, AclPermission.MANAGE_DATASOURCES) + // In case we have come across a datasource for this organization that the current user is not allowed to manage, move on. + .switchIfEmpty(Mono.empty()) + .map(datasource -> { + if (addPolicyToObject) { + return (Datasource) addPoliciesToExistingObject(newPoliciesMap, (T) datasource); + } else { + return (Datasource) removePoliciesFromExistingObject(newPoliciesMap, (T) datasource); + } + }) + .collectList() + .flatMapMany(updatedDatasources -> datasourceRepository.saveAll(updatedDatasources)); + } + public Flux updateWithNewPoliciesToApplicationsByOrgId(String orgId, Map newAppPoliciesMap, boolean addPolicyToObject) { return applicationRepository @@ -148,7 +170,7 @@ public class PolicyUtils { || policy.getPermission().equals(READ_APPLICATIONS.getValue())) .collect(Collectors.toSet()); - return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Application.class) + return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Application.class, Page.class) .stream() .collect(Collectors.toMap(Policy::getPermission, Function.identity())); } @@ -176,7 +198,7 @@ public class PolicyUtils { || policy.getPermission().equals(READ_PAGES.getValue())) .collect(Collectors.toSet()); - return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Page.class) + return policyGenerator.getAllChildPolicies(user, extractedInterestingPolicySet, Page.class, Action.class) .stream() .collect(Collectors.toMap(Policy::getPermission, Function.identity())); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepository.java index c38d7c056a..6eb5385e48 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepository.java @@ -1,6 +1,15 @@ package com.appsmith.server.repositories; +import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.Datasource; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; public interface CustomDatasourceRepository extends AppsmithRepository { + Flux findAllByOrganizationId(String organizationId, AclPermission permission); + + Mono findByName(String name, AclPermission aclPermission); + + Mono findById(String id, AclPermission aclPermission); + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepositoryImpl.java index d63fd8274e..1f4f5d08d9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomDatasourceRepositoryImpl.java @@ -1,9 +1,18 @@ package com.appsmith.server.repositories; +import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.Datasource; +import com.appsmith.server.domains.QDatasource; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +import static org.springframework.data.mongodb.core.query.Criteria.where; @Component public class CustomDatasourceRepositoryImpl extends BaseAppsmithRepositoryImpl implements CustomDatasourceRepository { @@ -11,4 +20,16 @@ public class CustomDatasourceRepositoryImpl extends BaseAppsmithRepositoryImpl findAllByOrganizationId(String organizationId, AclPermission permission) { + Criteria orgIdCriteria = where(fieldName(QDatasource.datasource.organizationId)).is(organizationId); + return queryAll(List.of(orgIdCriteria), permission); + } + + @Override + public Mono findByName(String name, AclPermission aclPermission) { + Criteria nameCriteria = where(fieldName(QDatasource.datasource.name)).is(name); + return queryOne(List.of(nameCriteria), aclPermission); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java index d8bc9366c9..1230739207 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java @@ -11,8 +11,6 @@ public interface CustomOrganizationRepository extends AppsmithRepository findByName(String name, AclPermission aclPermission); - Mono findByIdAndPluginsPluginId(String organizationId, String pluginId, AclPermission aclPermission); - Flux findByIdsIn(Set orgIds, AclPermission aclPermission); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java index f5864cf75e..5ef102c097 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java @@ -32,14 +32,6 @@ public class CustomOrganizationRepositoryImpl extends BaseAppsmithRepositoryImpl return queryOne(List.of(nameCriteria), aclPermission); } - @Override - public Mono findByIdAndPluginsPluginId(String organizationId, String pluginId, AclPermission aclPermission) { - Criteria idCriteria = where(fieldName(QOrganization.organization.id)).is(organizationId); - Criteria pluginIdCriteria = where(fieldName(QOrganization.organization.plugins.any().pluginId)).is(pluginId); - - return queryOne(List.of(idCriteria, pluginIdCriteria), aclPermission); - } - @Override public Flux findByIdsIn(Set orgIds, AclPermission aclPermission) { Criteria orgIdsCriteria = where(fieldName(QOrganization.organization.id)).in(orgIds); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/DatasourceRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/DatasourceRepository.java index a00fc49e19..5bfaeb7109 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/DatasourceRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/DatasourceRepository.java @@ -4,16 +4,10 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Datasource; import org.springframework.data.mongodb.repository.Query; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Repository public interface DatasourceRepository extends BaseRepository, CustomDatasourceRepository { - Mono findByName(String name); - - Mono findByIdAndOrganizationId(String id, String organizationId); - - Flux findAllByOrganizationId(String organizationId); @Query(value = "{" + FieldName.NAME + ": {$regex: ?0}}", count = true) Mono countNamesByPrefix(String namePrefix); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java index c69f82c639..8114a37f61 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java @@ -13,5 +13,7 @@ public interface OrganizationRepository extends BaseRepository countSlugsByPrefix(String keyword); + Mono findByIdAndPluginsPluginId(String organizationId, String pluginId); + Mono findByName(String name); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java index 4e6e33d73a..51b4e05edd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionServiceImpl.java @@ -60,7 +60,10 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; +import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; +import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES; import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static com.appsmith.server.helpers.MustacheHelper.extractMustacheKeysFromJson; @@ -132,7 +135,7 @@ public class ActionServiceImpl extends BaseService { + .flatMap(tuple -> { Page page = tuple.getT1(); User user = tuple.getT2(); @@ -140,12 +143,12 @@ public class ActionServiceImpl extends BaseService { action.setIsValid(false); invalids.add(AppsmithError.NO_RESOURCE_FOUND.getMessage(FieldName.DATASOURCE, action.getDatasource().getId())); @@ -317,7 +320,7 @@ public class ActionServiceImpl extends BaseService { action.setPluginId(datasource1.getPluginId()); return action; @@ -635,7 +638,7 @@ public class ActionServiceImpl extends BaseService policy.getPermission().equals(MANAGE_PAGES.getValue()) || policy.getPermission().equals(READ_PAGES.getValue())) .collect(Collectors.toSet()); - Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Page.class); + Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Page.class, Action.class); action.setPolicies(documentPolicies); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java index a176fcecdc..997b3603dc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java @@ -65,9 +65,6 @@ public class AnalyticsService { HashMap analyticsProperties = new HashMap<>(); analyticsProperties.put("id", ((BaseDomain) object).getId()); analyticsProperties.put("object", object.toString()); - if (user.getCurrentOrganizationId() != null) { - analyticsProperties.put("organizationId", user.getCurrentOrganizationId()); - } analytics.enqueue( TrackMessage.builder(eventTag) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiImporter.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiImporter.java index 6360252990..96ab7ce542 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiImporter.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiImporter.java @@ -5,6 +5,6 @@ import reactor.core.publisher.Mono; public interface ApiImporter { - Mono importAction(Object input, String pageId, String name); + Mono importAction(Object input, String pageId, String name, String orgId); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java index 2dab0eb628..29b6c80d7c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java @@ -235,7 +235,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService { policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue()) ).collect(Collectors.toSet()); - Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Organization.class); + Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Organization.class, Application.class); application.setPolicies(documentPolicies); return application; }); @@ -269,7 +269,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService { .filter(policy -> policy.getPermission().equals(MANAGE_APPLICATIONS.getValue()) || policy.getPermission().equals(READ_APPLICATIONS.getValue())) .collect(Collectors.toSet()); - Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Application.class); + Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Application.class, Page.class); page.setPolicies(documentPolicies); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseApiImporter.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseApiImporter.java index 85397eea27..7f7cce0e81 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseApiImporter.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseApiImporter.java @@ -5,6 +5,6 @@ import reactor.core.publisher.Mono; public abstract class BaseApiImporter implements ApiImporter { - public abstract Mono importAction(Object input, String pageId, String name); + public abstract Mono importAction(Object input, String pageId, String name, String orgId); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java index 1ea6b6b059..7a56963e80 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java @@ -47,7 +47,7 @@ public class CurlImporterService extends BaseApiImporter { } @Override - public Mono importAction(Object input, String pageId, String name) { + public Mono importAction(Object input, String pageId, String name, String orgId) { Action action = curlToAction((String) input, pageId, name); if (action == null) { @@ -62,6 +62,7 @@ public class CurlImporterService extends BaseApiImporter { final DatasourceConfiguration datasourceConfiguration = datasource.getDatasourceConfiguration(); datasource.setName(datasourceConfiguration.getUrl()); datasource.setPluginId(plugin.getId()); + datasource.setOrganizationId(orgId); return Mono.just(action); }) .flatMap(actionService::create); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java index be82f86e4a..eeaaf62e2a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java @@ -13,6 +13,8 @@ import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; +import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; + @Service @Slf4j public class DatasourceContextServiceImpl implements DatasourceContextService { @@ -47,7 +49,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService { Mono datasourceMono; if (datasource.getId() != null) { - datasourceMono = datasourceService.findById(datasourceId); + datasourceMono = datasourceService.findById(datasourceId, EXECUTE_DATASOURCES); } else { datasourceMono = Mono.just(datasource); } @@ -94,7 +96,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService { return Mono.empty(); } - Mono datasourceMono = datasourceService.findById(datasourceId); + Mono datasourceMono = datasourceService.findById(datasourceId, EXECUTE_DATASOURCES); Mono pluginMono = datasourceMono .flatMap(datasource -> pluginService.findById(datasource.getPluginId())); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceService.java index afbea1a8d4..cf8dd96962 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceService.java @@ -1,6 +1,7 @@ package com.appsmith.server.services; import com.appsmith.external.models.DatasourceTestResult; +import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.Datasource; import reactor.core.publisher.Mono; @@ -12,7 +13,7 @@ public interface DatasourceService extends CrudService { Mono findByName(String name); - Mono findById(String id); + Mono findById(String id, AclPermission aclPermission); Set extractKeysFromDatasource(Datasource datasource); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java index d6a36007a9..babdbf7b29 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java @@ -2,7 +2,10 @@ package com.appsmith.server.services; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceTestResult; +import com.appsmith.external.models.Policy; import com.appsmith.external.plugins.PluginExecutor; +import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.acl.PolicyGenerator; import com.appsmith.server.constants.AnalyticsEvents; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Datasource; @@ -18,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; @@ -28,7 +32,10 @@ import javax.validation.Validator; import javax.validation.constraints.NotNull; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; +import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIONS; import static com.appsmith.server.helpers.BeanCopyUtils.copyNestedNonNullProperties; import static com.appsmith.server.helpers.MustacheHelper.extractMustacheKeysFromJson; @@ -42,6 +49,7 @@ public class DatasourceServiceImpl extends BaseService datasourceMono = Mono.just(datasource); @@ -81,7 +93,30 @@ public class DatasourceServiceImpl extends BaseService + sessionUserService.getCurrentUser() + .flatMap(user -> { + // Create policies for this datasource -> This datasource should inherit its permissions and policies from + // the organization and this datasource should also allow the current user to crud this datasource. + return organizationService.findById(datasource1.getOrganizationId(), AclPermission.ORGANIZATION_MANAGE_APPLICATIONS) + .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ORGANIZATION, datasource1.getOrganizationId()))) + .map(org -> { + Set policySet = org.getPolicies().stream() + .filter(policy -> + policy.getPermission().equals(ORGANIZATION_MANAGE_APPLICATIONS.getValue()) || + policy.getPermission().equals(ORGANIZATION_READ_APPLICATIONS.getValue()) + ).collect(Collectors.toSet()); + + Set documentPolicies = policyGenerator.getAllChildPolicies(user, policySet, Organization.class, Datasource.class); + datasource1.setPolicies(documentPolicies); + return datasource1; + }); + }) + ) + .flatMap(this::validateAndSaveDatasourceToRepository); } @Override @@ -114,39 +149,20 @@ public class DatasourceServiceImpl extends BaseService currentOrganizationMono = sessionUserService - .getCurrentUser() - .map(user -> user.getCurrentOrganizationId()) - .cache(); - if (datasource.getPluginId() == null) { invalids.add(AppsmithError.PLUGIN_ID_NOT_GIVEN.getMessage()); datasource.setInvalids(invalids); - return currentOrganizationMono - .map(currentOrgId -> { - datasource.setOrganizationId(currentOrgId); - return datasource; - }); + return Mono.just(datasource); } - Mono checkPluginInstallationAndThenReturnOrganizationMono = currentOrganizationMono - .flatMap(currentOrgId -> organizationService.findByIdAndPluginsPluginId( - currentOrgId, datasource.getPluginId())) + Mono checkPluginInstallationAndThenReturnOrganizationMono = organizationService + .findByIdAndPluginsPluginId(datasource.getOrganizationId(), datasource.getPluginId()) .switchIfEmpty(Mono.defer(() -> { invalids.add(AppsmithError.PLUGIN_NOT_INSTALLED.getMessage(datasource.getPluginId())); datasource.setInvalids(invalids); return Mono.just(new Organization()); })); - //Add organization id to the datasource. - Mono updatedDatasourceMono = checkPluginInstallationAndThenReturnOrganizationMono - .map(organization -> { - if (organization.getId() != null) { - datasource.setOrganizationId(organization.getId()); - } - return datasource; - }); - if (datasource.getDatasourceConfiguration() == null) { invalids.add(AppsmithError.NO_CONFIGURATION_FOUND_IN_DATASOURCE.getMessage()); } @@ -154,18 +170,16 @@ public class DatasourceServiceImpl extends BaseService pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginService.findById(datasource.getPluginId())) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PLUGIN, datasource.getPluginId()))); - return Mono.zip(updatedDatasourceMono, pluginExecutorMono) - .flatMap(tuple -> { - Datasource datasource1 = tuple.getT1(); - PluginExecutor pluginExecutor = tuple.getT2(); - - DatasourceConfiguration datasourceConfiguration = datasource1.getDatasourceConfiguration(); + return checkPluginInstallationAndThenReturnOrganizationMono + .then(pluginExecutorMono) + .flatMap(pluginExecutor -> { + DatasourceConfiguration datasourceConfiguration = datasource.getDatasourceConfiguration(); if (datasourceConfiguration != null && !pluginExecutor.isDatasourceValid(datasourceConfiguration)) { invalids.addAll(pluginExecutor.validateDatasource(datasourceConfiguration)); } - datasource1.setInvalids(invalids); - return Mono.just(datasource1); + datasource.setInvalids(invalids); + return Mono.just(datasource); }); } @@ -177,6 +191,26 @@ public class DatasourceServiceImpl extends BaseService testDatasource(Datasource datasource) { + Mono datasourceMono; + + if (datasource.getId() != null) { + datasourceMono = getById(datasource.getId()); + } else { + datasourceMono = Mono.just(datasource); + } + + return datasourceMono + .flatMap(this::validateDatasource) + .flatMap(datasource1 -> { + if (CollectionUtils.isEmpty(datasource1.getInvalids())) { + return testDatasourceViaPlugin(datasource1); + } else { + return Mono.just(new DatasourceTestResult(datasource1.getInvalids())); + } + }); + } + + private Mono testDatasourceViaPlugin(Datasource datasource) { Mono pluginExecutorMono = pluginExecutorHelper.getPluginExecutor(pluginService.findById(datasource.getPluginId())) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PLUGIN, datasource.getPluginId()))); @@ -186,12 +220,12 @@ public class DatasourceServiceImpl extends BaseService findByName(String name) { - return repository.findByName(name); + return repository.findByName(name, AclPermission.READ_DATASOURCES); } @Override - public Mono findById(String id) { - return repository.findById(id); + public Mono findById(String id, AclPermission aclPermission) { + return repository.findById(id, aclPermission); } @Override @@ -214,19 +248,7 @@ public class DatasourceServiceImpl extends BaseService repository.findAllByOrganizationId(user.getCurrentOrganizationId())); - } - - @Override - public Mono getById(String id) { - if (id == null) { - return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID)); - } - - return sessionUserService - .getCurrentUser() - .flatMap(user -> repository.findByIdAndOrganizationId(id, user.getCurrentOrganizationId())) - .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.DATASOURCE, id))); + .flatMapMany(user -> repository.findAllByOrganizationId(user.getCurrentOrganizationId(), AclPermission.READ_DATASOURCES)); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java index 75852a9a80..47c30b4cb1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java @@ -1,6 +1,5 @@ package com.appsmith.server.services; -import com.appsmith.external.models.Policy; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.acl.PolicyGenerator; @@ -101,15 +100,6 @@ public class OrganizationServiceImpl extends BaseService initialSlug + (max == 0 ? "" : (max + 1))); } - private Set crudOrgPolicy(User user) { - Set policySet = user.getPolicies().stream() - .filter(policy -> - policy.getPermission().equals(USER_MANAGE_ORGANIZATIONS.getValue()) - ).collect(Collectors.toSet()); - - return policyGenerator.getAllChildPolicies(user, policySet, User.class); - } - /** * This function does the following: * 1. Creates the organization for the user @@ -258,7 +248,7 @@ public class OrganizationServiceImpl extends BaseService findByIdAndPluginsPluginId(String organizationId, String pluginId) { - return repository.findByIdAndPluginsPluginId(organizationId, pluginId, AclPermission.READ_ORGANIZATIONS); + return repository.findByIdAndPluginsPluginId(organizationId, pluginId); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PostmanImporterService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PostmanImporterService.java index 223eb28662..5ef592cf1e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PostmanImporterService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PostmanImporterService.java @@ -19,7 +19,7 @@ import java.util.List; @Slf4j public class PostmanImporterService extends BaseApiImporter { @Override - public Mono importAction(Object input, String pageId, String name) { + public Mono importAction(Object input, String pageId, String name, String orgId) { Action action = new Action(); ActionConfiguration actionConfiguration = new ActionConfiguration(); Datasource datasource = new Datasource(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java index 9f9dbe5e1b..322e209962 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserOrganizationServiceImpl.java @@ -6,6 +6,7 @@ import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Page; import com.appsmith.server.domains.User; @@ -135,7 +136,8 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { // Generate all the policies for Organization, Application, Page and Actions Set rolePermissions = role.getPermissions(); Map orgPolicyMap = policyUtils.generatePolicyFromPermission(rolePermissions, user); - Map applicationPolicyMap = policyUtils.generateApplicationPoliciesFromOrganizationPolicies(orgPolicyMap, user); + Map applicationPolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, user, Application.class); + Map datasourcePolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, user, Datasource.class); Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap, user); Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap, user); @@ -144,16 +146,17 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { updatedOrganization.setUserRoles(userRoles); // Update the underlying application/page/action + Flux updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, true); Flux updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, true); Flux updatedPagesFlux = updatedApplicationsFlux .flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, true)); Flux updatedActionsFlux = updatedPagesFlux .flatMap(page -> policyUtils.updateWithPagePermissionsToAllItsActions(page.getId(), actionPolicyMap, true)); - return Mono.zip(updatedActionsFlux.collectList(), Mono.just(updatedOrganization)) + return Mono.zip(updatedDatasourcesFlux.collectList(), updatedActionsFlux.collectList(), Mono.just(updatedOrganization)) .flatMap(tuple -> { - //By now all the applications/pages/actions have been updated. Just save the organization now - Organization updatedOrgBeforeSave = tuple.getT2(); + //By now all the datasources/applications/pages/actions have been updated. Just save the organization now + Organization updatedOrgBeforeSave = tuple.getT3(); return organizationRepository.save(updatedOrgBeforeSave); }); } @@ -198,7 +201,8 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { // Generate all the policies for Organization, Application, Page and Actions Set rolePermissions = role.getPermissions(); Map orgPolicyMap = policyUtils.generatePolicyFromPermission(rolePermissions, user); - Map applicationPolicyMap = policyUtils.generateApplicationPoliciesFromOrganizationPolicies(orgPolicyMap, user); + Map applicationPolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, user, Application.class); + Map datasourcePolicyMap = policyUtils.generateChildrenPoliciesFromOrganizationPolicies(orgPolicyMap, user, Datasource.class); Map pagePolicyMap = policyUtils.generatePagePoliciesFromApplicationPolicies(applicationPolicyMap, user); Map actionPolicyMap = policyUtils.generateActionPoliciesFromPagePolicies(pagePolicyMap, user); @@ -207,16 +211,17 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { updatedOrganization.setUserRoles(userRoles); // Update the underlying application/page/action + Flux updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, false); Flux updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, false); Flux updatedPagesFlux = updatedApplicationsFlux .flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, false)); Flux updatedActionsFlux = updatedPagesFlux .flatMap(page -> policyUtils.updateWithPagePermissionsToAllItsActions(page.getId(), actionPolicyMap, false)); - return Mono.zip(updatedActionsFlux.collectList(), Mono.just(updatedOrganization)) + return Mono.zip(updatedDatasourcesFlux.collectList(), updatedActionsFlux.collectList(), Mono.just(updatedOrganization)) .flatMap(tuple -> { - //By now all the applications/pages/actions have been updated. Just save the organization now - Organization updatedOrgBeforeSave = tuple.getT2(); + //By now all the datasources/applications/pages/actions have been updated. Just save the organization now + Organization updatedOrgBeforeSave = tuple.getT3(); return organizationRepository.save(updatedOrgBeforeSave); }); } @@ -229,15 +234,29 @@ public class UserOrganizationServiceImpl implements UserOrganizationService { Mono organizationMono = organizationRepository.findById(orgId, MANAGE_ORGANIZATIONS); Mono userMono = userRepository.findByEmail(userRole.getUsername()); + Mono currentUserMono = sessionUserService.getCurrentUser(); - return organizationMono.zipWith(userMono) + return Mono.zip(organizationMono, userMono, currentUserMono) .flatMap(tuple -> { Organization organization = tuple.getT1(); User user = tuple.getT2(); + User currentUser = tuple.getT3(); + + if (user.getUsername().equals(currentUser.getUsername())) { + // The user is trying to change its own role. Disallow the same. + return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION)); + } + List userRoles = organization.getUserRoles(); for (UserRole role : userRoles) { if (role.getUsername().equals(userRole.getUsername())) { // User found in the organization. + + if (role.getRoleName().equals(userRole.getRoleName())) { + // No change in the role. Do nothing. + Mono.just(userRole); + } + // Step 1. Remove the existing role of the user from the organization Mono userRemovedOrganizationMono = this.removeUserRoleFromOrganization(organization.getId(), userRole); // Step 2. Add the new role (if present) to the organization for the user diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java index 3faa26569e..02b121e12e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java @@ -2,21 +2,29 @@ package com.appsmith.server.services; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.Policy; +import com.appsmith.server.acl.AclPermission; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Page; +import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.helpers.PluginExecutorHelper; +import com.appsmith.server.repositories.OrganizationRepository; +import com.appsmith.server.repositories.PluginRepository; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpMethod; import org.springframework.security.test.context.support.WithUserDetails; import org.springframework.test.annotation.DirtiesContext; @@ -51,12 +59,23 @@ public class ActionServiceTest { @Autowired OrganizationService organizationService; + @Autowired + OrganizationRepository organizationRepository; + + @Autowired + PluginRepository pluginRepository; + + @MockBean + PluginExecutorHelper pluginExecutorHelper; + Application testApp = null; Page testPage = null; int i = 0; + Datasource datasource; + @Before @WithUserDetails(value = "api_user") public void setup() { @@ -73,6 +92,14 @@ public class ActionServiceTest { testApp = applicationPageService.createApplication(application, organization.getId()).block(); testPage = pageService.getById(testApp.getPages().get(0).getId()).block(); } + + Organization testOrg = organizationRepository.findByName("Another Test Organization", AclPermission.READ_ORGANIZATIONS).block(); + orgId = testOrg.getId(); + datasource = new Datasource(); + datasource.setName("Default Database"); + datasource.setOrganizationId(orgId); + Plugin installed_plugin = pluginRepository.findByPackageName("installed-plugin").block(); + datasource.setPluginId(installed_plugin.getId()); } @After @@ -87,6 +114,8 @@ public class ActionServiceTest { @Test @WithUserDetails(value = "api_user") public void createValidActionAndCheckPermissions() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new DatasourceServiceTest.TestPluginExecutor())); + Policy manageActionPolicy = Policy.builder().permission(MANAGE_ACTIONS.getValue()) .users(Set.of("api_user")) .build(); @@ -100,6 +129,7 @@ public class ActionServiceTest { ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); Mono actionMono = actionService.create(action); @@ -117,12 +147,15 @@ public class ActionServiceTest { @Test @WithUserDetails(value = "api_user") public void createValidActionWithJustName() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new DatasourceServiceTest.TestPluginExecutor())); + Action action = new Action(); action.setName("randomActionName"); action.setPageId(testPage.getId()); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); Mono actionMono = Mono.just(action) .flatMap(actionService::create); StepVerifier @@ -130,8 +163,7 @@ public class ActionServiceTest { .assertNext(createdAction -> { assertThat(createdAction.getId()).isNotEmpty(); assertThat(createdAction.getName()).isEqualTo(action.getName()); - assertThat(createdAction.getIsValid()).isFalse(); - assertThat(createdAction.getInvalids().size()).isEqualTo(1); + assertThat(createdAction.getIsValid()).isTrue(); }) .verifyComplete(); } @@ -139,9 +171,12 @@ public class ActionServiceTest { @Test @WithUserDetails(value = "api_user") public void createValidActionNullActionConfiguration() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new DatasourceServiceTest.TestPluginExecutor())); + Action action = new Action(); action.setName("randomActionName2"); action.setPageId(testPage.getId()); + action.setDatasource(datasource); Mono actionMono = Mono.just(action) .flatMap(actionService::create); StepVerifier @@ -163,6 +198,7 @@ public class ActionServiceTest { ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); Mono actionMono = Mono.just(action) .flatMap(actionService::create); StepVerifier diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java index 7bdbd83028..ab34e42957 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java @@ -112,7 +112,7 @@ public class CurlImporterServiceTest { Page page = pageService.findById(application.getPages().get(0).getId(), AclPermission.MANAGE_PAGES).block(); String command = "curl -X GET http://localhost:8080/api/v1/actions?name=something -H 'Accept: */*' -H 'Accept-Encoding: gzip, deflate' -H 'Authorization: Basic YXBpX3VzZXI6OHVBQDsmbUI6Y252Tn57Iw==' -H 'Cache-Control: no-cache' -H 'Connection: keep-alive' -H 'Content-Type: application/json' -H 'Cookie: SESSION=97c5def4-4f72-45aa-96fe-e8a9f5ade0b5,SESSION=97c5def4-4f72-45aa-96fe-e8a9f5ade0b5; SESSION=' -H 'Host: localhost:8080' -H 'Postman-Token: 16e4b6bc-2c7a-4ab1-a127-bca382dfc0f0,a6655daa-db07-4c5e-aca3-3fd505bd230d' -H 'User-Agent: PostmanRuntime/7.20.1' -H 'cache-control: no-cache' -d '{someJson}'"; - Mono action = curlImporterService.importAction(command, page.getId(), "actionName"); + Mono action = curlImporterService.importAction(command, page.getId(), "actionName", orgId); StepVerifier .create(action) .assertNext(action1 -> { @@ -542,7 +542,7 @@ public class CurlImporterServiceTest { public void importInvalidCurlCommand() { String command = "invalid curl command here"; - Mono actionMono = curlImporterService.importAction(command, "pageId", "actionName"); + Mono actionMono = curlImporterService.importAction(command, "pageId", "actionName", orgId); StepVerifier .create(actionMono) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java index 0b6f66344d..670e9442e3 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java @@ -4,15 +4,19 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.Connection; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceTestResult; +import com.appsmith.external.models.Policy; import com.appsmith.external.models.SSLDetails; import com.appsmith.external.models.UploadedFile; import com.appsmith.external.plugins.PluginExecutor; +import com.appsmith.server.acl.AclPermission; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Datasource; +import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.Plugin; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.PluginExecutorHelper; +import com.appsmith.server.repositories.OrganizationRepository; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; @@ -31,6 +35,9 @@ import reactor.util.function.Tuple2; import java.util.HashSet; import java.util.Set; +import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; +import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; +import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @@ -45,10 +52,13 @@ public class DatasourceServiceTest { @Autowired PluginService pluginService; + @Autowired + OrganizationRepository organizationRepository; + @MockBean PluginExecutorHelper pluginExecutorHelper; - class TestPluginExecutor implements PluginExecutor { + static class TestPluginExecutor implements PluginExecutor { @Override public Mono execute(Object connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { @@ -80,9 +90,13 @@ public class DatasourceServiceTest { } } - @Before - public void setup() { + String orgId = ""; + @Before + @WithUserDetails(value = "api_user") + public void setup() { + Organization testOrg = organizationRepository.findByName("Another Test Organization", AclPermission.READ_ORGANIZATIONS).block(); + orgId = testOrg.getId(); } @Test @@ -90,6 +104,7 @@ public class DatasourceServiceTest { public void createDatasourceWithNullPluginId() { Datasource datasource = new Datasource(); datasource.setName("DS-with-null-pluginId"); + datasource.setOrganizationId(orgId); Mono datasourceMono = Mono.just(datasource) .flatMap(datasourceService::create); StepVerifier @@ -108,6 +123,7 @@ public class DatasourceServiceTest { public void createDatasourceWithId() { Datasource datasource = new Datasource(); datasource.setId("randomId"); + datasource.setOrganizationId(orgId); Mono datasourceMono = Mono.just(datasource) .flatMap(datasourceService::create); StepVerifier @@ -125,6 +141,7 @@ public class DatasourceServiceTest { Mono pluginMono = pluginService.findByName("Not Installed Plugin Name"); Datasource datasource = new Datasource(); datasource.setName("DS-with-uninstalled-plugin"); + datasource.setOrganizationId(orgId); DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setUrl("http://test.com"); datasource.setDatasourceConfiguration(datasourceConfiguration); @@ -155,6 +172,7 @@ public class DatasourceServiceTest { Mono pluginMono = pluginService.findByName("Installed Plugin Name"); Datasource datasource = new Datasource(); datasource.setName("test datasource name"); + datasource.setOrganizationId(orgId); DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasourceConfiguration.setUrl("http://test.com"); datasource.setDatasourceConfiguration(datasourceConfiguration); @@ -169,6 +187,18 @@ public class DatasourceServiceTest { assertThat(createdDatasource.getId()).isNotEmpty(); assertThat(createdDatasource.getPluginId()).isEqualTo(datasource.getPluginId()); assertThat(createdDatasource.getName()).isEqualTo(datasource.getName()); + Policy manageDatasourcePolicy = Policy.builder().permission(MANAGE_DATASOURCES.getValue()) + .users(Set.of("api_user")) + .build(); + Policy readDatasourcePolicy = Policy.builder().permission(READ_DATASOURCES.getValue()) + .users(Set.of("api_user")) + .build(); + Policy executeDatasourcePolicy = Policy.builder().permission(EXECUTE_DATASOURCES.getValue()) + .users(Set.of("api_user")) + .build(); + + assertThat(createdDatasource.getPolicies()).isNotEmpty(); + assertThat(createdDatasource.getPolicies()).containsAll(Set.of(manageDatasourcePolicy, readDatasourcePolicy, executeDatasourcePolicy)); }) .verifyComplete(); } @@ -180,6 +210,7 @@ public class DatasourceServiceTest { Datasource datasource = new Datasource(); datasource.setName("test db datasource"); + datasource.setOrganizationId(orgId); DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); Connection connection = new Connection(); connection.setMode(Connection.Mode.READ_ONLY); @@ -192,7 +223,7 @@ public class DatasourceServiceTest { datasourceConfiguration.setConnection(connection); datasource.setDatasourceConfiguration(datasourceConfiguration); - datasource.setOrganizationId("fixme-put-valid-org-id-here"); + datasource.setOrganizationId(orgId); Mono pluginMono = pluginService.findByName("Installed Plugin Name"); @@ -233,10 +264,12 @@ public class DatasourceServiceTest { Datasource datasource1 = new Datasource(); datasource1.setDatasourceConfiguration(new DatasourceConfiguration()); + datasource1.setOrganizationId(orgId); datasource1.getDatasourceConfiguration().setUrl("http://test.com"); Datasource datasource2 = new Datasource(); datasource2.setDatasourceConfiguration(new DatasourceConfiguration()); + datasource2.setOrganizationId(orgId); datasource2.getDatasourceConfiguration().setUrl("http://test.com"); final Mono> datasourcesMono = pluginMono @@ -262,4 +295,35 @@ public class DatasourceServiceTest { }) .verifyComplete(); } + + + @Test + @WithUserDetails(value = "api_user") + public void testDatasourceValid() { + + Mono pluginMono = pluginService.findByName("Installed Plugin Name"); + Datasource datasource = new Datasource(); + datasource.setName("test datasource name for test"); + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("http://test.com"); + datasource.setDatasourceConfiguration(datasourceConfiguration); + datasource.setOrganizationId(orgId); + + Mono datasourceMono = pluginMono.map(plugin -> { + datasource.setPluginId(plugin.getId()); + return datasource; + }).flatMap(datasourceService::create); + + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new TestPluginExecutor())); + + Mono testResultMono = datasourceMono.flatMap(datasource1 -> datasourceService.testDatasource(datasource1)); + + StepVerifier + .create(testResultMono) + .assertNext(testResult -> { + assertThat(testResult).isNotNull(); + assertThat(testResult.getInvalids()).isEmpty(); + }) + .verifyComplete(); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceTest.java index 641826b039..d6598a08d8 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceTest.java @@ -5,12 +5,14 @@ import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.User; import com.appsmith.server.domains.UserRole; import com.appsmith.server.dtos.InviteUserDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; +import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.OrganizationRepository; import lombok.extern.slf4j.Slf4j; import org.junit.Before; @@ -25,15 +27,19 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import reactor.util.function.Tuple2; +import reactor.util.function.Tuple3; import java.util.List; import java.util.Map; import java.util.Set; +import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS; import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS; +import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES; import static com.appsmith.server.acl.AclPermission.READ_ORGANIZATIONS; import static org.assertj.core.api.Assertions.assertThat; @@ -61,6 +67,12 @@ public class OrganizationServiceTest { @Autowired UserService userService; + @Autowired + DatasourceService datasourceService; + + @Autowired + DatasourceRepository datasourceRepository; + Organization organization; @Before @@ -386,13 +398,13 @@ public class OrganizationServiceTest { } /** - * This test checks for application permissions if a user is invited to the organization as an Admin. + * This test checks for application and datasource permissions if a user is invited to the organization as an Admin. * The existing applications in the organization should now have the new user be included in both * manage:applications and read:applications policies. */ @Test @WithUserDetails(value = "api_user") - public void addUserToOrganizationAsAdminAndCheckApplicationPermissions() { + public void addUserToOrganizationAsAdminAndCheckApplicationAndDatasourcePermissions() { Organization organization = new Organization(); organization.setName("Member Management Admin Test Organization"); organization.setDomain("example.com"); @@ -410,6 +422,15 @@ public class OrganizationServiceTest { return applicationPageService.createApplication(application, org.getId()); }); + // Create datasource for this organization + Mono datasourceMono = organizationMono + .flatMap(org -> { + Datasource datasource = new Datasource(); + datasource.setName("test datasource"); + datasource.setOrganizationId(org.getId()); + return datasourceService.create(datasource); + }); + Mono userAddedToOrgMono = organizationMono .flatMap(organization1 -> { // Add user to organization @@ -417,6 +438,10 @@ public class OrganizationServiceTest { userRole.setRoleName(AppsmithRole.ORGANIZATION_ADMIN.getName()); userRole.setUsername("usertest@usertest.com"); return userOrganizationService.addUserRoleToOrganization(organization1.getId(), userRole); + }) + .map(organization1 -> { + log.debug("Organization policies after adding user is : {}", organization1.getPolicies()); + return organization1; }); Mono readApplicationByNameMono = applicationService.findByName("User Management Admin Test Application", @@ -426,16 +451,23 @@ public class OrganizationServiceTest { Mono readOrganizationByNameMono = organizationRepository.findByName("Member Management Admin Test Organization") .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "organization by name"))); - Mono> testMono = organizationMono - .then(applicationMono) + Mono readDatasourceByNameMono = datasourceRepository.findByName("test datasource", READ_DATASOURCES) + .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "Datasource"))); + + Mono> testMono = organizationMono + // create application and datasource + .then(Mono.zip(applicationMono, datasourceMono)) + // Now add the user .then(userAddedToOrgMono) - .then(Mono.zip(readApplicationByNameMono, readOrganizationByNameMono)); + // Read application, organization and datasource now to confirm the policies. + .then(Mono.zip(readApplicationByNameMono, readOrganizationByNameMono, readDatasourceByNameMono)); StepVerifier .create(testMono) .assertNext(tuple -> { Application application = tuple.getT1(); Organization org = tuple.getT2(); + Datasource datasource = tuple.getT3(); assertThat(org).isNotNull(); assertThat(org.getUserRoles().get(1).getUsername()).isEqualTo("usertest@usertest.com"); @@ -449,6 +481,23 @@ public class OrganizationServiceTest { assertThat(application.getPolicies()).isNotEmpty(); assertThat(application.getPolicies()).containsAll(Set.of(manageAppPolicy, readAppPolicy)); + /* + * Check for datasource permissions after the user addition + */ + Policy manageDatasourcePolicy = Policy.builder().permission(MANAGE_DATASOURCES.getValue()) + .users(Set.of("api_user", "usertest@usertest.com")) + .build(); + Policy readDatasourcePolicy = Policy.builder().permission(READ_DATASOURCES.getValue()) + .users(Set.of("api_user", "usertest@usertest.com")) + .build(); + Policy executeDatasourcePolicy = Policy.builder().permission(EXECUTE_DATASOURCES.getValue()) + .users(Set.of("api_user", "usertest@usertest.com")) + .build(); + + assertThat(datasource.getPolicies()).isNotEmpty(); + assertThat(datasource.getPolicies()).containsAll(Set.of(manageDatasourcePolicy, readDatasourcePolicy, + executeDatasourcePolicy)); + }) .verifyComplete(); }