This is an automated email from the ASF dual-hosted git repository.

SbloodyS pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new a247ec5f94 [Fix-18559][API] Align workflow mutations with project 
write permissions (#18561)
a247ec5f94 is described below

commit a247ec5f940afee5fb4eb73004872231d67bc794
Author: Wenjun Ruan <[email protected]>
AuthorDate: Wed Aug 19 14:44:18 2026 +0800

    [Fix-18559][API] Align workflow mutations with project write permissions 
(#18561)
---
 .../api/service/impl/ExecutorServiceImpl.java      |  35 +++----
 .../api/service/impl/SchedulerServiceImpl.java     |  30 +++---
 .../service/impl/TaskDefinitionServiceImpl.java    |   7 +-
 .../api/service/impl/TaskInstanceServiceImpl.java  |  14 +--
 .../impl/WorkflowDefinitionServiceImpl.java        |  53 +++++++----
 .../service/impl/WorkflowInstanceServiceImpl.java  |  17 +---
 .../api/service/ExecutorServiceTest.java           |  94 +++++++++++++++---
 .../api/service/ProjectServiceTest.java            |  15 +++
 .../api/service/SchedulerServiceTest.java          |  61 +++++++++++-
 .../api/service/TaskDefinitionServiceImplTest.java |  22 ++++-
 .../api/service/TaskInstanceServiceTest.java       |  52 ++++++++--
 .../api/service/WorkflowDefinitionServiceTest.java | 106 ++++++++++++++++++---
 .../api/service/WorkflowInstanceServiceTest.java   |  48 ++++++++--
 13 files changed, 426 insertions(+), 128 deletions(-)

diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ExecutorServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ExecutorServiceImpl.java
index 8950462eaf..55ec3f3f86 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ExecutorServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ExecutorServiceImpl.java
@@ -22,7 +22,6 @@ import static 
org.apache.dolphinscheduler.common.constants.CommandKeyConstants.C
 import static 
org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_START_NODES;
 import static 
org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_SUB_WORKFLOW_DEFINITION_CODE;
 
-import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant;
 import org.apache.dolphinscheduler.api.dto.workflow.WorkflowBackFillRequest;
 import org.apache.dolphinscheduler.api.dto.workflow.WorkflowTriggerRequest;
 import 
org.apache.dolphinscheduler.api.dto.workflowInstance.WorkflowExecuteResponse;
@@ -125,11 +124,8 @@ public class ExecutorServiceImpl extends BaseServiceImpl 
implements ExecutorServ
     @Override
     @Transactional
     public Integer triggerWorkflowDefinition(final WorkflowTriggerRequest 
triggerRequest) {
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(
-                triggerRequest.getLoginUser(),
-                triggerRequest.getProjectCode(),
-                ApiFuncIdentificationConstant.RERUN);
+        projectService.checkHasProjectWritePermissionThrowException(
+                triggerRequest.getLoginUser(), 
triggerRequest.getProjectCode());
         final TriggerWorkflowDTO triggerWorkflowDTO = 
triggerWorkflowRequestTransformer.transform(triggerRequest);
         // verify the workflow definition belongs to the URL's project
         if (triggerWorkflowDTO.getWorkflowDefinition() == null
@@ -145,11 +141,8 @@ public class ExecutorServiceImpl extends BaseServiceImpl 
implements ExecutorServ
     @Override
     @Transactional
     public List<Integer> backfillWorkflowDefinition(final 
WorkflowBackFillRequest workflowBackFillRequest) {
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(
-                workflowBackFillRequest.getLoginUser(),
-                workflowBackFillRequest.getProjectCode(),
-                ApiFuncIdentificationConstant.RERUN);
+        projectService.checkHasProjectWritePermissionThrowException(
+                workflowBackFillRequest.getLoginUser(), 
workflowBackFillRequest.getProjectCode());
         final BackfillWorkflowDTO backfillWorkflowDTO =
                 
backfillWorkflowRequestTransformer.transform(workflowBackFillRequest);
         // verify the workflow definition belongs to the URL's project
@@ -233,11 +226,7 @@ public class ExecutorServiceImpl extends BaseServiceImpl 
implements ExecutorServ
                 .queryOptionalById(workflowInstanceId)
                 .orElseThrow(() -> new 
ServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST, workflowInstanceId));
 
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(
-                loginUser,
-                workflowInstance.getProjectCode(),
-                ApiFuncIdentificationConstant.map.get(executeType));
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
workflowInstance.getProjectCode());
 
         switch (executeType) {
             case REPEAT_RUNNING:
@@ -295,9 +284,7 @@ public class ExecutorServiceImpl extends BaseServiceImpl 
implements ExecutorServ
 
         WorkflowExecuteResponse response = new WorkflowExecuteResponse();
 
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode,
-                
ApiFuncIdentificationConstant.map.get(ExecuteType.EXECUTE_TASK));
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         WorkflowInstance workflowInstance = 
processService.findWorkflowInstanceDetailById(workflowInstanceId)
                 .orElseThrow(() -> new 
ServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST, workflowInstanceId));
@@ -382,10 +369,12 @@ public class ExecutorServiceImpl extends BaseServiceImpl 
implements ExecutorServ
     public void forceStartTaskInstance(User loginUser, int queueId) {
         TaskGroupQueue taskGroupQueue = 
taskGroupQueueMapper.selectById(queueId);
         // check workflow instance exist
-        
workflowInstanceDao.queryOptionalById(taskGroupQueue.getWorkflowInstanceId())
-                .orElseThrow(
-                        () -> new 
ServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST,
-                                taskGroupQueue.getWorkflowInstanceId()));
+        WorkflowInstance workflowInstance =
+                
workflowInstanceDao.queryOptionalById(taskGroupQueue.getWorkflowInstanceId())
+                        .orElseThrow(
+                                () -> new 
ServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST,
+                                        
taskGroupQueue.getWorkflowInstanceId()));
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
workflowInstance.getProjectCode());
 
         if (taskGroupQueue.getInQueue() == Flag.NO.getCode()) {
             throw new ServiceException(Status.TASK_GROUP_QUEUE_ALREADY_START);
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java
index 7b0ae201c9..ad74cf4b91 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/SchedulerServiceImpl.java
@@ -18,7 +18,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.WORKFLOW_ONLINE_OFFLINE;
 
 import org.apache.dolphinscheduler.api.dto.ScheduleParam;
 import org.apache.dolphinscheduler.api.enums.Status;
@@ -128,8 +127,7 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
 
         Project project = projectDao.queryByCode(projectCode);
 
-        // check project auth
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
null);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         // check workflow define release state
         WorkflowDefinition workflowDefinition = 
workflowDefinitionDao.queryByCode(workflowDefinitionCode).orElse(null);
@@ -205,8 +203,7 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
             throw new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, 
workflowDefinitionCode);
         }
         Project project = 
projectDao.queryByCode(workflowDefinition.getProjectCode());
-        // check project auth
-        this.projectService.checkProjectAndAuthThrowException(loginUser, 
project, null);
+        
this.projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
     }
 
     /**
@@ -240,8 +237,7 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
 
         Project project = projectDao.queryByCode(projectCode);
 
-        // check project auth
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
null);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         // check schedule exists
         Schedule schedule = scheduleDao.queryById(id);
@@ -430,8 +426,7 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
                                                            String tenantCode,
                                                            long 
environmentCode) {
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
null);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         // check schedule exists
         Schedule schedule = 
scheduleDao.queryByWorkflowDefinitionCode(workflowDefinitionCode);
@@ -455,8 +450,9 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
     @Transactional
     @Override
     public void onlineScheduler(User loginUser, Long projectCode, Integer 
schedulerId) {
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode, WORKFLOW_ONLINE_OFFLINE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
         Schedule schedule = scheduleDao.queryById(schedulerId);
+        checkScheduleBelongsToProject(schedule, projectCode);
         doOnlineScheduler(schedule);
     }
 
@@ -492,8 +488,9 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
     @Transactional
     @Override
     public void offlineScheduler(User loginUser, Long projectCode, Integer 
schedulerId) {
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode, WORKFLOW_ONLINE_OFFLINE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
         Schedule schedule = scheduleDao.queryById(schedulerId);
+        checkScheduleBelongsToProject(schedule, projectCode);
         doOfflineScheduler(schedule);
     }
 
@@ -521,6 +518,17 @@ public class SchedulerServiceImpl extends BaseServiceImpl 
implements SchedulerSe
         schedulerApi.deleteScheduleTask(project.getId(), schedule.getId());
     }
 
+    private void checkScheduleBelongsToProject(Schedule schedule, long 
projectCode) {
+        if (schedule == null) {
+            return;
+        }
+        WorkflowDefinition workflowDefinition =
+                
workflowDefinitionDao.queryByCode(schedule.getWorkflowDefinitionCode()).orElse(null);
+        if (workflowDefinition == null || workflowDefinition.getProjectCode() 
!= projectCode) {
+            throw new ServiceException(Status.SCHEDULE_NOT_EXISTS, 
schedule.getId());
+        }
+    }
+
     private Schedule updateSchedule(Schedule schedule, WorkflowDefinition 
workflowDefinition,
                                     String scheduleExpression, WarningType 
warningType, int warningGroupId,
                                     FailureStrategy failureStrategy, Priority 
workflowInstancePriority,
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 4828f9acd5..5c6be5f86c 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
@@ -19,7 +19,6 @@ package org.apache.dolphinscheduler.api.service.impl;
 
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_DEFINITION;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_VERSION_VIEW;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_SWITCH_TO_THIS_VERSION;
 import static 
org.apache.dolphinscheduler.plugin.task.api.TaskPluginManager.checkTaskParameters;
 
 import org.apache.dolphinscheduler.api.enums.Status;
@@ -502,8 +501,7 @@ public class TaskDefinitionServiceImpl extends 
BaseServiceImpl implements TaskDe
     @Override
     public void switchVersion(User loginUser, long projectCode, long taskCode, 
int version) {
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
WORKFLOW_SWITCH_TO_THIS_VERSION);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         if (processService.isTaskOnline(taskCode)) {
             log.warn(
@@ -643,8 +641,7 @@ public class TaskDefinitionServiceImpl extends 
BaseServiceImpl implements TaskDe
     @Override
     public void releaseTaskDefinition(User loginUser, long projectCode, long 
code, ReleaseState releaseState) {
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
null);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         if (null == releaseState) {
             throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR, 
Constants.RELEASE_STATE);
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskInstanceServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskInstanceServiceImpl.java
index bbbd28737d..93e010e248 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskInstanceServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/TaskInstanceServiceImpl.java
@@ -17,7 +17,6 @@
 
 package org.apache.dolphinscheduler.api.service.impl;
 
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.FORCED_SUCCESS;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_INSTANCE;
 
 import org.apache.dolphinscheduler.api.enums.Status;
@@ -196,8 +195,7 @@ public class TaskInstanceServiceImpl extends 
BaseServiceImpl implements TaskInst
     @Transactional
     @Override
     public void forceTaskSuccess(User loginUser, long projectCode, Integer 
taskInstanceId) {
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode, FORCED_SUCCESS);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         TaskInstance task = taskInstanceDao.queryOptionalById(taskInstanceId)
                 .orElseThrow(() -> new 
ServiceException(Status.TASK_INSTANCE_NOT_FOUND));
@@ -235,11 +233,10 @@ public class TaskInstanceServiceImpl extends 
BaseServiceImpl implements TaskInst
         Result result = new Result();
 
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
FORCED_SUCCESS);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         TaskInstance taskInstance = taskInstanceDao.queryById(taskInstanceId);
-        if (taskInstance == null) {
+        if (taskInstance == null || taskInstance.getProjectCode() != 
projectCode) {
             log.error("Task definition can not be found, projectCode:{}, 
taskInstanceId:{}.", projectCode,
                     taskInstanceId);
             putMsg(result, Status.TASK_INSTANCE_NOT_FOUND);
@@ -259,11 +256,10 @@ public class TaskInstanceServiceImpl extends 
BaseServiceImpl implements TaskInst
         Result result = new Result();
 
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
FORCED_SUCCESS);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         TaskInstance taskInstance = taskInstanceDao.queryById(taskInstanceId);
-        if (taskInstance == null) {
+        if (taskInstance == null || taskInstance.getProjectCode() != 
projectCode) {
             log.error("Task definition can not be found, projectCode:{}, 
taskInstanceId:{}.", projectCode,
                     taskInstanceId);
             putMsg(result, Status.TASK_INSTANCE_NOT_FOUND);
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java
index fecf8ec406..d777635ac1 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java
@@ -22,9 +22,6 @@ import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationCon
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_BATCH_COPY;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_CREATE;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_DEFINITION;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_DEFINITION_DELETE;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_ONLINE_OFFLINE;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_SWITCH_TO_THIS_VERSION;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_TREE_VIEW;
 import static 
org.apache.dolphinscheduler.api.enums.Status.WORKFLOW_DEFINITION_NOT_EXIST;
 import static 
org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_SUB_WORKFLOW_DEFINITION_CODE;
@@ -863,8 +860,7 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
                 .orElseThrow(() -> new 
ServiceException(WORKFLOW_DEFINITION_NOT_EXIST, String.valueOf(code)));
 
         Project project = 
projectDao.queryByCode(workflowDefinition.getProjectCode());
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
WORKFLOW_DEFINITION_DELETE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         // Determine if the login user is the owner of the workflow definition
         if (loginUser.getId() != workflowDefinition.getUserId() && 
loginUser.getUserType() != UserType.ADMIN_USER) {
@@ -1264,9 +1260,9 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
                                             long projectCode,
                                             String codes,
                                             long targetProjectCode) {
-        checkParams(loginUser, projectCode, codes, targetProjectCode, 
WORKFLOW_BATCH_COPY);
+        checkParams(loginUser, projectCode, codes, targetProjectCode, 
WORKFLOW_BATCH_COPY, false);
         List<String> failedWorkflowList = new ArrayList<>();
-        doBatchOperateWorkflowDefinition(loginUser, targetProjectCode, 
failedWorkflowList, codes, true);
+        doBatchOperateWorkflowDefinition(loginUser, projectCode, 
targetProjectCode, failedWorkflowList, codes, true);
         checkBatchOperateResult(projectCode, targetProjectCode, 
failedWorkflowList, true);
     }
 
@@ -1285,24 +1281,29 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
                                             long projectCode,
                                             String codes,
                                             long targetProjectCode) {
-        checkParams(loginUser, projectCode, codes, targetProjectCode, 
TASK_DEFINITION_MOVE);
+        checkParams(loginUser, projectCode, codes, targetProjectCode, 
TASK_DEFINITION_MOVE, true);
         if (projectCode == targetProjectCode) {
             log.warn("Project code is same as target project code, 
projectCode:{}.", projectCode);
             return;
         }
 
         List<String> failedWorkflowList = new ArrayList<>();
-        doBatchOperateWorkflowDefinition(loginUser, targetProjectCode, 
failedWorkflowList, codes, false);
+        doBatchOperateWorkflowDefinition(loginUser, projectCode, 
targetProjectCode, failedWorkflowList, codes, false);
         checkBatchOperateResult(projectCode, targetProjectCode, 
failedWorkflowList, false);
     }
 
     private void checkParams(User loginUser,
                              long projectCode,
                              String workflowDefinitionCodes,
-                             long targetProjectCode, String perm) {
+                             long targetProjectCode,
+                             String perm,
+                             boolean requireSourceWritePermission) {
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
perm);
+        if (requireSourceWritePermission) {
+            
projectService.checkHasProjectWritePermissionThrowException(loginUser, project);
+        } else {
+            projectService.checkProjectAndAuthThrowException(loginUser, 
project, perm);
+        }
 
         if (StringUtils.isEmpty(workflowDefinitionCodes)) {
             log.error("Parameter workflowDefinitionCodes is empty, projectCode 
is {}.", projectCode);
@@ -1311,12 +1312,14 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
 
         if (projectCode != targetProjectCode) {
             Project targetProject = projectDao.queryByCode(targetProjectCode);
-            // check user access for project
-            projectService.checkProjectAndAuthThrowException(loginUser, 
targetProject, perm);
+            
projectService.checkHasProjectWritePermissionThrowException(loginUser, 
targetProject);
+        } else if (!requireSourceWritePermission) {
+            
projectService.checkHasProjectWritePermissionThrowException(loginUser, project);
         }
     }
 
     protected void doBatchOperateWorkflowDefinition(User loginUser,
+                                                    long sourceProjectCode,
                                                     long targetProjectCode,
                                                     List<String> 
failedWorkflowList,
                                                     String 
workflowDefinitionCodes,
@@ -1324,13 +1327,16 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
         Set<Long> definitionCodes = 
Arrays.stream(workflowDefinitionCodes.split(Constants.COMMA)).map(Long::parseLong)
                 .collect(Collectors.toSet());
         List<WorkflowDefinition> workflowDefinitionList = 
workflowDefinitionDao.queryByCodes(definitionCodes);
-        Set<Long> queryCodes =
-                
workflowDefinitionList.stream().map(WorkflowDefinition::getCode).collect(Collectors.toSet());
+        List<WorkflowDefinition> sourceWorkflowDefinitionList = 
workflowDefinitionList.stream()
+                .filter(workflowDefinition -> 
workflowDefinition.getProjectCode() == sourceProjectCode)
+                .collect(Collectors.toList());
+        Set<Long> queryCodes = 
sourceWorkflowDefinitionList.stream().map(WorkflowDefinition::getCode)
+                .collect(Collectors.toSet());
         // definitionCodes - queryCodes
         Set<Long> diffCode =
                 definitionCodes.stream().filter(code -> 
!queryCodes.contains(code)).collect(Collectors.toSet());
         diffCode.forEach(code -> failedWorkflowList.add(code + "[null]"));
-        for (WorkflowDefinition workflowDefinition : workflowDefinitionList) {
+        for (WorkflowDefinition workflowDefinition : 
sourceWorkflowDefinitionList) {
             List<WorkflowTaskRelation> workflowTaskRelations =
                     
workflowTaskRelationDao.queryByWorkflowDefinitionCode(workflowDefinition.getCode());
             List<WorkflowTaskRelationLog> taskRelationList =
@@ -1563,8 +1569,7 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
     public void switchWorkflowDefinitionVersion(User loginUser, long 
projectCode, long code,
                                                 int version) {
         Project project = projectDao.queryByCode(projectCode);
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project, 
WORKFLOW_SWITCH_TO_THIS_VERSION);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
 
         WorkflowDefinition workflowDefinition = 
workflowDefinitionDao.queryByCode(code).orElse(null);
         if (Objects.isNull(workflowDefinition) || projectCode != 
workflowDefinition.getProjectCode()) {
@@ -1737,10 +1742,13 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
     @Transactional
     @Override
     public void onlineWorkflowDefinition(User loginUser, Long projectCode, 
Long workflowDefinitionCode) {
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode, WORKFLOW_ONLINE_OFFLINE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         WorkflowDefinition workflowDefinition = 
workflowDefinitionDao.queryByCode(workflowDefinitionCode)
                 .orElseThrow(() -> new 
ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode));
+        if (projectCode != workflowDefinition.getProjectCode()) {
+            throw new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, 
workflowDefinitionCode);
+        }
 
         if (ReleaseState.ONLINE.equals(workflowDefinition.getReleaseState())) {
             // do nothing if the workflow is already online
@@ -1757,10 +1765,13 @@ public class WorkflowDefinitionServiceImpl extends 
BaseServiceImpl implements Wo
     @Transactional
     @Override
     public void offlineWorkflowDefinition(User loginUser, Long projectCode, 
Long workflowDefinitionCode) {
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode, WORKFLOW_ONLINE_OFFLINE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         WorkflowDefinition workflowDefinition = 
workflowDefinitionDao.queryByCode(workflowDefinitionCode)
                 .orElseThrow(() -> new 
ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, workflowDefinitionCode));
+        if (projectCode != workflowDefinition.getProjectCode()) {
+            throw new ServiceException(Status.WORKFLOW_DEFINITION_NOT_EXIST, 
workflowDefinitionCode);
+        }
 
         if (ReleaseState.OFFLINE.equals(workflowDefinition.getReleaseState())) 
{
             // do nothing if the workflow is already offline
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowInstanceServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowInstanceServiceImpl.java
index 7ae496636b..96cfde7061 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowInstanceServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowInstanceServiceImpl.java
@@ -26,7 +26,6 @@ import static 
org.apache.dolphinscheduler.common.constants.Constants.SUBWORKFLOW
 import static org.apache.dolphinscheduler.common.utils.JSONUtils.parseObject;
 import static 
org.apache.dolphinscheduler.plugin.task.api.TaskPluginManager.checkTaskParameters;
 
-import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant;
 import org.apache.dolphinscheduler.api.dto.gantt.GanttDto;
 import org.apache.dolphinscheduler.api.dto.gantt.Task;
 import 
org.apache.dolphinscheduler.api.dto.workflowInstance.WorkflowInstanceTaskListDTO;
@@ -369,18 +368,14 @@ public class WorkflowInstanceServiceImpl extends 
BaseServiceImpl implements Work
                                                      Boolean syncDefine,
                                                      String globalParams,
                                                      String locations, int 
timeout) {
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, 
projectCode,
-                ApiFuncIdentificationConstant.INSTANCE_UPDATE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
         // check workflow instance exists
         WorkflowInstance workflowInstance = 
processService.findWorkflowInstanceDetailById(workflowInstanceId)
                 .orElseThrow(() -> new 
ServiceException(WORKFLOW_INSTANCE_NOT_EXIST, workflowInstanceId));
         // check workflow instance exists in project
-        WorkflowDefinition workflowDefinition0 =
-                
workflowDefinitionDao.queryByCode(workflowInstance.getWorkflowDefinitionCode()).orElse(null);
-        if (workflowDefinition0 != null && projectCode != 
workflowDefinition0.getProjectCode()) {
-            log.error("workflow definition does not exist, projectCode:{}, 
workflowDefinitionCode:{}.", projectCode,
-                    workflowInstance.getWorkflowDefinitionCode());
+        if (workflowInstance.getProjectCode() != projectCode) {
+            log.error("workflow instance does not exist, projectCode:{}, 
workflowInstanceId:{}.", projectCode,
+                    workflowInstanceId);
             throw new ServiceException(WORKFLOW_INSTANCE_NOT_EXIST, 
workflowInstanceId);
         }
         // check workflow instance status
@@ -519,9 +514,7 @@ public class WorkflowInstanceServiceImpl extends 
BaseServiceImpl implements Work
                 workflowInstance.getWorkflowDefinitionCode(), 
workflowInstance.getWorkflowDefinitionVersion());
 
         Project project = 
projectDao.queryByCode(workflowDefinition.getProjectCode());
-        // check user access for project
-        projectService.checkProjectAndAuthThrowException(loginUser, project,
-                ApiFuncIdentificationConstant.INSTANCE_DELETE);
+        projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project);
         // check workflow instance status
         if (!workflowInstance.getState().isFinalState()) {
             log.warn("workflow Instance state is {} so can not delete workflow 
instance, workflowInstanceId:{}.",
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorServiceTest.java
index c8b8b6db12..d820926e9e 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ExecutorServiceTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.dolphinscheduler.api.service;
 
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.RERUN;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.doNothing;
@@ -28,6 +27,7 @@ import static org.mockito.Mockito.when;
 
 import org.apache.dolphinscheduler.api.dto.workflow.WorkflowBackFillRequest;
 import org.apache.dolphinscheduler.api.dto.workflow.WorkflowTriggerRequest;
+import org.apache.dolphinscheduler.api.enums.ExecuteType;
 import org.apache.dolphinscheduler.api.enums.Status;
 import org.apache.dolphinscheduler.api.exceptions.ServiceException;
 import 
org.apache.dolphinscheduler.api.executor.workflow.BackfillWorkflowExecutorDelegate;
@@ -41,9 +41,17 @@ import 
org.apache.dolphinscheduler.api.validator.workflow.BackfillWorkflowReques
 import org.apache.dolphinscheduler.api.validator.workflow.TriggerWorkflowDTO;
 import 
org.apache.dolphinscheduler.api.validator.workflow.TriggerWorkflowDTOValidator;
 import 
org.apache.dolphinscheduler.api.validator.workflow.TriggerWorkflowRequestTransformer;
+import org.apache.dolphinscheduler.common.enums.TaskDependType;
 import org.apache.dolphinscheduler.common.enums.UserType;
+import org.apache.dolphinscheduler.dao.entity.TaskGroupQueue;
 import org.apache.dolphinscheduler.dao.entity.User;
 import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
+import org.apache.dolphinscheduler.dao.entity.WorkflowInstance;
+import org.apache.dolphinscheduler.dao.mapper.TaskGroupQueueMapper;
+import org.apache.dolphinscheduler.dao.repository.WorkflowInstanceDao;
+import org.apache.dolphinscheduler.service.process.ProcessService;
+
+import java.util.Optional;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -85,6 +93,15 @@ public class ExecutorServiceTest {
     @Mock
     private BackfillWorkflowExecutorDelegate backfillWorkflowExecutorDelegate;
 
+    @Mock
+    private WorkflowInstanceDao workflowInstanceDao;
+
+    @Mock
+    private TaskGroupQueueMapper taskGroupQueueMapper;
+
+    @Mock
+    private ProcessService processService;
+
     private User getLoginUser() {
         User user = new User();
         user.setId(1);
@@ -94,7 +111,7 @@ public class ExecutorServiceTest {
     }
 
     @Test
-    public void testTriggerWorkflowDefinition_userHasNoProjectPermission() {
+    public void 
testTriggerWorkflowDefinition_userHasNoProjectWritePermission() {
         long projectCode = 100L;
         long workflowDefinitionCode = 200L;
         User loginUser = getLoginUser();
@@ -105,14 +122,14 @@ public class ExecutorServiceTest {
                 .workflowDefinitionCode(workflowDefinitionCode)
                 .build();
 
-        doThrow(new ServiceException(Status.USER_NO_OPERATION_PROJECT_PERM,
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
                 loginUser.getUserName(), projectCode))
                         .when(projectService)
-                        .checkProjectAndAuthThrowException(loginUser, 
projectCode, RERUN);
+                        
.checkHasProjectWritePermissionThrowException(loginUser, projectCode);
 
         ServiceException ex = assertThrows(ServiceException.class,
                 () -> executorService.triggerWorkflowDefinition(request));
-        assertEquals(Status.USER_NO_OPERATION_PROJECT_PERM.getCode(), 
ex.getCode());
+        assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
 
         verify(triggerWorkflowRequestTransformer, 
never()).transform(Mockito.any());
         verify(triggerWorkflowDTOValidator, never()).validate(Mockito.any());
@@ -132,7 +149,7 @@ public class ExecutorServiceTest {
                 .build();
 
         doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(loginUser, projectCode, 
RERUN);
+                .checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         WorkflowDefinition workflowDefinition = new WorkflowDefinition();
         workflowDefinition.setCode(workflowDefinitionCode);
@@ -151,7 +168,7 @@ public class ExecutorServiceTest {
     }
 
     @Test
-    public void testBackfillWorkflowDefinition_userHasNoProjectPermission() {
+    public void 
testBackfillWorkflowDefinition_userHasNoProjectWritePermission() {
         long projectCode = 100L;
         long workflowDefinitionCode = 200L;
         User loginUser = getLoginUser();
@@ -162,14 +179,14 @@ public class ExecutorServiceTest {
                 .workflowDefinitionCode(workflowDefinitionCode)
                 .build();
 
-        doThrow(new ServiceException(Status.USER_NO_OPERATION_PROJECT_PERM,
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
                 loginUser.getUserName(), projectCode))
                         .when(projectService)
-                        .checkProjectAndAuthThrowException(loginUser, 
projectCode, RERUN);
+                        
.checkHasProjectWritePermissionThrowException(loginUser, projectCode);
 
         ServiceException ex = assertThrows(ServiceException.class,
                 () -> executorService.backfillWorkflowDefinition(request));
-        assertEquals(Status.USER_NO_OPERATION_PROJECT_PERM.getCode(), 
ex.getCode());
+        assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
 
         verify(backfillWorkflowRequestTransformer, 
never()).transform(Mockito.any());
         verify(backfillWorkflowDTOValidator, never()).validate(Mockito.any());
@@ -189,7 +206,7 @@ public class ExecutorServiceTest {
                 .build();
 
         doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(loginUser, projectCode, 
RERUN);
+                .checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
 
         WorkflowDefinition workflowDefinition = new WorkflowDefinition();
         workflowDefinition.setCode(workflowDefinitionCode);
@@ -206,4 +223,59 @@ public class ExecutorServiceTest {
 
         verify(backfillWorkflowDTOValidator, never()).validate(Mockito.any());
     }
+
+    @Test
+    public void testControlWorkflowInstance_userHasNoProjectWritePermission() {
+        long projectCode = 100L;
+        int workflowInstanceId = 1;
+        User loginUser = getLoginUser();
+        WorkflowInstance workflowInstance = new WorkflowInstance();
+        workflowInstance.setProjectCode(projectCode);
+        
when(workflowInstanceDao.queryOptionalById(workflowInstanceId)).thenReturn(Optional.of(workflowInstance));
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+                () -> executorService.controlWorkflowInstance(
+                        loginUser, workflowInstanceId, 
ExecuteType.REPEAT_RUNNING));
+
+        assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
+        Mockito.verifyNoInteractions(executorClient);
+    }
+
+    @Test
+    public void testExecuteTask_userHasNoProjectWritePermission() {
+        long projectCode = 100L;
+        User loginUser = getLoginUser();
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+                () -> executorService.executeTask(loginUser, projectCode, 1, 
"1", TaskDependType.TASK_POST));
+
+        assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
+        Mockito.verifyNoInteractions(processService);
+    }
+
+    @Test
+    public void testForceStartTaskInstance_userHasNoProjectWritePermission() {
+        long projectCode = 100L;
+        int workflowInstanceId = 1;
+        int queueId = 2;
+        User loginUser = getLoginUser();
+        TaskGroupQueue taskGroupQueue = new TaskGroupQueue();
+        taskGroupQueue.setWorkflowInstanceId(workflowInstanceId);
+        WorkflowInstance workflowInstance = new WorkflowInstance();
+        workflowInstance.setProjectCode(projectCode);
+        
when(taskGroupQueueMapper.selectById(queueId)).thenReturn(taskGroupQueue);
+        
when(workflowInstanceDao.queryOptionalById(workflowInstanceId)).thenReturn(Optional.of(workflowInstance));
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+                () -> executorService.forceStartTaskInstance(loginUser, 
queueId));
+
+        assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
+        verify(taskGroupQueueMapper, never()).updateById(Mockito.any());
+    }
 }
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 66fbbebe75..988c83784a 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
@@ -31,9 +31,11 @@ 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;
 import org.apache.dolphinscheduler.common.enums.UserType;
 import org.apache.dolphinscheduler.dao.entity.Project;
+import org.apache.dolphinscheduler.dao.entity.ProjectUser;
 import org.apache.dolphinscheduler.dao.entity.User;
 import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
 import org.apache.dolphinscheduler.dao.repository.ProjectDao;
@@ -170,6 +172,19 @@ public class ProjectServiceTest {
                 () -> 
projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project));
         Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
noWriteEx.getCode());
 
+        // USER_NO_WRITE_PROJECT_PERM: read-only project member
+        ProjectUser projectUser = new ProjectUser();
+        projectUser.setPerm(Constants.READ_PERMISSION);
+        Mockito.when(projectUserDao.queryProjectRelation(project.getId(), 
loginUser.getId())).thenReturn(projectUser);
+        ServiceException readOnlyEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> 
projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project));
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
readOnlyEx.getCode());
+
+        // success: project member with write permission
+        projectUser.setPerm(Constants.DEFAULT_ADMIN_PERMISSION);
+        Assertions.assertDoesNotThrow(
+                () -> 
projectService.checkHasProjectWritePermissionThrowException(loginUser, 
project));
+
         // success: admin
         loginUser.setUserType(UserType.ADMIN_USER);
         Assertions.assertDoesNotThrow(
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java
index 3c9a9dedc6..67572905ac 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/SchedulerServiceTest.java
@@ -303,16 +303,17 @@ public class SchedulerServiceTest extends 
BaseServiceTestTool {
         // error project permissions
         Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode))
                 .thenReturn(Optional.of(this.getProcessDefinition()));
-        
Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(this.getProject());
-        Mockito.doThrow(new 
ServiceException(Status.USER_NO_OPERATION_PROJECT_PERM)).when(projectService)
-                .checkProjectAndAuthThrowException(user, this.getProject(), 
null);
+        Project project = this.getProject();
+        Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        Mockito.doThrow(new 
ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)).when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, project);
         exception = Assertions.assertThrows(ServiceException.class,
                 () -> schedulerService.deleteSchedulesById(user, scheduleId));
-        
Assertions.assertEquals(Status.USER_NO_OPERATION_PROJECT_PERM.getCode(),
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(),
                 ((ServiceException) exception).getCode());
 
         // error delete mapper
-        
Mockito.doNothing().when(projectService).checkProjectAndAuthThrowException(user,
 this.getProject(), null);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project);
         Mockito.when(scheduleDao.deleteById(scheduleId)).thenReturn(false);
         exception = Assertions.assertThrows(ServiceException.class,
                 () -> schedulerService.deleteSchedulesById(user, scheduleId));
@@ -323,6 +324,56 @@ public class SchedulerServiceTest extends 
BaseServiceTestTool {
         Assertions.assertDoesNotThrow(() -> 
schedulerService.deleteSchedulesById(user, scheduleId));
     }
 
+    @Test
+    public void testReadOnlyUserCannotChangeSchedule() {
+        Project project = this.getProject();
+        Mockito.when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        Mockito.doThrow(new 
ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)).when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, project);
+        Mockito.doThrow(new 
ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)).when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, 
projectCode);
+
+        Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.insertSchedule(
+                        user, projectCode, processDefinitionCode, 
scheduleExpression(null), WarningType.NONE, 0,
+                        FailureStrategy.CONTINUE, Priority.MEDIUM, "default", 
"tenantCode", environmentCode));
+        Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.updateSchedule(
+                        user, projectCode, scheduleId, 
scheduleExpression(null), WarningType.NONE, 0,
+                        FailureStrategy.CONTINUE, Priority.MEDIUM, "default", 
"tenantCode", environmentCode));
+        Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.updateScheduleByWorkflowDefinitionCode(
+                        user, projectCode, processDefinitionCode, 
scheduleExpression(null), WarningType.NONE, 0,
+                        FailureStrategy.CONTINUE, Priority.MEDIUM, "default", 
"tenantCode", environmentCode));
+        Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.onlineScheduler(user, projectCode, 
scheduleId));
+        Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.offlineScheduler(user, projectCode, 
scheduleId));
+
+        Mockito.verify(scheduleDao, Mockito.never()).insert(Mockito.any());
+        Mockito.verify(scheduleDao, Mockito.never()).updateById(Mockito.any());
+    }
+
+    @Test
+    public void testProjectCodeCannotAuthorizeScheduleFromAnotherProject() {
+        Schedule schedule = this.getSchedule();
+        WorkflowDefinition workflowDefinition = this.getProcessDefinition();
+        workflowDefinition.setProjectCode(2L);
+        Mockito.when(scheduleDao.queryById(scheduleId)).thenReturn(schedule);
+        Mockito.when(workflowDefinitionDao.queryByCode(processDefinitionCode))
+                .thenReturn(Optional.of(workflowDefinition));
+
+        ServiceException onlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.onlineScheduler(user, projectCode, 
scheduleId));
+        ServiceException offlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> schedulerService.offlineScheduler(user, projectCode, 
scheduleId));
+
+        Assertions.assertEquals(Status.SCHEDULE_NOT_EXISTS.getCode(), 
onlineEx.getCode());
+        Assertions.assertEquals(Status.SCHEDULE_NOT_EXISTS.getCode(), 
offlineEx.getCode());
+        Mockito.verify(scheduleDao, Mockito.never()).updateById(Mockito.any());
+        Mockito.verifyNoInteractions(schedulerApi);
+    }
+
     private Project getProject() {
         Project project = new Project();
         project.setId(1);
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 c749e77e70..d85ee6ef3f 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
@@ -19,7 +19,6 @@ package org.apache.dolphinscheduler.api.service;
 
 import static 
org.apache.dolphinscheduler.api.AssertionsHelper.assertThrowsServiceException;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_DEFINITION;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_SWITCH_TO_THIS_VERSION;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -169,8 +168,7 @@ public class TaskDefinitionServiceImplTest {
     public void switchVersion() {
         Project project = getProject();
         when(projectDao.queryByCode(PROJECT_CODE)).thenReturn(project);
-        Mockito.doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(user, project, 
WORKFLOW_SWITCH_TO_THIS_VERSION);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project);
 
         
when(taskDefinitionLogMapper.queryByDefinitionCodeAndVersion(TASK_CODE, 
VERSION))
                 .thenReturn(new TaskDefinitionLog());
@@ -255,7 +253,7 @@ public class TaskDefinitionServiceImplTest {
     public void testReleaseTaskDefinition() {
         when(projectDao.queryByCode(PROJECT_CODE)).thenReturn(getProject());
         Project project = getProject();
-        
Mockito.doNothing().when(projectService).checkProjectAndAuthThrowException(user,
 project, null);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project);
 
         // check task dose not exist
         assertThrowsServiceException(Status.TASK_DEFINE_NOT_EXIST,
@@ -287,6 +285,22 @@ public class TaskDefinitionServiceImplTest {
                         ReleaseState.getEnum(2)));
     }
 
+    @Test
+    public void testReadOnlyUserCannotChangeTaskDefinition() {
+        Project project = getProject();
+        when(projectDao.queryByCode(PROJECT_CODE)).thenReturn(project);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(user, 
project);
+
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
+                () -> taskDefinitionService.switchVersion(user, PROJECT_CODE, 
TASK_CODE, VERSION));
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
+                () -> taskDefinitionService.releaseTaskDefinition(
+                        user, PROJECT_CODE, TASK_CODE, ReleaseState.ONLINE));
+
+        Mockito.verifyNoInteractions(taskDefinitionDao, 
taskDefinitionLogMapper);
+    }
+
     @Test
     public void testUpdateDag() {
         User loginUser = getLoginUser();
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskInstanceServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskInstanceServiceTest.java
index a977a3a6c1..d7f5771121 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskInstanceServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/TaskInstanceServiceTest.java
@@ -18,7 +18,6 @@
 package org.apache.dolphinscheduler.api.service;
 
 import static 
org.apache.dolphinscheduler.api.AssertionsHelper.assertThrowsServiceException;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.FORCED_SUCCESS;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_INSTANCE;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -336,17 +335,19 @@ public class TaskInstanceServiceTest {
     public void testForceTaskSuccess_withNoPermission() {
         User user = getAdminUser();
         TaskInstance task = getTaskInstance();
-        doThrow(new 
ServiceException(Status.USER_NO_OPERATION_PROJECT_PERM)).when(projectService)
-                .checkProjectAndAuthThrowException(user, 
task.getProjectCode(), FORCED_SUCCESS);
-        assertThrowsServiceException(Status.USER_NO_OPERATION_PROJECT_PERM,
+        doThrow(new 
ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)).when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, 
task.getProjectCode());
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
                 () -> taskInstanceService.forceTaskSuccess(user, 
task.getProjectCode(), task.getId()));
+        Mockito.verifyNoInteractions(taskInstanceDao);
     }
 
     @Test
     public void testForceTaskSuccess_withTaskInstanceNotFound() {
         User user = getAdminUser();
         TaskInstance task = getTaskInstance();
-        
doNothing().when(projectService).checkProjectAndAuthThrowException(user, 
task.getProjectCode(), FORCED_SUCCESS);
+        doNothing().when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, 
task.getProjectCode());
         
when(taskInstanceDao.queryOptionalById(task.getId())).thenReturn(Optional.empty());
         assertThrowsServiceException(Status.TASK_INSTANCE_NOT_FOUND,
                 () -> taskInstanceService.forceTaskSuccess(user, 
task.getProjectCode(), task.getId()));
@@ -356,7 +357,8 @@ public class TaskInstanceServiceTest {
     public void testForceTaskSuccess_withWorkflowInstanceNotFound() {
         User user = getAdminUser();
         TaskInstance task = getTaskInstance();
-        
doNothing().when(projectService).checkProjectAndAuthThrowException(user, 
task.getProjectCode(), FORCED_SUCCESS);
+        doNothing().when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, 
task.getProjectCode());
         
when(taskInstanceDao.queryOptionalById(task.getId())).thenReturn(Optional.of(task));
         
when(workflowInstanceDao.queryOptionalById(task.getWorkflowInstanceId())).thenReturn(Optional.empty());
 
@@ -371,7 +373,7 @@ public class TaskInstanceServiceTest {
         TaskInstance task = getTaskInstance();
         WorkflowInstance workflowInstance = getProcessInstance();
         workflowInstance.setState(WorkflowExecutionStatus.RUNNING_EXECUTION);
-        
doNothing().when(projectService).checkProjectAndAuthThrowException(user, 
projectCode, FORCED_SUCCESS);
+        
doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 projectCode);
         
when(taskInstanceDao.queryOptionalById(task.getId())).thenReturn(Optional.of(task));
         
when(workflowInstanceDao.queryOptionalById(task.getWorkflowInstanceId()))
                 .thenReturn(Optional.of(workflowInstance));
@@ -388,7 +390,8 @@ public class TaskInstanceServiceTest {
         TaskInstance task = getTaskInstance();
         WorkflowInstance workflowInstance = getProcessInstance();
         workflowInstance.setState(WorkflowExecutionStatus.FAILURE);
-        
doNothing().when(projectService).checkProjectAndAuthThrowException(user, 
task.getProjectCode(), FORCED_SUCCESS);
+        doNothing().when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, 
task.getProjectCode());
         
when(taskInstanceDao.queryOptionalById(task.getId())).thenReturn(Optional.of(task));
         
when(workflowInstanceDao.queryOptionalById(task.getWorkflowInstanceId()))
                 .thenReturn(Optional.of(workflowInstance));
@@ -398,4 +401,37 @@ public class TaskInstanceServiceTest {
                 () -> taskInstanceService.forceTaskSuccess(user, 
task.getProjectCode(), task.getId()));
     }
 
+    @Test
+    public void testReadOnlyUserCannotSavepointOrStopTask() {
+        long projectCode = 1L;
+        User user = getAdminUser();
+        Project project = getProject(projectCode);
+        when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(user, 
project);
+
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
+                () -> taskInstanceService.taskSavePoint(user, projectCode, 1));
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
+                () -> taskInstanceService.stopTask(user, projectCode, 1));
+        Mockito.verifyNoInteractions(taskInstanceDao);
+    }
+
+    @Test
+    public void testProjectCodeCannotAuthorizeTaskFromAnotherProject() {
+        long projectCode = 1L;
+        User user = getAdminUser();
+        Project project = getProject(projectCode);
+        TaskInstance taskInstance = getTaskInstance();
+        taskInstance.setProjectCode(2L);
+        when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        
when(taskInstanceDao.queryById(taskInstance.getId())).thenReturn(taskInstance);
+
+        Result savepointResult = taskInstanceService.taskSavePoint(user, 
projectCode, taskInstance.getId());
+        Result stopResult = taskInstanceService.stopTask(user, projectCode, 
taskInstance.getId());
+
+        Assertions.assertEquals(Status.TASK_INSTANCE_NOT_FOUND.getCode(), 
savepointResult.getCode());
+        Assertions.assertEquals(Status.TASK_INSTANCE_NOT_FOUND.getCode(), 
stopResult.getCode());
+    }
+
 }
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java
index 3ea6da8fb3..b26d0da000 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowDefinitionServiceTest.java
@@ -17,11 +17,9 @@
 
 package org.apache.dolphinscheduler.api.service;
 
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.TASK_DEFINITION_MOVE;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_BATCH_COPY;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_CREATE;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_DEFINITION;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_DEFINITION_DELETE;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_TREE_VIEW;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -469,14 +467,14 @@ public class WorkflowDefinitionServiceTest extends 
BaseServiceTestTool {
         Assertions.assertEquals(Status.PROJECT_NOT_FOUND.getCode(), 
ex.getCode());
 
         // project check auth success, target project name not equal project 
name
-        Mockito.doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(user, project, 
WORKFLOW_BATCH_COPY);
         Project project1 = getProject(projectCodeOther);
         when(projectDao.queryByCode(projectCodeOther)).thenReturn(project1);
         Mockito.doNothing().when(projectService)
                 .checkProjectAndAuthThrowException(user, project1, 
WORKFLOW_BATCH_COPY);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project);
 
         WorkflowDefinition definition = getWorkflowDefinition();
+        definition.setProjectCode(projectCodeOther);
         List<WorkflowDefinition> workflowDefinitionList = new ArrayList<>();
         workflowDefinitionList.add(definition);
         Set<Long> definitionCodes = new HashSet<>();
@@ -503,10 +501,8 @@ public class WorkflowDefinitionServiceTest extends 
BaseServiceTestTool {
         Project project2 = getProject(projectCodeOther);
         when(projectDao.queryByCode(projectCodeOther)).thenReturn(project2);
 
-        Mockito.doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(user, project1, 
TASK_DEFINITION_MOVE);
-        Mockito.doNothing().when(projectService)
-                .checkProjectAndAuthThrowException(user, project2, 
TASK_DEFINITION_MOVE);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project1);
+        
Mockito.doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project2);
 
         WorkflowDefinition definition = getWorkflowDefinition();
         definition.setVersion(1);
@@ -531,6 +527,91 @@ public class WorkflowDefinitionServiceTest extends 
BaseServiceTestTool {
                 user, projectCode, String.valueOf(processDefinitionCode), 
projectCodeOther));
     }
 
+    @Test
+    public void 
testBatchCopyWorkflowDefinitionRequiresTargetProjectWritePermission() {
+        Project sourceProject = getProject(projectCode);
+        Project targetProject = getProject(projectCodeOther);
+        when(projectDao.queryByCode(projectCode)).thenReturn(sourceProject);
+        
when(projectDao.queryByCode(projectCodeOther)).thenReturn(targetProject);
+        doNothing().when(projectService)
+                .checkProjectAndAuthThrowException(user, sourceProject, 
WORKFLOW_BATCH_COPY);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(user, 
targetProject);
+
+        ServiceException ex = Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.batchCopyWorkflowDefinition(
+                        user, projectCode, 
String.valueOf(processDefinitionCode), projectCodeOther));
+
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
ex.getCode());
+        verify(workflowDefinitionDao, 
Mockito.never()).queryByCodes(Mockito.anySet());
+    }
+
+    @Test
+    public void 
testBatchMoveWorkflowDefinitionRejectsDefinitionOutsideSourceProject() {
+        Project sourceProject = getProject(projectCode);
+        Project targetProject = getProject(projectCodeOther);
+        when(projectDao.queryByCode(projectCode)).thenReturn(sourceProject);
+        
when(projectDao.queryByCode(projectCodeOther)).thenReturn(targetProject);
+        
doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 sourceProject);
+        
doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 targetProject);
+
+        WorkflowDefinition definition = getWorkflowDefinition();
+        definition.setProjectCode(3L);
+        
when(workflowDefinitionDao.queryByCodes(Collections.singleton(processDefinitionCode)))
+                .thenReturn(Collections.singletonList(definition));
+
+        ServiceException ex = Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.batchMoveWorkflowDefinition(
+                        user, projectCode, 
String.valueOf(processDefinitionCode), projectCodeOther));
+
+        
Assertions.assertEquals(Status.MOVE_WORKFLOW_DEFINITION_ERROR.getCode(), 
ex.getCode());
+        verify(workflowTaskRelationDao, 
Mockito.never()).queryByWorkflowDefinitionCode(Mockito.anyLong());
+    }
+
+    @Test
+    public void testReadOnlyUserCannotSwitchOrReleaseWorkflowDefinition() {
+        Project project = getProject(projectCode);
+        when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(user, 
project);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(user, 
projectCode);
+
+        ServiceException switchEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> 
workflowDefinitionService.switchWorkflowDefinitionVersion(
+                        user, projectCode, processDefinitionCode, 1));
+        ServiceException onlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.onlineWorkflowDefinition(
+                        user, projectCode, processDefinitionCode));
+        ServiceException offlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.offlineWorkflowDefinition(
+                        user, projectCode, processDefinitionCode));
+
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
switchEx.getCode());
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
onlineEx.getCode());
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
offlineEx.getCode());
+        verify(workflowDefinitionDao, 
Mockito.never()).updateById(Mockito.any());
+    }
+
+    @Test
+    public void 
testProjectCodeCannotAuthorizeWorkflowDefinitionFromAnotherProject() {
+        WorkflowDefinition workflowDefinition = getWorkflowDefinition();
+        workflowDefinition.setProjectCode(projectCodeOther);
+        when(workflowDefinitionDao.queryByCode(processDefinitionCode))
+                .thenReturn(Optional.of(workflowDefinition));
+
+        ServiceException onlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.onlineWorkflowDefinition(
+                        user, projectCode, processDefinitionCode));
+        ServiceException offlineEx = 
Assertions.assertThrows(ServiceException.class,
+                () -> workflowDefinitionService.offlineWorkflowDefinition(
+                        user, projectCode, processDefinitionCode));
+
+        
Assertions.assertEquals(Status.WORKFLOW_DEFINITION_NOT_EXIST.getCode(), 
onlineEx.getCode());
+        
Assertions.assertEquals(Status.WORKFLOW_DEFINITION_NOT_EXIST.getCode(), 
offlineEx.getCode());
+        verify(workflowDefinitionDao, 
Mockito.never()).updateById(Mockito.any());
+    }
+
     @Test
     public void deleteWorkflowDefinitionByCodeTest() {
         
when(projectDao.queryByCode(projectCode)).thenReturn(getProject(projectCode));
@@ -545,15 +626,14 @@ public class WorkflowDefinitionServiceTest extends 
BaseServiceTestTool {
 
         // project check auth fail
         
when(workflowDefinitionDao.queryByCode(6L)).thenReturn(Optional.of(getWorkflowDefinition()));
-        doThrow(new 
ServiceException(Status.PROJECT_NOT_FOUND)).when(projectService)
-                .checkProjectAndAuthThrowException(user, project, 
WORKFLOW_DEFINITION_DELETE);
+        doThrow(new 
ServiceException(Status.USER_NO_WRITE_PROJECT_PERM)).when(projectService)
+                .checkHasProjectWritePermissionThrowException(user, project);
         exception = Assertions.assertThrows(ServiceException.class,
                 () -> 
workflowDefinitionService.deleteWorkflowDefinitionByCode(user, 6L));
-        Assertions.assertEquals(Status.PROJECT_NOT_FOUND.getCode(), 
((ServiceException) exception).getCode());
+        Assertions.assertEquals(Status.USER_NO_WRITE_PROJECT_PERM.getCode(), 
((ServiceException) exception).getCode());
 
         // project check auth success, instance not exist
-        
doNothing().when(projectService).checkProjectAndAuthThrowException(user, 
project,
-                WORKFLOW_DEFINITION_DELETE);
+        
doNothing().when(projectService).checkHasProjectWritePermissionThrowException(user,
 project);
         
when(workflowDefinitionDao.queryByCode(1L)).thenReturn(Optional.empty());
         exception = Assertions.assertThrows(ServiceException.class,
                 () -> 
workflowDefinitionService.deleteWorkflowDefinitionByCode(user, 1L));
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowInstanceServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowInstanceServiceTest.java
index 88e9051ffb..b066cad2a8 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowInstanceServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/WorkflowInstanceServiceTest.java
@@ -18,7 +18,6 @@
 package org.apache.dolphinscheduler.api.service;
 
 import static 
org.apache.dolphinscheduler.api.AssertionsHelper.assertThrowsServiceException;
-import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.INSTANCE_UPDATE;
 import static 
org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_INSTANCE;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.eq;
@@ -531,21 +530,22 @@ public class WorkflowInstanceServiceTest {
         User loginUser = getAdminUser();
         Project project = getProject(projectCode);
 
-        // project auth fail
+        // project write permission fail
         when(projectDao.queryByCode(projectCode)).thenReturn(project);
-        doThrow(new ServiceException(Status.PROJECT_NOT_FOUND, projectCode))
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM, 
projectCode))
                 .when(projectService)
-                .checkProjectAndAuthThrowException(loginUser, projectCode, 
INSTANCE_UPDATE);
-        assertThrowsServiceException(Status.PROJECT_NOT_FOUND,
+                .checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
                 () -> 
workflowInstanceService.updateWorkflowInstance(loginUser, projectCode, 1,
                         shellJson, taskJson, "2020-02-21 00:00:00", true, "", 
"", 0));
 
         // process instance null
         WorkflowInstance workflowInstance = getProcessInstance();
+        workflowInstance.setProjectCode(projectCode);
         when(projectDao.queryByCode(projectCode)).thenReturn(project);
         doNothing()
                 .when(projectService)
-                .checkProjectAndAuthThrowException(loginUser, projectCode, 
INSTANCE_UPDATE);
+                .checkHasProjectWritePermissionThrowException(loginUser, 
projectCode);
         
when(processService.findWorkflowInstanceDetailById(1)).thenReturn(Optional.empty());
         assertThrowsServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST,
                 () -> 
workflowInstanceService.updateWorkflowInstance(loginUser, projectCode, 1,
@@ -617,6 +617,20 @@ public class WorkflowInstanceServiceTest {
         }
     }
 
+    @Test
+    public void testUpdateWorkflowInstanceWithMismatchedProjectCode() {
+        long projectCode = 1L;
+        User loginUser = getAdminUser();
+        WorkflowInstance workflowInstance = getProcessInstance();
+        workflowInstance.setProjectCode(2L);
+        
when(processService.findWorkflowInstanceDetailById(1)).thenReturn(Optional.of(workflowInstance));
+
+        assertThrowsServiceException(Status.WORKFLOW_INSTANCE_NOT_EXIST,
+                () -> 
workflowInstanceService.updateWorkflowInstance(loginUser, projectCode, 1,
+                        shellJson, taskJson, "2020-02-21 00:00:00", true, "", 
"", 0));
+        Mockito.verifyNoInteractions(workflowDefinitionDao);
+    }
+
     @Test
     public void testQueryParentInstanceBySubId() {
         long projectCode = 1L;
@@ -701,6 +715,28 @@ public class WorkflowInstanceServiceTest {
         Assertions.assertDoesNotThrow(() -> 
workflowInstanceService.deleteWorkflowInstanceById(loginUser, 1));
     }
 
+    @Test
+    public void testReadOnlyUserCannotDeleteWorkflowInstance() {
+        long projectCode = 1L;
+        User loginUser = getAdminUser();
+        WorkflowInstance workflowInstance = getProcessInstance();
+        workflowInstance.setWorkflowDefinitionCode(46L);
+        workflowInstance.setWorkflowDefinitionVersion(1);
+        WorkflowDefinitionLog workflowDefinitionLog = new 
WorkflowDefinitionLog();
+        workflowDefinitionLog.setProjectCode(projectCode);
+        Project project = getProject(projectCode);
+        
when(processService.findWorkflowInstanceDetailById(1)).thenReturn(Optional.of(workflowInstance));
+        when(workflowDefinitionLogMapper.queryByDefinitionCodeAndVersion(46L, 
1))
+                .thenReturn(workflowDefinitionLog);
+        when(projectDao.queryByCode(projectCode)).thenReturn(project);
+        doThrow(new ServiceException(Status.USER_NO_WRITE_PROJECT_PERM))
+                
.when(projectService).checkHasProjectWritePermissionThrowException(loginUser, 
project);
+
+        assertThrowsServiceException(Status.USER_NO_WRITE_PROJECT_PERM,
+                () -> 
workflowInstanceService.deleteWorkflowInstanceById(loginUser, 1));
+        Mockito.verify(processService, 
Mockito.never()).deleteWorkflowInstanceById(Mockito.anyInt());
+    }
+
     @Test
     public void testViewVariables() {
         long projectCode = 1L;

Reply via email to