chore: Impose permission on Git operations (#27954)

## Description
This PR checks whether user has permission to create and application in
a workspace before doing git operations - Git connect and disconnect
from Git.

#### PR fixes following issue(s)
Fixes #26878
This commit is contained in:
Nayan 2023-10-20 10:59:22 +06:00 committed by GitHub
parent 41ee6473a8
commit 17eae14dfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 1 deletions

View File

@ -18,6 +18,7 @@ import com.appsmith.server.repositories.GitDeployKeysRepository;
import com.appsmith.server.services.ce_compatible.GitServiceCECompatibleImpl; import com.appsmith.server.services.ce_compatible.GitServiceCECompatibleImpl;
import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.WorkspacePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -48,6 +49,7 @@ public class GitServiceImpl extends GitServiceCECompatibleImpl implements GitSer
PluginService pluginService, PluginService pluginService,
DatasourcePermission datasourcePermission, DatasourcePermission datasourcePermission,
ApplicationPermission applicationPermission, ApplicationPermission applicationPermission,
WorkspacePermission workspacePermission,
WorkspaceService workspaceService, WorkspaceService workspaceService,
RedisUtils redisUtils, RedisUtils redisUtils,
ObservationRegistry observationRegistry, ObservationRegistry observationRegistry,
@ -73,6 +75,7 @@ public class GitServiceImpl extends GitServiceCECompatibleImpl implements GitSer
pluginService, pluginService,
datasourcePermission, datasourcePermission,
applicationPermission, applicationPermission,
workspacePermission,
workspaceService, workspaceService,
redisUtils, redisUtils,
observationRegistry, observationRegistry,

View File

@ -61,6 +61,7 @@ import com.appsmith.server.services.UserService;
import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.WorkspacePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -144,6 +145,7 @@ public class GitServiceCEImpl implements GitServiceCE {
private final PluginService pluginService; private final PluginService pluginService;
private final DatasourcePermission datasourcePermission; private final DatasourcePermission datasourcePermission;
private final ApplicationPermission applicationPermission; private final ApplicationPermission applicationPermission;
private final WorkspacePermission workspacePermission;
private final WorkspaceService workspaceService; private final WorkspaceService workspaceService;
private final RedisUtils redisUtils; private final RedisUtils redisUtils;
private final ObservationRegistry observationRegistry; private final ObservationRegistry observationRegistry;
@ -744,7 +746,15 @@ public class GitServiceCEImpl implements GitServiceCE {
GitUtils.isRepoPrivate(browserSupportedUrl).cache(); GitUtils.isRepoPrivate(browserSupportedUrl).cache();
Mono<Application> connectApplicationMono = profileMono Mono<Application> connectApplicationMono = profileMono
.then(getApplicationById(defaultApplicationId).zipWith(isPrivateRepoMono)) .then(getApplicationById(defaultApplicationId))
.flatMap(application ->
// Check if the user has permission to create app on the workspace, if yes then proceed
checkPermissionOnWorkspace(
application.getWorkspaceId(),
workspacePermission.getApplicationCreatePermission(),
"Connect to Git")
.thenReturn(application))
.zipWith(isPrivateRepoMono)
.flatMap(tuple -> { .flatMap(tuple -> {
Application application = tuple.getT1(); Application application = tuple.getT1();
boolean isRepoPrivate = tuple.getT2(); boolean isRepoPrivate = tuple.getT2();
@ -1158,6 +1168,11 @@ public class GitServiceCEImpl implements GitServiceCE {
public Mono<Application> detachRemote(String defaultApplicationId) { public Mono<Application> detachRemote(String defaultApplicationId) {
Mono<Application> disconnectMono = getApplicationById(defaultApplicationId) Mono<Application> disconnectMono = getApplicationById(defaultApplicationId)
.flatMap(application -> checkPermissionOnWorkspace(
application.getWorkspaceId(),
workspacePermission.getApplicationCreatePermission(),
"Disconnect from Git")
.thenReturn(application))
.flatMap(defaultApplication -> { .flatMap(defaultApplication -> {
if (Optional.ofNullable(defaultApplication.getGitApplicationMetadata()) if (Optional.ofNullable(defaultApplication.getGitApplicationMetadata())
.isEmpty() .isEmpty()
@ -1578,6 +1593,14 @@ public class GitServiceCEImpl implements GitServiceCE {
AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId))); AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION_ID, applicationId)));
} }
protected Mono<Workspace> checkPermissionOnWorkspace(
String workspaceId, AclPermission aclPermission, String operationName) {
return workspaceService
.findById(workspaceId, aclPermission)
.switchIfEmpty(
Mono.error(new AppsmithException(AppsmithError.ACTION_IS_NOT_AUTHORIZED, operationName)));
}
/** /**
* Method to pull application json files from remote repo, make a commit with the changes present in local DB and * Method to pull application json files from remote repo, make a commit with the changes present in local DB and
* make a system commit to remote repo * make a system commit to remote repo

View File

@ -24,6 +24,7 @@ import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.services.ce.GitServiceCEImpl; import com.appsmith.server.services.ce.GitServiceCEImpl;
import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.DatasourcePermission;
import com.appsmith.server.solutions.WorkspacePermission;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -51,6 +52,7 @@ public class GitServiceCECompatibleImpl extends GitServiceCEImpl implements GitS
PluginService pluginService, PluginService pluginService,
DatasourcePermission datasourcePermission, DatasourcePermission datasourcePermission,
ApplicationPermission applicationPermission, ApplicationPermission applicationPermission,
WorkspacePermission workspacePermission,
WorkspaceService workspaceService, WorkspaceService workspaceService,
RedisUtils redisUtils, RedisUtils redisUtils,
ObservationRegistry observationRegistry, ObservationRegistry observationRegistry,
@ -76,6 +78,7 @@ public class GitServiceCECompatibleImpl extends GitServiceCEImpl implements GitS
pluginService, pluginService,
datasourcePermission, datasourcePermission,
applicationPermission, applicationPermission,
workspacePermission,
workspaceService, workspaceService,
redisUtils, redisUtils,
observationRegistry, observationRegistry,

View File

@ -12,6 +12,7 @@ import com.appsmith.external.models.DatasourceStorageDTO;
import com.appsmith.external.models.DefaultResources; import com.appsmith.external.models.DefaultResources;
import com.appsmith.external.models.JSValue; import com.appsmith.external.models.JSValue;
import com.appsmith.external.models.PluginType; import com.appsmith.external.models.PluginType;
import com.appsmith.external.models.Policy;
import com.appsmith.server.actioncollections.base.ActionCollectionService; import com.appsmith.server.actioncollections.base.ActionCollectionService;
import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.FieldName;
import com.appsmith.server.datasources.base.DatasourceService; import com.appsmith.server.datasources.base.DatasourceService;
@ -59,6 +60,7 @@ import com.appsmith.server.services.UserService;
import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.services.WorkspaceService;
import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.ApplicationPermission;
import com.appsmith.server.solutions.EnvironmentPermission; import com.appsmith.server.solutions.EnvironmentPermission;
import com.appsmith.server.solutions.WorkspacePermission;
import com.appsmith.server.themes.base.ThemeService; import com.appsmith.server.themes.base.ThemeService;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
@ -109,6 +111,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties;
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
@ -206,6 +209,9 @@ public class GitServiceCETest {
@Autowired @Autowired
ApplicationPermission applicationPermission; ApplicationPermission applicationPermission;
@Autowired
WorkspacePermission workspacePermission;
@BeforeEach @BeforeEach
public void setup() throws IOException, GitAPIException { public void setup() throws IOException, GitAPIException {
@ -4099,4 +4105,61 @@ public class GitServiceCETest {
}) })
.verifyComplete(); .verifyComplete();
} }
/**
* This method creates an workspace, creates an application in the workspace and removes the
* create application permission from the workspace for the api_user.
* @return Created Application
*/
private Application createApplicationAndRemoveCreateAppPermissionFromWorkspace() {
User apiUser = userService.findByEmail("api_user").block();
Workspace toCreate = new Workspace();
toCreate.setName("Workspace_" + UUID.randomUUID());
Workspace workspace =
workspaceService.create(toCreate, apiUser, Boolean.FALSE).block();
Application testApplication = new Application();
testApplication.setWorkspaceId(workspace.getId());
testApplication.setName("Test App");
Application application1 =
applicationPageService.createApplication(testApplication).block();
// remove create application permission from the workspace for the api user
Set<Policy> existingPolicies = workspace.getPolicies();
Set<Policy> newPoliciesWithoutExport = existingPolicies.stream()
.filter(policy -> !policy.getPermission()
.equals(workspacePermission
.getApplicationCreatePermission()
.getValue()))
.collect(Collectors.toSet());
workspace.setPolicies(newPoliciesWithoutExport);
workspaceRepository.save(workspace).block();
return application1;
}
@WithUserDetails("api_user")
@Test
public void ConnectApplicationToGit_WhenUserDoesNotHaveRequiredPermission_OperationFails() {
Application application = createApplicationAndRemoveCreateAppPermissionFromWorkspace();
GitConnectDTO gitConnectDTO = getConnectRequest("git@github.com:test/testRepo.git", testUserProfile);
Mono<Application> applicationMono =
gitService.connectApplicationToGit(application.getId(), gitConnectDTO, "baseUrl");
StepVerifier.create(applicationMono)
.expectErrorMessage(AppsmithError.ACTION_IS_NOT_AUTHORIZED.getMessage("Connect to Git"))
.verify();
}
@WithUserDetails("api_user")
@Test
public void detachRemote_WhenUserDoesNotHaveRequiredPermission_OperationFails() {
Application application = createApplicationAndRemoveCreateAppPermissionFromWorkspace();
Mono<Application> applicationMono = gitService.detachRemote(application.getId());
StepVerifier.create(applicationMono)
.expectErrorMessage(AppsmithError.ACTION_IS_NOT_AUTHORIZED.getMessage("Disconnect from Git"))
.verify();
}
} }