Browse Source

[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
3.2.0-release
Yiming Guo 2 years ago committed by GitHub
parent
commit
dc8d18cf87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 57
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java
  2. 48
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java
  3. 2
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/enums/Status.java
  4. 2
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java
  5. 24
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProjectService.java
  6. 4
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java
  7. 19
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java
  8. 28
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
  9. 156
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProjectServiceImpl.java
  10. 33
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskDefinitionServiceImpl.java
  11. 97
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/UsersServiceImpl.java
  12. 1
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java
  13. 106
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProjectServiceTest.java
  14. 8
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskDefinitionServiceImplTest.java
  15. 46
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/UsersServiceTest.java
  16. 2
      dolphinscheduler-dao/src/main/java/org/apache/dolphinscheduler/dao/mapper/ProjectUserMapper.java
  17. 3
      dolphinscheduler-ui/src/common/column-width-config.ts
  18. 6
      dolphinscheduler-ui/src/locales/en_US/project.ts
  19. 3
      dolphinscheduler-ui/src/locales/en_US/security.ts
  20. 6
      dolphinscheduler-ui/src/locales/zh_CN/project.ts
  21. 3
      dolphinscheduler-ui/src/locales/zh_CN/security.ts
  22. 18
      dolphinscheduler-ui/src/service/modules/projects/index.ts
  23. 8
      dolphinscheduler-ui/src/service/modules/projects/types.ts
  24. 16
      dolphinscheduler-ui/src/service/modules/users/index.ts
  25. 92
      dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx
  26. 89
      dolphinscheduler-ui/src/views/security/user-manage/components/use-authorize.ts
  27. 91
      dolphinscheduler-ui/src/views/security/user-manage/components/use-columns.ts
  28. 5
      dolphinscheduler-ui/src/views/security/user-manage/index.module.scss

57
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/ProjectController.java

@ -174,6 +174,43 @@ public class ProjectController extends BaseController {
return result; 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 * delete project by code
* *
@ -234,6 +271,26 @@ public class ProjectController extends BaseController {
return projectService.queryAuthorizedProject(loginUser, userId); 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 * query authorized user
* *

48
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/controller/UsersController.java

@ -214,6 +214,54 @@ public class UsersController extends BaseController {
return returnDataList(result); 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<String, Object> 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<String, Object> result = usersService.grantProjectWithReadPerm(loginUser, userId, projectIds);
return returnDataList(result);
}
/** /**
* grant project * grant project
* *

2
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_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_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_NOT_EXIST(50001, "process instance {0} does not exist", "工作流实例[{0}]不存在"),
PROCESS_INSTANCE_EXIST(50002, "process instance {0} already exists", "工作流实例[{0}]已存在"), PROCESS_INSTANCE_EXIST(50002, "process instance {0} already exists", "工作流实例[{0}]已存在"),

2
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/permission/ResourcePermissionCheckServiceImpl.java

@ -242,7 +242,7 @@ public class ResourcePermissionCheckServiceImpl
} }
List<Resource> ownResourceList = resourceMapper.queryResourceListAuthored(userId, -1); List<Resource> ownResourceList = resourceMapper.queryResourceListAuthored(userId, -1);
relationResources.addAll(ownResourceList); relationResources.addAll(ownResourceList);
return ownResourceList.stream().map(Resource::getId).collect(toSet()); return relationResources.stream().map(Resource::getId).collect(toSet());
} }
@Override @Override

24
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 hasProjectAndPerm(User loginUser, Project project, Result result, String permission);
boolean hasProjectAndWritePerm(User loginUser, Project project, Result result);
boolean hasProjectAndWritePerm(User loginUser, Project project, Map<String, Object> result);
/** /**
* admin can view all projects * admin can view all projects
* *
@ -92,6 +96,19 @@ public interface ProjectService {
*/ */
Result queryProjectListPaging(User loginUser, Integer pageSize, Integer pageNo, String searchVal); 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 * delete project by code
* *
@ -131,6 +148,13 @@ public interface ProjectService {
*/ */
Result queryAuthorizedProject(User loginUser, Integer userId); 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 * query authorized user
* *

4
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ResourcesService.java

@ -195,7 +195,9 @@ public interface ResourcesService {
/** /**
* updateProcessInstance resource * updateProcessInstance resource
* *
* @param resourceId resource id * @param loginUser login user
* @param fullName full name
* @param tenantCode tenantCode
* @param content content * @param content content
* @return update result cod * @return update result cod
*/ */

19
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/UsersService.java

@ -153,6 +153,16 @@ public interface UsersService {
*/ */
Map<String, Object> grantProject(User loginUser, int userId, String projectIds); Map<String, Object> 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<String, Object> grantProjectWithReadPerm(User loginUser, int userId, String projectIds);
/** /**
* grant project by code * grant project by code
* *
@ -163,6 +173,15 @@ public interface UsersService {
*/ */
Map<String, Object> grantProjectByCode(User loginUser, int userId, long projectCode); Map<String, Object> 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<String, Object> revokeProjectById(User loginUser, int userId, String projectIds);
/** /**
* revoke the project permission for specified user. * revoke the project permission for specified user.
* @param loginUser Login user * @param loginUser Login user

28
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, String otherParamsJson,
ProcessExecutionTypeEnum executionType) { ProcessExecutionTypeEnum executionType) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project
// check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_CREATE); 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; return result;
} }
if (checkDescriptionLength(description)) { if (checkDescriptionLength(description)) {
@ -754,12 +756,14 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro
String otherParamsJson, String otherParamsJson,
ProcessExecutionTypeEnum executionType) { ProcessExecutionTypeEnum executionType) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_UPDATE); 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; return result;
} }
if (checkDescriptionLength(description)) { if (checkDescriptionLength(description)) {
logger.warn("Parameter description is too long."); logger.warn("Parameter description is too long.");
putMsg(result, Status.DESCRIPTION_TOO_LONG_ERROR); putMsg(result, Status.DESCRIPTION_TOO_LONG_ERROR);
@ -2404,12 +2408,14 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro
public Map<String, Object> deleteProcessDefinitionVersion(User loginUser, long projectCode, long code, public Map<String, Object> deleteProcessDefinitionVersion(User loginUser, long projectCode, long code,
int version) { int version) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, VERSION_DELETE); 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; return result;
} }
ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(code); ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(code);
if (processDefinition == null || projectCode != processDefinition.getProjectCode()) { if (processDefinition == null || projectCode != processDefinition.getProjectCode()) {
@ -2466,10 +2472,11 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro
String scheduleJson, String scheduleJson,
ProcessExecutionTypeEnum executionType) { ProcessExecutionTypeEnum executionType) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_CREATE); 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; return result;
} }
if (checkDescriptionLength(description)) { if (checkDescriptionLength(description)) {
@ -2613,10 +2620,11 @@ public class ProcessDefinitionServiceImpl extends BaseServiceImpl implements Pro
String otherParamsJson, String otherParamsJson,
ProcessExecutionTypeEnum executionType) { ProcessExecutionTypeEnum executionType) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_UPDATE); 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; return result;
} }
if (checkDescriptionLength(description)) { if (checkDescriptionLength(description)) {

156
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;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_CREATE; 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_DELETE;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_UPDATE;
import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.exceptions.ServiceException;
@ -256,6 +255,60 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic
return checkResult; 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<String, Object> 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 @Override
public boolean hasProjectAndPerm(User loginUser, Project project, Result result, String permission) { public boolean hasProjectAndPerm(User loginUser, Project project, Result result, String permission) {
boolean checkResult = false; boolean checkResult = false;
@ -310,6 +363,57 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic
return result; 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<Project> pageInfo = new PageInfo<>(pageNo, pageSize);
Page<Project> page = new Page<>(pageNo, pageSize);
Set<Integer> allProjectIds = resourcePermissionCheckService
.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, loginUser.getId(), logger);
Set<Integer> userProjectIds = resourcePermissionCheckService
.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, userId, logger);
if (allProjectIds.isEmpty()) {
result.setData(pageInfo);
putMsg(result, Status.SUCCESS);
return result;
}
IPage<Project> projectIPage =
projectMapper.queryProjectListPaging(page, new ArrayList<>(allProjectIds), searchVal);
List<Project> 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 * delete project by code
* *
@ -322,6 +426,11 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic
Result result = new Result(); Result result = new Result();
Project project = projectMapper.queryByCode(projectCode); 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); checkProjectAndAuth(result, loginUser, project, project == null ? 0L : project.getCode(), PROJECT_DELETE);
if (result.getCode() != Status.SUCCESS.getCode()) { if (result.getCode() != Status.SUCCESS.getCode()) {
return result; return result;
@ -386,8 +495,8 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic
} }
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
boolean hasProjectAndPerm = hasProjectAndPerm(loginUser, project, result, PROJECT_UPDATE); boolean hasProjectAndWritePerm = hasProjectAndWritePerm(loginUser, project, result);
if (!hasProjectAndPerm) { if (!hasProjectAndWritePerm) {
return result; return result;
} }
Project tempProject = projectMapper.queryByName(projectName); Project tempProject = projectMapper.queryByName(projectName);
@ -417,6 +526,47 @@ public class ProjectServiceImpl extends BaseServiceImpl implements ProjectServic
return result; 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<Integer> projectIds = resourcePermissionCheckService
.userOwnedResourceIdsAcquisition(AuthorizationType.PROJECTS, loginUser.getId(), logger);
List<Project> projectList = projectMapper.listAuthorizedProjects(
loginUser.getUserType().equals(UserType.ADMIN_USER) ? 0 : loginUser.getId(),
new ArrayList<>(projectIds));
List<Project> unauthorizedProjectsList = new ArrayList<>();
List<Project> authedProjectList = new ArrayList<>();
Set<Project> 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<Project> joined = new ArrayList<>();
joined.addAll(authedProjectList);
joined.addAll(unauthorizedProjectsList);
result.setData(joined);
putMsg(result, Status.SUCCESS);
return result;
}
/** /**
* query unauthorized project * query unauthorized project
* *

33
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, long projectCode,
String taskDefinitionJson) { String taskDefinitionJson) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_CREATE); 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; return result;
} }
@ -285,10 +286,11 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe
String taskDefinitionJsonObj, String taskDefinitionJsonObj,
String upstreamCodes) { String upstreamCodes) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_CREATE); 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; return result;
} }
ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode); ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode);
@ -426,10 +428,16 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe
/** /**
* Whether task definition can be deleted or not * 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 // check user access for project
Project project = projectMapper.queryByCode(taskDefinition.getProjectCode()); Project project = projectMapper.queryByCode(taskDefinition.getProjectCode());
projectService.checkProjectAndAuthThrowException(user, project, TASK_DEFINITION_DELETE); projectService.checkProjectAndAuthThrowException(user, project, TASK_DEFINITION_DELETE);
// check if user have write perm for project
Map<String, Object> 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 // Whether task relation workflow is online
if (processService.isTaskOnline(taskDefinition.getCode()) && taskDefinition.getFlag() == Flag.YES) { 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); throw new ServiceException(Status.TASK_DEFINE_NOT_EXIST, taskCode);
} }
this.taskCanDeleteValid(loginUser, taskDefinition); this.taskCanDeleteValid(loginUser, taskDefinition, loginUser);
int delete = taskDefinitionMapper.deleteByCode(taskCode); int delete = taskDefinitionMapper.deleteByCode(taskCode);
if (delete <= 0) { if (delete <= 0) {
throw new ServiceException(Status.DELETE_TASK_DEFINE_BY_CODE_MSG_ERROR, taskDefinition.getCode()); 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, private TaskDefinitionLog updateTask(User loginUser, long projectCode, long taskCode, String taskDefinitionJsonObj,
Map<String, Object> result) { Map<String, Object> result) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project
result.putAll(projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_UPDATE)); // check if user have write perm for project
if (result.get(Constants.STATUS) != Status.SUCCESS) { boolean hasProjectAndWritePerm = projectService.hasProjectAndWritePerm(loginUser, project, result);
if (!hasProjectAndWritePerm) {
return null; return null;
} }
TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode); TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode);
if (taskDefinition == null) { if (taskDefinition == null) {
logger.error("Task definition does not exist, taskDefinitionCode:{}.", taskCode); logger.error("Task definition does not exist, taskDefinitionCode:{}.", taskCode);
@ -943,10 +953,11 @@ public class TaskDefinitionServiceImpl extends BaseServiceImpl implements TaskDe
@Override @Override
public Map<String, Object> deleteByCodeAndVersion(User loginUser, long projectCode, long taskCode, int version) { public Map<String, Object> deleteByCodeAndVersion(User loginUser, long projectCode, long taskCode, int version) {
Project project = projectMapper.queryByCode(projectCode); Project project = projectMapper.queryByCode(projectCode);
// check user access for project // check if user have write perm for project
Map<String, Object> result = Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, TASK_DEFINITION_DELETE); 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; return result;
} }
TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode); TaskDefinition taskDefinition = taskDefinitionMapper.queryByCode(taskCode);

97
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<String, Object> revokeProjectById(User loginUser, int userId, String projectIds) {
Map<String, Object> 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<String, Object> grantProjectWithReadPerm(User loginUser, int userId, String projectIds) {
Map<String, Object> 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 * grant project
* *
@ -559,13 +650,15 @@ public class UsersServiceImpl extends BaseServiceImpl implements UsersService {
return result; return result;
} }
projectUserMapper.deleteProjectRelation(0, userId);
if (check(result, StringUtils.isEmpty(projectIds), Status.SUCCESS)) { if (check(result, StringUtils.isEmpty(projectIds), Status.SUCCESS)) {
logger.warn("Parameter projectIds is empty."); logger.warn("Parameter projectIds is empty.");
return result; return result;
} }
Arrays.stream(projectIds.split(",")).distinct().forEach(projectId -> { 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(); Date now = new Date();
ProjectUser projectUser = new ProjectUser(); ProjectUser projectUser = new ProjectUser();
projectUser.setUserId(userId); projectUser.setUserId(userId);

1
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(projectMapper.queryByCode(projectCode)).thenReturn(getProject(projectCode));
Mockito.when(projectService.checkProjectAndAuth(user, project, projectCode, WORKFLOW_UPDATE)) Mockito.when(projectService.checkProjectAndAuth(user, project, projectCode, WORKFLOW_UPDATE))
.thenReturn(result); .thenReturn(result);
Mockito.when(projectService.hasProjectAndWritePerm(user, project, result)).thenReturn(true);
try { try {
processDefinitionService.updateProcessDefinition(user, projectCode, "test", 1, processDefinitionService.updateProcessDefinition(user, projectCode, "test", 1,

106
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_CREATE;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_DELETE; import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_DELETE;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.PROJECT_UPDATE; 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.enums.Status;
import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService; import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService;
import org.apache.dolphinscheduler.api.service.impl.BaseServiceImpl; import org.apache.dolphinscheduler.api.service.impl.BaseServiceImpl;
import org.apache.dolphinscheduler.api.service.impl.ProjectServiceImpl; 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.api.utils.Result;
import org.apache.dolphinscheduler.common.constants.Constants; import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.AuthorizationType; import org.apache.dolphinscheduler.common.enums.AuthorizationType;
@ -62,6 +65,9 @@ import org.mockito.quality.Strictness;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
/** /**
* project service test * project service test
**/ **/
@ -206,6 +212,66 @@ public class ProjectServiceTest {
Assertions.assertTrue(checkResult); Assertions.assertTrue(checkResult);
} }
@Test
public void testQueryProjectWithAuthorizedLevelListPaging() {
IPage<Project> 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<Integer> 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<Project> pageInfo = (PageInfo<Project>) 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<String, Object> 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 @Test
public void testDeleteProject() { public void testDeleteProject() {
User loginUser = getLoginUser(); User loginUser = getLoginUser();
@ -213,10 +279,10 @@ public class ProjectServiceTest {
Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.PROJECTS, Mockito.when(resourcePermissionCheckService.operationPermissionCheck(AuthorizationType.PROJECTS,
new Object[]{1}, loginUser.getId(), new Object[]{1}, loginUser.getId(),
PROJECT_DELETE, baseServiceLogger)).thenReturn(true); PROJECT_DELETE, baseServiceLogger)).thenReturn(true);
// PROJECT_NOT_FOUNT // PROJECT_NOT_FOUND
Result result = projectService.deleteProject(loginUser, 11L); Result result = projectService.deleteProject(loginUser, 11L);
logger.info(result.toString()); 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); loginUser.setId(2);
// USER_NO_OPERATION_PROJECT_PERM // USER_NO_OPERATION_PROJECT_PERM
Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.PROJECTS, new Object[]{1}, Mockito.when(resourcePermissionCheckService.resourcePermissionCheck(AuthorizationType.PROJECTS, new Object[]{1},
@ -224,7 +290,7 @@ public class ProjectServiceTest {
baseServiceLogger)).thenReturn(true); baseServiceLogger)).thenReturn(true);
result = projectService.deleteProject(loginUser, 1L); result = projectService.deleteProject(loginUser, 1L);
logger.info(result.toString()); 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 // DELETE_PROJECT_ERROR_DEFINES_NOT_NULL
Mockito.when(processDefinitionMapper.queryAllDefinitionList(1L)).thenReturn(getProcessDefinitions()); Mockito.when(processDefinitionMapper.queryAllDefinitionList(1L)).thenReturn(getProcessDefinitions());
@ -418,6 +484,40 @@ public class ProjectServiceTest {
} }
@Test
public void testQueryProjectWithAuthorizedLevel() {
Set<Integer> set = new HashSet();
set.add(1);
// test admin user
User loginUser = new User();
loginUser.setUserType(UserType.ADMIN_USER);
loginUser.setId(1);
List<Integer> 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<Project> projects = (List<Project>) 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<Project>) result.getData();
Assertions.assertTrue(CollectionUtils.isNotEmpty(projects));
}
@Test @Test
public void testQueryUnauthorizedProject() { public void testQueryUnauthorizedProject() {
Set<Integer> set = new HashSet(); Set<Integer> set = new HashSet();

8
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," + "\\\\\\\"failedNode\\\\\\\":[\\\\\\\"\\\\\\\"]}\\\",\\\"dependence\\\":{}}\",\"flag\":0,\"taskPriority\":0,"
+ "\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":0,\"timeoutFlag\":0," + "\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":0,\"timeoutFlag\":0,"
+ "\"timeoutNotifyStrategy\":0,\"timeout\":0,\"delayTime\":0,\"resourceIds\":\"\"}]"; + "\"timeoutNotifyStrategy\":0,\"timeout\":0,\"delayTime\":0,\"resourceIds\":\"\"}]";
List<TaskDefinitionLog> 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<String, Object> relation = taskDefinitionService Map<String, Object> relation = taskDefinitionService
.createTaskDefinition(user, PROJECT_CODE, createTaskDefinitionJson); .createTaskDefinition(user, PROJECT_CODE, createTaskDefinitionJson);
Assertions.assertEquals(Status.SUCCESS, relation.get(Constants.STATUS)); Assertions.assertEquals(Status.SUCCESS, relation.get(Constants.STATUS));
@ -166,8 +162,7 @@ public class TaskDefinitionServiceImplTest {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
putMsg(result, Status.SUCCESS, PROJECT_CODE); putMsg(result, Status.SUCCESS, PROJECT_CODE);
Mockito.when(projectService.checkProjectAndAuth(user, project, PROJECT_CODE, TASK_DEFINITION_UPDATE)) Mockito.when(projectService.hasProjectAndWritePerm(user, project, new HashMap<>())).thenReturn(true);
.thenReturn(result);
Mockito.when(processService.isTaskOnline(TASK_CODE)).thenReturn(Boolean.FALSE); Mockito.when(processService.isTaskOnline(TASK_CODE)).thenReturn(Boolean.FALSE);
Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(new TaskDefinition()); Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(new TaskDefinition());
@ -212,6 +207,7 @@ public class TaskDefinitionServiceImplTest {
// error delete single task definition object // error delete single task definition object
Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(getTaskDefinition()); Mockito.when(taskDefinitionMapper.queryByCode(TASK_CODE)).thenReturn(getTaskDefinition());
Mockito.when(taskDefinitionMapper.deleteByCode(TASK_CODE)).thenReturn(0); Mockito.when(taskDefinitionMapper.deleteByCode(TASK_CODE)).thenReturn(0);
Mockito.when(projectService.hasProjectAndWritePerm(user, project, new HashMap<>())).thenReturn(true);
exception = Assertions.assertThrows(ServiceException.class, exception = Assertions.assertThrows(ServiceException.class,
() -> taskDefinitionService.deleteTaskDefinitionByCode(user, TASK_CODE)); () -> taskDefinitionService.deleteTaskDefinitionByCode(user, TASK_CODE));
Assertions.assertEquals(Status.DELETE_TASK_DEFINE_BY_CODE_MSG_ERROR.getCode(), Assertions.assertEquals(Status.DELETE_TASK_DEFINE_BY_CODE_MSG_ERROR.getCode(),

46
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)); 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<String, Object> 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 @Test
public void testGrantProjectByCode() { public void testGrantProjectByCode() {
// Mock Project, User // Mock Project, User
@ -440,6 +461,31 @@ public class UsersServiceTest {
Assertions.assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); 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<String, Object> 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 @Test
public void testGrantResources() { public void testGrantResources() {
String resourceIds = "100000,120000"; String resourceIds = "100000,120000";

2
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<ProjectUser> { public interface ProjectUserMapper extends BaseMapper<ProjectUser> {
/** /**
* delte prject user relation * delete project user relation
* *
* @param projectId projectId * @param projectId projectId
* @param userId userId * @param userId userId

3
dolphinscheduler-ui/src/common/column-width-config.ts

@ -95,6 +95,9 @@ export const COLUMN_WIDTH_CONFIG = {
tag: { tag: {
width: 160 width: 160
}, },
checkbox:{
width: 20
},
copy: { copy: {
width: 50 width: 50
} }

6
dolphinscheduler-ui/src/locales/en_US/project.ts

@ -36,7 +36,11 @@ export default {
delete: 'Delete', delete: 'Delete',
confirm: 'Confirm', confirm: 'Confirm',
cancel: 'Cancel', 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: { workflow: {
on_line: 'Online', on_line: 'Online',

3
dolphinscheduler-ui/src/locales/en_US/security.ts

@ -150,6 +150,9 @@ export default {
datasource: 'Datasource', datasource: 'Datasource',
udf: 'UDF Function', udf: 'UDF Function',
namespace: 'Namespace', namespace: 'Namespace',
revoke_auth: 'Revoke',
grant_read: 'Grant Read',
grant_all:'Grant All',
authorize_project: 'Project Authorize', authorize_project: 'Project Authorize',
authorize_resource: 'Resource Authorize', authorize_resource: 'Resource Authorize',
authorize_namespace: 'Namespace Authorize', authorize_namespace: 'Namespace Authorize',

6
dolphinscheduler-ui/src/locales/zh_CN/project.ts

@ -36,7 +36,11 @@ export default {
delete: '删除', delete: '删除',
confirm: '确定', confirm: '确定',
cancel: '取消', cancel: '取消',
delete_confirm: '确定删除吗?' delete_confirm: '确定删除吗?',
authorize_level:'权限等级',
no_permission: '无权限',
read_permission: '读权限',
all_permission: '所有权限',
}, },
workflow: { workflow: {
on_line: '线上', on_line: '线上',

3
dolphinscheduler-ui/src/locales/zh_CN/security.ts

@ -148,6 +148,9 @@ export default {
datasource: '数据源', datasource: '数据源',
udf: 'UDF函数', udf: 'UDF函数',
namespace: '命名空间', namespace: '命名空间',
revoke_auth: '撤销权限',
grant_read: '授予读权限',
grant_all:'授予所有权限',
authorize_project: '项目授权', authorize_project: '项目授权',
authorize_resource: '资源授权', authorize_resource: '资源授权',
authorize_namespace: '命名空间授权', authorize_namespace: '命名空间授权',

18
dolphinscheduler-ui/src/service/modules/projects/index.ts

@ -16,7 +16,7 @@
*/ */
import { axios } from '@/service/service' 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 { export function queryProjectListPaging(params: ListReq): any {
return axios({ 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 { export function createProject(data: ProjectsReq): any {
return axios({ return axios({
url: '/projects', 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 { export function queryProjectByCode(code: number): any {
return axios({ return axios({
url: `/projects/${code}`, url: `/projects/${code}`,

8
dolphinscheduler-ui/src/service/modules/projects/types.ts

@ -21,6 +21,13 @@ interface ListReq {
searchVal?: string searchVal?: string
} }
interface ListIdReq {
userId?: number
pageNo: number
pageSize: number
searchVal?: string
}
interface ProjectsReq { interface ProjectsReq {
description?: string description?: string
projectName: string projectName: string
@ -59,6 +66,7 @@ interface ProjectRes {
export { export {
ListReq, ListReq,
ListIdReq,
ProjectsReq, ProjectsReq,
UserIdReq, UserIdReq,
UpdateProjectsReq, UpdateProjectsReq,

16
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) { export function grantProject(data: GrantProject) {
return axios({ return axios({
url: '/users/grant-project', 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 { export function grantProjectByCode(data: ProjectCodeReq & UserIdReq): any {
return axios({ return axios({
url: '/users/grant-project-by-code', url: '/users/grant-project-by-code',

92
dolphinscheduler-ui/src/views/security/user-manage/components/authorize-modal.tsx

@ -18,16 +18,23 @@
import { defineComponent, PropType, toRefs, watch} from 'vue' import { defineComponent, PropType, toRefs, watch} from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { import {
NInput,
NButton,
NIcon,
NTransfer, NTransfer,
NSpace, NSpace,
NRadioGroup, NRadioGroup,
NRadioButton, NRadioButton,
NTreeSelect NTreeSelect,
NDataTable,
NPagination
} from 'naive-ui' } from 'naive-ui'
import { useAuthorize } from './use-authorize' import { useAuthorize } from './use-authorize'
import Modal from '@/components/modal' import Modal from '@/components/modal'
import styles from '../index.module.scss' import styles from '../index.module.scss'
import type { TAuthType } from '../types' import type { TAuthType } from '../types'
import { useColumns } from './use-columns'
import { SearchOutlined } from '@vicons/antd'
const props = { const props = {
show: { show: {
@ -43,14 +50,13 @@ const props = {
default: 'auth_project' default: 'auth_project'
} }
} }
export const AuthorizeModal = defineComponent({ export const AuthorizeModal = defineComponent({
name: 'authorize-project-modal', name: 'authorize-project-modal',
props, props,
emits: ['cancel'], emits: ['cancel'],
setup(props, ctx) { setup(props, ctx) {
const { t } = useI18n() const { t } = useI18n()
const { state, onInit, onSave } = useAuthorize() const { state, onInit, onSave, getProjects, revokeProjectByIdRequest, grantProjectRequest, grantProjectWithReadPermRequest, requestData, handleChangePageSize } = useAuthorize()
const onCancel = () => { const onCancel = () => {
ctx.emit('cancel') ctx.emit('cancel')
} }
@ -59,6 +65,20 @@ export const AuthorizeModal = defineComponent({
if (result) onCancel() 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<number>) => {
state.projectIds = rowKeys.join()
}
watch( watch(
() => props.show, () => props.show,
() => { () => {
@ -70,14 +90,23 @@ export const AuthorizeModal = defineComponent({
return { return {
t, t,
columnsRef,
rowKey: (row: any) => row.id,
...toRefs(state), ...toRefs(state),
onCancel, onCancel,
onConfirm onConfirm,
getProjects,
handleCheck,
requestData,
handleChangePageSize,
onRevokeProject,
onGrantReadPerm,
onGrantAllPerm
} }
}, },
render(props: { type: TAuthType }) { render(props: { type: TAuthType, userId: number }) {
const { t } = this const { t } = this
const { type } = props const { type, userId } = props
return ( return (
<Modal <Modal
show={this.show} show={this.show}
@ -89,13 +118,54 @@ export const AuthorizeModal = defineComponent({
cancelClassName='btn-cancel' cancelClassName='btn-cancel'
> >
{type === 'authorize_project' && ( {type === 'authorize_project' && (
<NTransfer
<NSpace vertical>
<NSpace>
<NButton size='small' type='primary' onClick={this.onRevokeProject}>
{t('security.user.revoke_auth')}
</NButton>
<NButton size='small' type='primary' onClick={this.onGrantReadPerm}>
{t('security.user.grant_read')}
</NButton>
<NButton size='small' type='primary' onClick={this.onGrantAllPerm}>
{t('security.user.grant_all')}
</NButton>
<NInput
size='small'
placeholder={t('project.list.project_tips')}
clearable
v-model:value={this.searchVal}
/>
{/* <NButton size='small' type='primary' onClick={this.handleSearch}> */}
<NButton size='small' type='primary' onClick={() => this.getProjects(userId)}>
<NIcon>
<SearchOutlined />
</NIcon>
</NButton>
</NSpace>
<NDataTable
virtualScroll virtualScroll
options={this.unauthorizedProjects} row-class-name='items'
filterable columns={this.columnsRef.columns}
v-model={[this.authorizedProjects, 'value']} data={this.projectWithAuthorizedLevel}
class={styles.transfer} loading={this.loading}
max-height="250"
row-key={this.rowKey}
on-update:checked-row-keys={this.handleCheck}
/>
<div class={styles.pagination}>
<NPagination
v-model:page={this.pagination.page}
v-model:page-size={this.pagination.pageSize}
page-count={this.pagination.totalPage}
show-size-picker
page-sizes={[5, 10]}
show-quick-jumper
onUpdatePage={this.requestData}
onUpdatePageSize={this.handleChangePageSize}
/> />
</div>
</NSpace>
)} )}
{type === 'authorize_datasource' && ( {type === 'authorize_datasource' && (
<NTransfer <NTransfer

89
dolphinscheduler-ui/src/views/security/user-manage/components/use-authorize.ts

@ -16,8 +16,7 @@
*/ */
import { reactive } from 'vue' import { reactive } from 'vue'
import { import {
queryAuthorizedProject, queryProjectWithAuthorizedLevelListPaging
queryUnauthorizedProject
} from '@/service/modules/projects' } from '@/service/modules/projects'
import { import {
authedDatasource, authedDatasource,
@ -36,17 +35,22 @@ import {
import { import {
grantProject, grantProject,
grantResource, grantResource,
grantProjectWithReadPerm,
grantDataSource, grantDataSource,
grantUDFFunc, grantUDFFunc,
grantNamespaceFunc grantNamespaceFunc,
revokeProjectById,
} from '@/service/modules/users' } from '@/service/modules/users'
import utils from '@/utils' import utils from '@/utils'
import type { TAuthType, IResourceOption, IOption } from '../types' import type { TAuthType, IResourceOption, IOption, IRecord } from '../types'
export function useAuthorize() { export function useAuthorize() {
const state = reactive({ const state = reactive({
saving: false, saving: false,
loading: false, loading: false,
projectIds: '',
currentRecord: {} as IRecord | null,
projectWithAuthorizedLevel: [],
authorizedProjects: [] as number[], authorizedProjects: [] as number[],
unauthorizedProjects: [] as IOption[], unauthorizedProjects: [] as IOption[],
authorizedDatasources: [] as number[], authorizedDatasources: [] as number[],
@ -59,26 +63,69 @@ export function useAuthorize() {
fileResources: [] as IResourceOption[], fileResources: [] as IResourceOption[],
udfResources: [] as IResourceOption[], udfResources: [] as IResourceOption[],
authorizedFileResources: [] as number[], authorizedFileResources: [] as number[],
authorizedUdfResources: [] as number[] authorizedUdfResources: [] as number[],
pagination: {
pageSize: 5,
page: 1,
totalPage: 0
},
searchVal: '',
userId: 0
}) })
const getProjects = async (userId: number) => { const getProjects = async (userId: number) => {
if (state.loading) return if (state.loading) return
state.loading = true state.loading = true
const projects = await Promise.all([ if (userId) {
queryAuthorizedProject({ userId }), state.userId = userId
queryUnauthorizedProject({ userId }) }
])
const projectsList = await queryProjectWithAuthorizedLevelListPaging({
userId,
searchVal: state.searchVal,
pageSize: state.pagination.pageSize,
pageNo: state.pagination.page
})
state.loading = false state.loading = false
state.authorizedProjects = projects[0].map( if (!projectsList) throw Error()
(item: { name: string; id: number }) => item.id state.pagination.totalPage = projectsList.totalPage
) state.projectWithAuthorizedLevel = projectsList.totalList
state.unauthorizedProjects = [...projects[0], ...projects[1]].map( return state.projectWithAuthorizedLevel
(item: { name: string; id: number }) => ({ }
label: item.name,
value: item.id 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) => { const getDatasources = async (userId: number) => {
@ -216,12 +263,6 @@ export function useAuthorize() {
const onSave = async (type: TAuthType, userId: number) => { const onSave = async (type: TAuthType, userId: number) => {
if (state.saving) return false if (state.saving) return false
state.saving = true state.saving = true
if (type === 'authorize_project') {
await grantProject({
userId,
projectIds: state.authorizedProjects.join(',')
})
}
if (type === 'authorize_datasource') { if (type === 'authorize_datasource') {
await grantDataSource({ await grantDataSource({
userId, userId,
@ -281,5 +322,5 @@ export function useAuthorize() {
return true return true
} }
return { state, onInit, onSave } return { state, onInit, onSave, getProjects, revokeProjectByIdRequest, grantProjectRequest, grantProjectWithReadPermRequest, requestData, handleChangePageSize }
} }

91
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
}
}

5
dolphinscheduler-ui/src/views/security/user-manage/index.module.scss

@ -18,3 +18,8 @@
.transfer { .transfer {
width: 100%; width: 100%;
} }
.pagination {
margin-top: 10px;
display: flex;
justify-content: center;
}

Loading…
Cancel
Save