From dc8d18cf871c9c54f2a7e10023469428bc19f983 Mon Sep 17 00:00:00 2001 From: Yiming Guo <49181899+GavinGYM@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:01:51 +0800 Subject: [PATCH] [Feature-12040][api][ui] Add authorization management of read and write permissions for project center (#12048) [Feature-12040][api][ui] Add authorization management of read and write permissions for project center --- .../api/controller/ProjectController.java | 57 +++++++ .../api/controller/UsersController.java | 48 ++++++ .../dolphinscheduler/api/enums/Status.java | 2 + .../ResourcePermissionCheckServiceImpl.java | 2 +- .../api/service/ProjectService.java | 24 +++ .../api/service/ResourcesService.java | 4 +- .../api/service/UsersService.java | 19 +++ .../impl/ProcessDefinitionServiceImpl.java | 28 ++-- .../api/service/impl/ProjectServiceImpl.java | 156 +++++++++++++++++- .../impl/TaskDefinitionServiceImpl.java | 33 ++-- .../api/service/impl/UsersServiceImpl.java | 97 ++++++++++- .../service/ProcessDefinitionServiceTest.java | 1 + .../api/service/ProjectServiceTest.java | 106 +++++++++++- .../TaskDefinitionServiceImplTest.java | 8 +- .../api/service/UsersServiceTest.java | 46 ++++++ .../dao/mapper/ProjectUserMapper.java | 2 +- .../src/common/column-width-config.ts | 3 + .../src/locales/en_US/project.ts | 6 +- .../src/locales/en_US/security.ts | 3 + .../src/locales/zh_CN/project.ts | 6 +- .../src/locales/zh_CN/security.ts | 3 + .../src/service/modules/projects/index.ts | 18 +- .../src/service/modules/projects/types.ts | 8 + .../src/service/modules/users/index.ts | 16 ++ .../components/authorize-modal.tsx | 94 +++++++++-- .../user-manage/components/use-authorize.ts | 91 +++++++--- .../user-manage/components/use-columns.ts | 91 ++++++++++ .../security/user-manage/index.module.scss | 5 + 28 files changed, 899 insertions(+), 78 deletions(-) create mode 100644 dolphinscheduler-ui/src/views/security/user-manage/components/use-columns.ts diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java index 1b3f2def46..dcfc774b4c 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java @@ -174,6 +174,43 @@ public class ProjectController extends BaseController { return result; } + /** + * query project with authorized level list paging + * + * @param userId user id + * @param loginUser login user + * @param searchVal search value + * @param pageSize page size + * @param pageNo page number + * @return project list which with the login user's authorized level + */ + @Operation(summary = "queryProjectWithAuthorizedLevelListPaging", description = "QUERY_PROJECT_WITH_AUTH_LEVEL_LIST_PAGING_NOTES") + @Parameters({ + @Parameter(name = "userId", description = "USER_ID", schema = @Schema(implementation = int.class, example = "100")), + @Parameter(name = "searchVal", description = "SEARCH_VAL", schema = @Schema(implementation = String.class)), + @Parameter(name = "pageSize", description = "PAGE_SIZE", required = true, schema = @Schema(implementation = int.class, example = "10")), + @Parameter(name = "pageNo", description = "PAGE_NO", required = true, schema = @Schema(implementation = int.class, example = "1")) + }) + @GetMapping(value = "/project-with-authorized-level-list-paging") + @ResponseStatus(HttpStatus.OK) + @ApiException(LOGIN_USER_QUERY_PROJECT_LIST_PAGING_ERROR) + @AccessLogAnnotation(ignoreRequestArgs = "loginUser") + public Result queryProjectWithAuthorizedLevelListPaging(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, + @RequestParam("userId") Integer userId, + @RequestParam(value = "searchVal", required = false) String searchVal, + @RequestParam("pageSize") Integer pageSize, + @RequestParam("pageNo") Integer pageNo) { + + Result result = checkPageParams(pageNo, pageSize); + if (!result.checkResult()) { + return result; + } + searchVal = ParameterUtils.handleEscapes(searchVal); + result = projectService.queryProjectWithAuthorizedLevelListPaging(userId, loginUser, pageSize, pageNo, + searchVal); + return result; + } + /** * delete project by code * @@ -234,6 +271,26 @@ public class ProjectController extends BaseController { return projectService.queryAuthorizedProject(loginUser, userId); } + /** + * query all project with authorized level + * + * @param loginUser login user + * @param userId user id + * @return All projects with users' authorized level for them + */ + @Operation(summary = "queryProjectWithAuthorizedLevel", description = "QUERY_PROJECT_AUTHORIZED_LEVEL") + @Parameters({ + @Parameter(name = "userId", description = "USER_ID", schema = @Schema(implementation = int.class, example = "100")) + }) + @GetMapping(value = "/project-with-authorized-level") + @ResponseStatus(HttpStatus.OK) + @ApiException(QUERY_AUTHORIZED_PROJECT) + @AccessLogAnnotation(ignoreRequestArgs = "loginUser") + public Result queryProjectWithAuthorizedLevel(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, + @RequestParam("userId") Integer userId) { + return projectService.queryProjectWithAuthorizedLevel(loginUser, userId); + } + /** * query authorized user * diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java index dcaf7a9b81..93c13b6f50 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java @@ -214,6 +214,54 @@ public class UsersController extends BaseController { return returnDataList(result); } + /** + * revoke project By Id + * + * @param loginUser login user + * @param userId user id + * @param projectIds project id array + * @return revoke result code + */ + @Operation(summary = "revokeProjectById", description = "REVOKE_PROJECT_NOTES") + @Parameters({ + @Parameter(name = "userId", description = "USER_ID", required = true, schema = @Schema(implementation = int.class, example = "100")), + @Parameter(name = "projectIds", description = "PROJECT_IDS", required = true, schema = @Schema(implementation = String.class)) + }) + @PostMapping(value = "/revoke-project-by-id") + @ResponseStatus(HttpStatus.OK) + @ApiException(REVOKE_PROJECT_ERROR) + @AccessLogAnnotation + public Result revokeProjectById(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, + @RequestParam(value = "userId") int userId, + @RequestParam(value = "projectIds") String projectIds) { + Map result = usersService.revokeProjectById(loginUser, userId, projectIds); + return returnDataList(result); + } + + /** + * grant project with read permission + * + * @param loginUser login user + * @param userId user id + * @param projectIds project id array + * @return grant result code + */ + @Operation(summary = "grantProjectWithReadPerm", description = "GRANT_PROJECT_WITH_READ_PERM_NOTES") + @Parameters({ + @Parameter(name = "userId", description = "USER_ID", required = true, schema = @Schema(implementation = int.class, example = "100")), + @Parameter(name = "projectIds", description = "PROJECT_IDS", required = true, schema = @Schema(implementation = String.class)) + }) + @PostMapping(value = "/grant-project-with-read-perm") + @ResponseStatus(HttpStatus.OK) + @ApiException(GRANT_PROJECT_ERROR) + @AccessLogAnnotation + public Result grantProjectWithReadPerm(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser, + @RequestParam(value = "userId") int userId, + @RequestParam(value = "projectIds") String projectIds) { + Map result = usersService.grantProjectWithReadPerm(loginUser, userId, projectIds); + return returnDataList(result); + } + /** * grant project * diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java index 2c67861426..f6d8257ec9 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java @@ -280,6 +280,8 @@ public enum Status { USER_NO_OPERATION_PERM(30001, "user has no operation privilege", "当前用户没有操作权限"), USER_NO_OPERATION_PROJECT_PERM(30002, "user {0} is not has project {1} permission", "当前用户[{0}]没有[{1}]项目的操作权限"), + USER_NO_WRITE_PROJECT_PERM(30003, "user [{0}] does not have write permission for project [{1}]", + "当前用户[{0}]没有[{1}]项目的写权限"), PROCESS_INSTANCE_NOT_EXIST(50001, "process instance {0} does not exist", "工作流实例[{0}]不存在"), PROCESS_INSTANCE_EXIST(50002, "process instance {0} already exists", "工作流实例[{0}]已存在"), diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java index 7b3da83bf5..bf3d48e4cf 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java @@ -242,7 +242,7 @@ public class ResourcePermissionCheckServiceImpl } List ownResourceList = resourceMapper.queryResourceListAuthored(userId, -1); relationResources.addAll(ownResourceList); - return ownResourceList.stream().map(Resource::getId).collect(toSet()); + return relationResources.stream().map(Resource::getId).collect(toSet()); } @Override diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectService.java index a0d7217766..43b8a04acf 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectService.java @@ -81,6 +81,10 @@ public interface ProjectService { */ boolean hasProjectAndPerm(User loginUser, Project project, Result result, String permission); + boolean hasProjectAndWritePerm(User loginUser, Project project, Result result); + + boolean hasProjectAndWritePerm(User loginUser, Project project, Map result); + /** * admin can view all projects * @@ -92,6 +96,19 @@ public interface ProjectService { */ Result queryProjectListPaging(User loginUser, Integer pageSize, Integer pageNo, String searchVal); + /** + * admin can view all projects + * + * @param userId user id + * @param loginUser login user + * @param searchVal search value + * @param pageSize page size + * @param pageNo page number + * @return project list which with the login user's authorized level + */ + Result queryProjectWithAuthorizedLevelListPaging(Integer userId, User loginUser, Integer pageSize, Integer pageNo, + String searchVal); + /** * delete project by code * @@ -131,6 +148,13 @@ public interface ProjectService { */ Result queryAuthorizedProject(User loginUser, Integer userId); + /** + * query all project with authorized level + * @param loginUser login user + * @return project list + */ + Result queryProjectWithAuthorizedLevel(User loginUser, Integer userId); + /** * query authorized user * diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java index 04f477e36b..c0c7a7d211 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java @@ -195,7 +195,9 @@ public interface ResourcesService { /** * updateProcessInstance resource * - * @param resourceId resource id + * @param loginUser login user + * @param fullName full name + * @param tenantCode tenantCode * @param content content * @return update result cod */ diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java index d854e9afdd..9abe60c72c 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java @@ -153,6 +153,16 @@ public interface UsersService { */ Map grantProject(User loginUser, int userId, String projectIds); + /** + * grant project with read permission + * + * @param loginUser login user + * @param userId user id + * @param projectIds project id array + * @return grant result code + */ + Map grantProjectWithReadPerm(User loginUser, int userId, String projectIds); + /** * grant project by code * @@ -163,6 +173,15 @@ public interface UsersService { */ Map grantProjectByCode(User loginUser, int userId, long projectCode); + /** + * revoke the project permission for specified user by id + * @param loginUser Login user + * @param userId User id + * @param projectIds project id array + * @return + */ + Map revokeProjectById(User loginUser, int userId, String projectIds); + /** * revoke the project permission for specified user. * @param loginUser Login user diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java index acb05b897e..fc01482236 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java @@ -261,10 +261,12 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro String otherParamsJson, ProcessExecutionTypeEnum executionType) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_CREATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } if (checkDescriptionLength(description)) { @@ -754,12 +756,14 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro String otherParamsJson, ProcessExecutionTypeEnum executionType) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_UPDATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } + if (checkDescriptionLength(description)) { logger.warn("Parameter description is too long."); putMsg(result, Status.DESCRIPTION_TOO_LONG_ERROR); @@ -2404,12 +2408,14 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro public Map deleteProcessDefinitionVersion(User loginUser, long projectCode, long code, int version) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, VERSION_DELETE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } + ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(code); if (processDefinition == null || projectCode != processDefinition.getProjectCode()) { @@ -2466,10 +2472,11 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro String scheduleJson, ProcessExecutionTypeEnum executionType) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_CREATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } if (checkDescriptionLength(description)) { @@ -2613,10 +2620,11 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro String otherParamsJson, ProcessExecutionTypeEnum executionType) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_UPDATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } if (checkDescriptionLength(description)) { diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectServiceImpl.java index 0ba236dd44..1353a30fed 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectServiceImpl.java @@ -20,7 +20,6 @@ package org.apache.dolphinscheduler.api.service.impl; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_CREATE; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_DELETE; -import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_UPDATE; import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.exceptions.ServiceException; @@ -256,6 +255,60 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic return checkResult; } + @Override + public boolean hasProjectAndWritePerm(User loginUser, Project project, Result result) { + boolean checkResult = false; + if (project == null) { + logger.error("Project does not exist."); + putMsg(result, Status.PROJECT_NOT_FOUND, ""); + } else { + // case 1: user is admin + if (loginUser.getUserType() == UserType.ADMIN_USER) { + return true; + } + // case 2: user is project owner + if (project.getUserId().equals(loginUser.getId())) { + return true; + } + // case 3: check user permission level + ProjectUser projectUser = projectUserMapper.queryProjectRelation(project.getId(), loginUser.getId()); + if (projectUser == null || projectUser.getPerm() != Constants.DEFAULT_ADMIN_PERMISSION) { + putMsg(result, Status.USER_NO_WRITE_PROJECT_PERM, loginUser.getUserName(), project.getCode()); + checkResult = false; + } else { + checkResult = true; + } + } + return checkResult; + } + + @Override + public boolean hasProjectAndWritePerm(User loginUser, Project project, Map result) { + boolean checkResult = false; + if (project == null) { + logger.error("Project does not exist."); + putMsg(result, Status.PROJECT_NOT_FOUND, ""); + } else { + // case 1: user is admin + if (loginUser.getUserType() == UserType.ADMIN_USER) { + return true; + } + // case 2: user is project owner + if (project.getUserId().equals(loginUser.getId())) { + return true; + } + // case 3: check user permission level + ProjectUser projectUser = projectUserMapper.queryProjectRelation(project.getId(), loginUser.getId()); + if (projectUser == null || projectUser.getPerm() != Constants.DEFAULT_ADMIN_PERMISSION) { + putMsg(result, Status.USER_NO_WRITE_PROJECT_PERM, loginUser.getUserName(), project.getCode()); + checkResult = false; + } else { + checkResult = true; + } + } + return checkResult; + } + @Override public boolean hasProjectAndPerm(User loginUser, Project project, Result result, String permission) { boolean checkResult = false; @@ -310,6 +363,57 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic return result; } + /** + * admin can view all projects + * + * @param userId user id + * @param loginUser login user + * @param searchVal search value + * @param pageSize page size + * @param pageNo page number + * @return project list which with the login user's authorized level + */ + @Override + public Result queryProjectWithAuthorizedLevelListPaging(Integer userId, User loginUser, Integer pageSize, + Integer pageNo, String searchVal) { + Result result = new Result(); + PageInfo pageInfo = new PageInfo<>(pageNo, pageSize); + Page page = new Page<>(pageNo, pageSize); + Set allProjectIds = resourcePermissionCheckService + .userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, loginUser.getId(), logger); + Set userProjectIds = resourcePermissionCheckService + .userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, userId, logger); + if (allProjectIds.isEmpty()) { + result.setData(pageInfo); + putMsg(result, Status.SUCCESS); + return result; + } + IPage projectIPage = + projectMapper.queryProjectListPaging(page, new ArrayList<>(allProjectIds), searchVal); + + List projectList = projectIPage.getRecords(); + + for (Project project : projectList) { + if (userProjectIds.contains(project.getId())) { + ProjectUser projectUser = projectUserMapper.queryProjectRelation(project.getId(), userId); + if (projectUser == null) { + // in this case, the user is the project owner, maybe it's better to set it to ALL_PERMISSION. + project.setPerm(Constants.DEFAULT_ADMIN_PERMISSION); + } else { + project.setPerm(projectUser.getPerm()); + } + } else { + project.setPerm(0); + } + } + + pageInfo.setTotal((int) projectIPage.getTotal()); + pageInfo.setTotalList(projectList); + result.setData(pageInfo); + putMsg(result, Status.SUCCESS); + return result; + } + /** * delete project by code * @@ -322,6 +426,11 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic Result result = new Result(); Project project = projectMapper.queryByCode(projectCode); + boolean hasProjectAndWritePerm = hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { + return result; + } + checkProjectAndAuth(result, loginUser, project, project == null ? 0L : project.getCode(), PROJECT_DELETE); if (result.getCode() != Status.SUCCESS.getCode()) { return result; @@ -386,8 +495,8 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic } Project project = projectMapper.queryByCode(projectCode); - boolean hasProjectAndPerm = hasProjectAndPerm(loginUser, project, result, PROJECT_UPDATE); - if (!hasProjectAndPerm) { + boolean hasProjectAndWritePerm = hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } Project tempProject = projectMapper.queryByName(projectName); @@ -417,6 +526,47 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic return result; } + /** + * query all project with authorized level + * @param loginUser login user + * @return project list + */ + @Override + public Result queryProjectWithAuthorizedLevel(User loginUser, Integer userId) { + Result result = new Result(); + + Set projectIds = resourcePermissionCheckService + .userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, loginUser.getId(), logger); + List projectList = projectMapper.listAuthorizedProjects( + loginUser.getUserType().equals(UserType.ADMIN_USER) ? 0 : loginUser.getId(), + new ArrayList<>(projectIds)); + + List unauthorizedProjectsList = new ArrayList<>(); + List authedProjectList = new ArrayList<>(); + Set projectSet; + if (projectList != null && !projectList.isEmpty()) { + projectSet = new HashSet<>(projectList); + authedProjectList = projectMapper.queryAuthedProjectListByUserId(userId); + unauthorizedProjectsList = getUnauthorizedProjects(projectSet, authedProjectList); + } + + for (int i = 0; i < authedProjectList.size(); i++) { + authedProjectList.get(i).setPerm(7); + } + + for (int i = 0; i < unauthorizedProjectsList.size(); i++) { + unauthorizedProjectsList.get(i).setPerm(0); + } + + List joined = new ArrayList<>(); + joined.addAll(authedProjectList); + joined.addAll(unauthorizedProjectsList); + + result.setData(joined); + putMsg(result, Status.SUCCESS); + return result; + } + /** * query unauthorized project * diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskDefinitionServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskDefinitionServiceImpl.java index c12cbb53f1..1dbc91ad91 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskDefinitionServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskDefinitionServiceImpl.java @@ -143,10 +143,11 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe long projectCode, String taskDefinitionJson) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_CREATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } @@ -285,10 +286,11 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe String taskDefinitionJsonObj, String upstreamCodes) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_CREATE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode); @@ -426,10 +428,16 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe /** * Whether task definition can be deleted or not */ - private void taskCanDeleteValid(User user, TaskDefinition taskDefinition) { + private void taskCanDeleteValid(User user, TaskDefinition taskDefinition, User loginUser) { // check user access for project Project project = projectMapper.queryByCode(taskDefinition.getProjectCode()); projectService.checkProjectAndAuthThrowException(user, project, TASK_DEFINITION_DELETE); + // check if user have write perm for project + Map result = new HashMap<>(); + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { + throw new ServiceException(Status.TASK_DEFINE_STATE_ONLINE, taskDefinition.getCode()); + } // Whether task relation workflow is online if (processService.isTaskOnline(taskDefinition.getCode()) && taskDefinition.getFlag() == Flag.YES) { @@ -466,7 +474,7 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe throw new ServiceException(Status.TASK_DEFINE_NOT_EXIST, taskCode); } - this.taskCanDeleteValid(loginUser, taskDefinition); + this.taskCanDeleteValid(loginUser, taskDefinition, loginUser); int delete = taskDefinitionMapper.deleteByCode(taskCode); if (delete <= 0) { throw new ServiceException(Status.DELETE_TASK_DEFINE_BY_CODE_MSG_ERROR, taskDefinition.getCode()); @@ -683,11 +691,13 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe private TaskDefinitionLog updateTask(User loginUser, long projectCode, long taskCode, String taskDefinitionJsonObj, Map result) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project - result.putAll(projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_UPDATE)); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + + // check if user have write perm for project + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return null; } + TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode); if (taskDefinition == null) { logger.error("Task definition does not exist, taskDefinitionCode:{}.", taskCode); @@ -943,10 +953,11 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe @Override public Map deleteByCodeAndVersion(User loginUser, long projectCode, long taskCode, int version) { Project project = projectMapper.queryByCode(projectCode); - // check user access for project + // check if user have write perm for project Map result = projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_DELETE); - if (result.get(Constants.STATUS) != Status.SUCCESS) { + boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result); + if (!hasProjectAndWritePerm) { return result; } TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode); diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java index 3b65634979..f200586fc6 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java +++ b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java @@ -533,6 +533,97 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { } } + /** + * revoke the project permission for specified user by id + * @param loginUser Login user + * @param userId User id + * @param projectIds project id array + * @return + */ + @Override + @Transactional(rollbackFor = RuntimeException.class) + public Map revokeProjectById(User loginUser, int userId, String projectIds) { + Map result = new HashMap<>(); + result.put(Constants.STATUS, false); + + if (resourcePermissionCheckService.functionDisabled()) { + putMsg(result, Status.FUNCTION_DISABLED); + return result; + } + // 1. only admin can operate + if (this.check(result, !this.isAdmin(loginUser), Status.USER_NO_OPERATION_PERM)) { + return result; + } + + // 2. check if user is existed + User user = this.userMapper.selectById(userId); + if (user == null) { + this.putMsg(result, Status.USER_NOT_EXIST, userId); + return result; + } + + Arrays.stream(projectIds.split(",")).distinct().forEach(projectId -> { + // 3. check if project is existed + Project project = this.projectMapper.queryDetailById(Integer.parseInt(projectId)); + if (project == null) { + this.putMsg(result, Status.PROJECT_NOT_FOUND, Integer.parseInt(projectId)); + } else { + // 4. delete the relationship between project and user + this.projectUserMapper.deleteProjectRelation(project.getId(), user.getId()); + } + }); + + this.putMsg(result, Status.SUCCESS); + return result; + } + + /** + * grant project with read permission + * + * @param loginUser login user + * @param userId user id + * @param projectIds project id array + * @return grant result code + */ + @Override + @Transactional(rollbackFor = RuntimeException.class) + public Map grantProjectWithReadPerm(User loginUser, int userId, String projectIds) { + Map result = new HashMap<>(); + result.put(Constants.STATUS, false); + + if (resourcePermissionCheckService.functionDisabled()) { + putMsg(result, Status.FUNCTION_DISABLED); + return result; + } + // check exist + User tempUser = userMapper.selectById(userId); + if (tempUser == null) { + putMsg(result, Status.USER_NOT_EXIST, userId); + return result; + } + + if (check(result, StringUtils.isEmpty(projectIds), Status.SUCCESS)) { + return result; + } + Arrays.stream(projectIds.split(Constants.COMMA)).distinct().forEach(projectId -> { + ProjectUser projectUserOld = projectUserMapper.queryProjectRelation(Integer.parseInt(projectId), userId); + if (projectUserOld != null) { + projectUserMapper.deleteProjectRelation(Integer.parseInt(projectId), userId); + } + Date now = new Date(); + ProjectUser projectUser = new ProjectUser(); + projectUser.setUserId(userId); + projectUser.setProjectId(Integer.parseInt(projectId)); + projectUser.setPerm(Constants.READ_PERMISSION); + projectUser.setCreateTime(now); + projectUser.setUpdateTime(now); + projectUserMapper.insert(projectUser); + }); + putMsg(result, Status.SUCCESS); + + return result; + } + /** * grant project * @@ -559,13 +650,15 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService { return result; } - projectUserMapper.deleteProjectRelation(0, userId); - if (check(result, StringUtils.isEmpty(projectIds), Status.SUCCESS)) { logger.warn("Parameter projectIds is empty."); return result; } Arrays.stream(projectIds.split(",")).distinct().forEach(projectId -> { + ProjectUser projectUserOld = projectUserMapper.queryProjectRelation(Integer.parseInt(projectId), userId); + if (projectUserOld != null) { + projectUserMapper.deleteProjectRelation(Integer.parseInt(projectId), userId); + } Date now = new Date(); ProjectUser projectUser = new ProjectUser(); projectUser.setUserId(userId); diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java index 31502b917e..de8a58f619 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java @@ -779,6 +779,7 @@ public class ProcessDefinitionServiceTest extends BaseServiceTestTool { Mockito.when(projectMapper.queryByCode(projectCode)).thenReturn(getProject(projectCode)); Mockito.when(projectService.checkProjectAndAuth(user, project, projectCode, WORKFLOW_UPDATE)) .thenReturn(result); + Mockito.when(projectService.hasProjectAndWritePerm(user, project, result)).thenReturn(true); try { processDefinitionService.updateProcessDefinition(user, projectCode, "test", 1, diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectServiceTest.java index 5ad77a3e87..f2b3d4d16b 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectServiceTest.java @@ -22,11 +22,14 @@ import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationCon import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_CREATE; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_DELETE; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_UPDATE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService; import org.apache.dolphinscheduler.api.service.impl.BaseServiceImpl; import org.apache.dolphinscheduler.api.service.impl.ProjectServiceImpl; +import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.enums.AuthorizationType; @@ -62,6 +65,9 @@ import org.mockito.quality.Strictness; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + /** * project service test **/ @@ -206,6 +212,66 @@ public class ProjectServiceTest { Assertions.assertTrue(checkResult); } + @Test + public void testQueryProjectWithAuthorizedLevelListPaging() { + IPage page = new Page<>(1, 10); + page.setTotal(1L); + page.setRecords(getList()); + + User loginUser = getLoginUser(); + Integer pageSize = 10; + Integer pageNo = 1; + String searchVal = "testVal"; + Result result = new Result(); + Mockito.when(projectMapper.queryProjectListPaging(any(Page.class), Mockito.anyList(), eq(searchVal))) + .thenReturn(page); + + Set allProjectIds = new HashSet(); + allProjectIds.add(1); + Mockito.when(resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, + loginUser.getId(), projectLogger)).thenReturn(allProjectIds); + + // SUCCESS + result = projectService.queryProjectWithAuthorizedLevelListPaging(loginUser.getId(), loginUser, pageSize, + pageNo, searchVal); + logger.info(result.toString()); + PageInfo pageInfo = (PageInfo) result.getData(); + Assertions.assertTrue(CollectionUtils.isNotEmpty(pageInfo.getTotalList())); + } + + @Test + public void testHasProjectAndWritePerm() { + + // Mockito.when(projectUserMapper.queryProjectRelation(1, 1)).thenReturn(getProjectUser()); + User loginUser = getLoginUser(); + Project project = getProject(); + Map result = new HashMap<>(); + // not exist user + User tempUser = new User(); + tempUser.setId(Integer.MAX_VALUE); + tempUser.setUserType(UserType.GENERAL_USER); + Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.PROJECTS, + new Object[]{project.getId()}, + tempUser.getId(), null, baseServiceLogger)).thenReturn(true); + boolean checkResult = projectService.hasProjectAndWritePerm(tempUser, project, result); + logger.info(result.toString()); + Assertions.assertFalse(checkResult); + + // success + result = new HashMap<>(); + project.setUserId(1); + loginUser.setUserType(UserType.ADMIN_USER); + Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.PROJECTS, + new Object[]{project.getId()}, + loginUser.getId(), null, baseServiceLogger)).thenReturn(true); + Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.PROJECTS, + new Object[]{project.getId()}, + 0, baseServiceLogger)).thenReturn(true); + checkResult = projectService.hasProjectAndWritePerm(loginUser, project, result); + logger.info(result.toString()); + Assertions.assertTrue(checkResult); + } + @Test public void testDeleteProject() { User loginUser = getLoginUser(); @@ -213,10 +279,10 @@ public class ProjectServiceTest { Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.PROJECTS, new Object[]{1}, loginUser.getId(), PROJECT_DELETE, baseServiceLogger)).thenReturn(true); - // PROJECT_NOT_FOUNT + // PROJECT_NOT_FOUND Result result = projectService.deleteProject(loginUser, 11L); logger.info(result.toString()); - Assertions.assertTrue(Status.PROJECT_NOT_EXIST.getCode() == result.getCode()); + Assertions.assertTrue(Status.PROJECT_NOT_FOUND.getCode() == result.getCode()); loginUser.setId(2); // USER_NO_OPERATION_PROJECT_PERM Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.PROJECTS, new Object[]{1}, @@ -224,7 +290,7 @@ public class ProjectServiceTest { baseServiceLogger)).thenReturn(true); result = projectService.deleteProject(loginUser, 1L); logger.info(result.toString()); - Assertions.assertTrue(Status.USER_NO_OPERATION_PROJECT_PERM.getCode() == result.getCode()); + Assertions.assertTrue(Status.USER_NO_WRITE_PROJECT_PERM.getCode() == result.getCode()); // DELETE_PROJECT_ERROR_DEFINES_NOT_NULL Mockito.when(processDefinitionMapper.queryAllDefinitionList(1L)).thenReturn(getProcessDefinitions()); @@ -418,6 +484,40 @@ public class ProjectServiceTest { } + @Test + public void testQueryProjectWithAuthorizedLevel() { + Set set = new HashSet(); + set.add(1); + // test admin user + User loginUser = new User(); + loginUser.setUserType(UserType.ADMIN_USER); + loginUser.setId(1); + List list = new ArrayList<>(1); + list.add(1); + Mockito.when(resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, + loginUser.getId(), projectLogger)).thenReturn(set); + Mockito.when(projectMapper.listAuthorizedProjects( + loginUser.getUserType().equals(UserType.ADMIN_USER) ? 0 : loginUser.getId(), list)) + .thenReturn(getList()); + Result result = projectService.queryProjectWithAuthorizedLevel(loginUser, 2); + logger.info(result.toString()); + List projects = (List) result.getData(); + Assertions.assertTrue(CollectionUtils.isNotEmpty(projects)); + + // test non-admin user + loginUser.setId(2); + loginUser.setUserType(UserType.GENERAL_USER); + Mockito.when(resourcePermissionCheckService.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, + loginUser.getId(), projectLogger)).thenReturn(set); + Mockito.when(projectMapper.listAuthorizedProjects( + loginUser.getUserType().equals(UserType.ADMIN_USER) ? 0 : loginUser.getId(), list)) + .thenReturn(getList()); + result = projectService.queryProjectWithAuthorizedLevel(loginUser, 3); + logger.info(result.toString()); + projects = (List) result.getData(); + Assertions.assertTrue(CollectionUtils.isNotEmpty(projects)); + } + @Test public void testQueryUnauthorizedProject() { Set set = new HashSet(); diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskDefinitionServiceImplTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskDefinitionServiceImplTest.java index 798367c422..0c271ace6f 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskDefinitionServiceImplTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskDefinitionServiceImplTest.java @@ -141,10 +141,6 @@ public class TaskDefinitionServiceImplTest { + "\\\\\\\"failedNode\\\\\\\":[\\\\\\\"\\\\\\\"]}\\\",\\\"dependence\\\":{}}\",\"flag\":0,\"taskPriority\":0," + "\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":0,\"timeoutFlag\":0," + "\"timeoutNotifyStrategy\":0,\"timeout\":0,\"delayTime\":0,\"resourceIds\":\"\"}]"; - List taskDefinitions = JSONUtils.toList(createTaskDefinitionJson, TaskDefinitionLog.class); - Mockito.when(processService.saveTaskDefine(user, PROJECT_CODE, taskDefinitions, Boolean.TRUE)) - .thenReturn(1); - Mockito.when(taskPluginManager.checkTaskParameters(Mockito.any())).thenReturn(true); Map relation = taskDefinitionService .createTaskDefinition(user, PROJECT_CODE, createTaskDefinitionJson); Assertions.assertEquals(Status.SUCCESS, relation.get(Constants.STATUS)); @@ -166,8 +162,7 @@ public class TaskDefinitionServiceImplTest { Map result = new HashMap<>(); putMsg(result, Status.SUCCESS, PROJECT_CODE); - Mockito.when(projectService.checkProjectAndAuth(user, project, PROJECT_CODE, TASK_DEFINITION_UPDATE)) - .thenReturn(result); + Mockito.when(projectService.hasProjectAndWritePerm(user, project, new HashMap<>())).thenReturn(true); Mockito.when(processService.isTaskOnline(TASK_CODE)).thenReturn(Boolean.FALSE); Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(new TaskDefinition()); @@ -212,6 +207,7 @@ public class TaskDefinitionServiceImplTest { // error delete single task definition object Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(getTaskDefinition()); Mockito.when(taskDefinitionMapper.deleteByCode(TASK_CODE)).thenReturn(0); + Mockito.when(projectService.hasProjectAndWritePerm(user, project, new HashMap<>())).thenReturn(true); exception = Assertions.assertThrows(ServiceException.class, () -> taskDefinitionService.deleteTaskDefinitionByCode(user, TASK_CODE)); Assertions.assertEquals(Status.DELETE_TASK_DEFINE_BY_CODE_MSG_ERROR.getCode(), diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java index aae27f88b2..8a9c6506be 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java @@ -369,6 +369,27 @@ public class UsersServiceTest { Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); } + @Test + public void testGrantProjectWithReadPerm() { + String projectIds = "100000,120000"; + User loginUser = new User(); + int userId = 3; + + // user not exist + loginUser.setId(1); + loginUser.setUserType(UserType.ADMIN_USER); + when(userMapper.selectById(userId)).thenReturn(null); + Map result = usersService.grantProjectWithReadPerm(loginUser, userId, projectIds); + logger.info(result.toString()); + Assertions.assertEquals(Status.USER_NOT_EXIST, result.get(Constants.STATUS)); + + // SUCCESS + when(userMapper.selectById(userId)).thenReturn(getUser()); + result = usersService.grantProjectWithReadPerm(loginUser, userId, projectIds); + logger.info(result.toString()); + Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); + } + @Test public void testGrantProjectByCode() { // Mock Project, User @@ -440,6 +461,31 @@ public class UsersServiceTest { Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); } + @Test + public void testRevokeProjectById() { + Mockito.when(this.userMapper.selectById(1)).thenReturn(this.getUser()); + + String projectId = "100000"; + + // user no permission + User loginUser = new User(); + Map result = this.usersService.revokeProjectById(loginUser, 1, projectId); + logger.info(result.toString()); + Assertions.assertEquals(Status.USER_NO_OPERATION_PERM, result.get(Constants.STATUS)); + + // user not exist + loginUser.setUserType(UserType.ADMIN_USER); + result = this.usersService.revokeProjectById(loginUser, 2, projectId); + logger.info(result.toString()); + Assertions.assertEquals(Status.USER_NOT_EXIST, result.get(Constants.STATUS)); + + // success + Mockito.when(this.projectMapper.queryByCode(Mockito.anyLong())).thenReturn(new Project()); + result = this.usersService.revokeProjectById(loginUser, 1, projectId); + logger.info(result.toString()); + Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); + } + @Test public void testGrantResources() { String resourceIds = "100000,120000"; diff --git a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectUserMapper.java b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectUserMapper.java index b5496c438d..38c4a2b4c9 100644 --- a/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectUserMapper.java +++ b/dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectUserMapper.java @@ -29,7 +29,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface ProjectUserMapper extends BaseMapper { /** - * delte prject user relation + * delete project user relation * * @param projectId projectId * @param userId userId diff --git a/dolphinscheduler-ui/src/common/column-width-config.ts b/dolphinscheduler-ui/src/common/column-width-config.ts index 7df8dbc321..d292a9a84f 100644 --- a/dolphinscheduler-ui/src/common/column-width-config.ts +++ b/dolphinscheduler-ui/src/common/column-width-config.ts @@ -95,6 +95,9 @@ export const COLUMN_WIDTH_CONFIG = { tag: { width: 160 }, + checkbox:{ + width: 20 + }, copy: { width: 50 } diff --git a/dolphinscheduler-ui/src/locales/en_US/project.ts b/dolphinscheduler-ui/src/locales/en_US/project.ts index 115cb70967..0aa20551f7 100644 --- a/dolphinscheduler-ui/src/locales/en_US/project.ts +++ b/dolphinscheduler-ui/src/locales/en_US/project.ts @@ -36,7 +36,11 @@ export default { delete: 'Delete', confirm: 'Confirm', cancel: 'Cancel', - delete_confirm: 'Delete?' + delete_confirm: 'Delete?', + authorize_level:'Authorize Level', + no_permission: 'No Permission', + read_permission: 'Read Permission', + all_permission: 'All Permission', }, workflow: { on_line: 'Online', diff --git a/dolphinscheduler-ui/src/locales/en_US/security.ts b/dolphinscheduler-ui/src/locales/en_US/security.ts index 2dbb66fe84..4318cdb0c8 100644 --- a/dolphinscheduler-ui/src/locales/en_US/security.ts +++ b/dolphinscheduler-ui/src/locales/en_US/security.ts @@ -150,6 +150,9 @@ export default { datasource: 'Datasource', udf: 'UDF Function', namespace: 'Namespace', + revoke_auth: 'Revoke', + grant_read: 'Grant Read', + grant_all:'Grant All', authorize_project: 'Project Authorize', authorize_resource: 'Resource Authorize', authorize_namespace: 'Namespace Authorize', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/project.ts b/dolphinscheduler-ui/src/locales/zh_CN/project.ts index 31c5c64617..fe9f14b279 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/project.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/project.ts @@ -36,7 +36,11 @@ export default { delete: '删除', confirm: '确定', cancel: '取消', - delete_confirm: '确定删除吗?' + delete_confirm: '确定删除吗?', + authorize_level:'权限等级', + no_permission: '无权限', + read_permission: '读权限', + all_permission: '所有权限', }, workflow: { on_line: '线上', diff --git a/dolphinscheduler-ui/src/locales/zh_CN/security.ts b/dolphinscheduler-ui/src/locales/zh_CN/security.ts index 9135ba78e4..995293ec68 100644 --- a/dolphinscheduler-ui/src/locales/zh_CN/security.ts +++ b/dolphinscheduler-ui/src/locales/zh_CN/security.ts @@ -148,6 +148,9 @@ export default { datasource: '数据源', udf: 'UDF函数', namespace: '命名空间', + revoke_auth: '撤销权限', + grant_read: '授予读权限', + grant_all:'授予所有权限', authorize_project: '项目授权', authorize_resource: '资源授权', authorize_namespace: '命名空间授权', diff --git a/dolphinscheduler-ui/src/service/modules/projects/index.ts b/dolphinscheduler-ui/src/service/modules/projects/index.ts index 69e097c7b8..e426d47f5e 100644 --- a/dolphinscheduler-ui/src/service/modules/projects/index.ts +++ b/dolphinscheduler-ui/src/service/modules/projects/index.ts @@ -16,7 +16,7 @@ */ import { axios } from '@/service/service' -import { ListReq, ProjectsReq, UserIdReq, UpdateProjectsReq } from './types' +import { ListReq, ListIdReq, ProjectsReq, UserIdReq, UpdateProjectsReq } from './types' export function queryProjectListPaging(params: ListReq): any { return axios({ @@ -26,6 +26,14 @@ export function queryProjectListPaging(params: ListReq): any { }) } +export function queryProjectWithAuthorizedLevelListPaging(params: ListIdReq): any { + return axios({ + url: '/projects/project-with-authorized-level-list-paging', + method: 'get', + params + }) +} + export function createProject(data: ProjectsReq): any { return axios({ url: '/projects', @@ -64,6 +72,14 @@ export function queryUnauthorizedProject(params: UserIdReq): any { }) } +export function queryProjectWithAuthorizedLevel(params: UserIdReq): any { + return axios({ + url: '/projects/project-with-authorized-level', + method: 'get', + params + }) +} + export function queryProjectByCode(code: number): any { return axios({ url: `/projects/${code}`, diff --git a/dolphinscheduler-ui/src/service/modules/projects/types.ts b/dolphinscheduler-ui/src/service/modules/projects/types.ts index 6b02d52ba0..3d0d824c8f 100644 --- a/dolphinscheduler-ui/src/service/modules/projects/types.ts +++ b/dolphinscheduler-ui/src/service/modules/projects/types.ts @@ -21,6 +21,13 @@ interface ListReq { searchVal?: string } +interface ListIdReq { + userId?: number + pageNo: number + pageSize: number + searchVal?: string +} + interface ProjectsReq { description?: string projectName: string @@ -59,6 +66,7 @@ interface ProjectRes { export { ListReq, + ListIdReq, ProjectsReq, UserIdReq, UpdateProjectsReq, diff --git a/dolphinscheduler-ui/src/service/modules/users/index.ts b/dolphinscheduler-ui/src/service/modules/users/index.ts index 9a1313cbfb..ce3806e4af 100644 --- a/dolphinscheduler-ui/src/service/modules/users/index.ts +++ b/dolphinscheduler-ui/src/service/modules/users/index.ts @@ -97,6 +97,14 @@ export function grantResource(data: GrantResourceReq) { }) } +export function revokeProjectById(data: GrantProject) { + return axios({ + url: '/users/revoke-project-by-id', + method: 'post', + data + }) +} + export function grantProject(data: GrantProject) { return axios({ url: '/users/grant-project', @@ -105,6 +113,14 @@ export function grantProject(data: GrantProject) { }) } +export function grantProjectWithReadPerm(data: GrantProject) { + return axios({ + url: 'users/grant-project-with-read-perm', + method: 'post', + data + }) +} + export function grantProjectByCode(data: ProjectCodeReq & UserIdReq): any { return axios({ url: '/users/grant-project-by-code', diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx b/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx index bce9c567c6..e0855d9d5b 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx @@ -15,19 +15,26 @@ * limitations under the License. */ -import { defineComponent, PropType, toRefs, watch } from 'vue' +import { defineComponent, PropType, toRefs, watch} from 'vue' import { useI18n } from 'vue-i18n' import { + NInput, + NButton, + NIcon, NTransfer, NSpace, NRadioGroup, NRadioButton, - NTreeSelect + NTreeSelect, + NDataTable, + NPagination } from 'naive-ui' import { useAuthorize } from './use-authorize' import Modal from '@/components/modal' import styles from '../index.module.scss' import type { TAuthType } from '../types' +import { useColumns } from './use-columns' +import { SearchOutlined } from '@vicons/antd' const props = { show: { @@ -43,14 +50,13 @@ const props = { default: 'auth_project' } } - export const AuthorizeModal = defineComponent({ name: 'authorize-project-modal', props, emits: ['cancel'], setup(props, ctx) { const { t } = useI18n() - const { state, onInit, onSave } = useAuthorize() + const { state, onInit, onSave, getProjects, revokeProjectByIdRequest, grantProjectRequest, grantProjectWithReadPermRequest, requestData, handleChangePageSize } = useAuthorize() const onCancel = () => { ctx.emit('cancel') } @@ -59,6 +65,20 @@ export const AuthorizeModal = defineComponent({ if (result) onCancel() } + const onRevokeProject = () => { + revokeProjectByIdRequest(props.userId, state.projectIds) + } + const onGrantReadPerm = () => { + grantProjectWithReadPermRequest(props.userId, state.projectIds) + } + const onGrantAllPerm = () => { + grantProjectRequest(props.userId, state.projectIds) + } + + const { columnsRef } = useColumns() + const handleCheck = (rowKeys: Array) => { + state.projectIds = rowKeys.join() + } watch( () => props.show, () => { @@ -70,14 +90,23 @@ export const AuthorizeModal = defineComponent({ return { t, + columnsRef, + rowKey: (row: any) => row.id, ...toRefs(state), onCancel, - onConfirm + onConfirm, + getProjects, + handleCheck, + requestData, + handleChangePageSize, + onRevokeProject, + onGrantReadPerm, + onGrantAllPerm } }, - render(props: { type: TAuthType }) { + render(props: { type: TAuthType, userId: number }) { const { t } = this - const { type } = props + const { type, userId } = props return ( {type === 'authorize_project' && ( - + + + {t('security.user.revoke_auth')} + + + {t('security.user.grant_read')} + + + {t('security.user.grant_all')} + + + {/* */} + this.getProjects(userId)}> + + + + + + +
+ +
+ )} {type === 'authorize_datasource' && ( { if (state.loading) return state.loading = true - const projects = await Promise.all([ - queryAuthorizedProject({ userId }), - queryUnauthorizedProject({ userId }) - ]) + if (userId) { + state.userId = userId + } + + const projectsList = await queryProjectWithAuthorizedLevelListPaging({ + userId, + searchVal: state.searchVal, + pageSize: state.pagination.pageSize, + pageNo: state.pagination.page + }) state.loading = false - state.authorizedProjects = projects[0].map( - (item: { name: string; id: number }) => item.id - ) - state.unauthorizedProjects = [...projects[0], ...projects[1]].map( - (item: { name: string; id: number }) => ({ - label: item.name, - value: item.id - }) - ) + if (!projectsList) throw Error() + state.pagination.totalPage = projectsList.totalPage + state.projectWithAuthorizedLevel = projectsList.totalList + return state.projectWithAuthorizedLevel + } + + const requestData = async (page: number) => { + state.pagination.page = page + await getProjects(state.userId) + } + + const handleChangePageSize = async (pageSize: number) => { + state.pagination.page = 1 + state.pagination.pageSize = pageSize + await getProjects(state.userId) + } + + const revokeProjectByIdRequest = async (userId: number, projectIds: string) => { + await revokeProjectById({ + userId, + projectIds: projectIds + }) + await getProjects(userId) + } + + const grantProjectRequest = async (userId: number, projectIds: string) => { + await grantProject({ + userId, + projectIds: projectIds + }) + await getProjects(userId) + } + + const grantProjectWithReadPermRequest = async (userId: number, projectIds: string) => { + await grantProjectWithReadPerm({ + userId, + projectIds: projectIds + }) + await getProjects(userId) } const getDatasources = async (userId: number) => { @@ -216,12 +263,6 @@ export function useAuthorize() { const onSave = async (type: TAuthType, userId: number) => { if (state.saving) return false state.saving = true - if (type === 'authorize_project') { - await grantProject({ - userId, - projectIds: state.authorizedProjects.join(',') - }) - } if (type === 'authorize_datasource') { await grantDataSource({ userId, @@ -281,5 +322,5 @@ export function useAuthorize() { return true } - return { state, onInit, onSave } + return { state, onInit, onSave, getProjects, revokeProjectByIdRequest, grantProjectRequest, grantProjectWithReadPermRequest, requestData, handleChangePageSize } } diff --git a/dolphinscheduler-ui/src/views/security/user-manage/components/use-columns.ts b/dolphinscheduler-ui/src/views/security/user-manage/components/use-columns.ts new file mode 100644 index 0000000000..9e7a609136 --- /dev/null +++ b/dolphinscheduler-ui/src/views/security/user-manage/components/use-columns.ts @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ref, watch, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import type { TableColumns } from '../types' + +// const { t } = useI18n() +const PERM_LIST = [ + { + label: 'project.list.no_permission', + value: 0 + }, + { + label: 'project.list.read_permission', + value: 2 + }, + { + label: 'project.list.all_permission', + value: 7 + } +] + + +export function useColumns() { + const { t } = useI18n() + + const columnsRef = ref({ + columns: [] as TableColumns, + tableWidth: DefaultTableWidth + }) + + const createColumns = () => { + const columns: any = [ + { + type: 'selection', + key: 'selection', + ...COLUMN_WIDTH_CONFIG['checkbox'] + }, + { + title: t('project.list.project_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['size'] + }, + { + title: t('project.list.authorize_level'), + key: 'perm', + render: (record: any):any => { + return PERM_LIST.filter(item => item.value == record.perm).map(item => t(item.label)) + }, + ...COLUMN_WIDTH_CONFIG['index'] + } + ] + columnsRef.value = { + columns, + tableWidth: calculateTableWidth(columns) + } + } + + onMounted(() => { + createColumns() + }) + + watch(useI18n().locale, () => { + createColumns() + }) + + return { + columnsRef, + createColumns + } +} diff --git a/dolphinscheduler-ui/src/views/security/user-manage/index.module.scss b/dolphinscheduler-ui/src/views/security/user-manage/index.module.scss index 68d62fdd11..4bdd2c4c60 100644 --- a/dolphinscheduler-ui/src/views/security/user-manage/index.module.scss +++ b/dolphinscheduler-ui/src/views/security/user-manage/index.module.scss @@ -18,3 +18,8 @@ .transfer { width: 100%; } +.pagination { + margin-top: 10px; + display: flex; + justify-content: center; +}