github-advanced-security[bot] commented on code in PR #18585:
URL: 
https://github.com/apache/dolphinscheduler/pull/18585#discussion_r4056350348


##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/utils/SensitivePropertyUtils.java:
##########
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.api.utils;
+
+import static 
org.apache.dolphinscheduler.common.constants.Constants.LOCAL_PARAMS;
+import static 
org.apache.dolphinscheduler.plugin.task.api.TaskConstants.LOCAL_PARAMS_LIST;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ServiceException;
+import org.apache.dolphinscheduler.api.vo.TaskDefinitionVO;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
+import org.apache.dolphinscheduler.dao.entity.AbstractTaskInstanceContext;
+import org.apache.dolphinscheduler.dao.entity.DagData;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.TaskInstance;
+import org.apache.dolphinscheduler.dao.entity.TaskInstanceDependentDetails;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.WorkflowInstance;
+import org.apache.dolphinscheduler.extract.master.command.AbstractCommandParam;
+import org.apache.dolphinscheduler.extract.master.command.ICommandParam;
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+import org.apache.dolphinscheduler.plugin.task.api.utils.GlobalParameterUtils;
+import 
org.apache.dolphinscheduler.plugin.task.api.utils.PropertySensitiveUtils;
+import org.apache.dolphinscheduler.plugin.task.api.utils.VarPoolUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import lombok.experimental.UtilityClass;
+
+import org.springframework.beans.BeanUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * HTTP-layer helpers for {@link Property#isSensitive()}.
+ * Values stay plaintext in DB. Query masks them as {@code ******}.
+ * Create rejects {@code ******}; Update {@link #merge}s it back to the old 
value.
+ * Start: {@code ******} is replaced with the definition global plaintext.
+ */
+@UtilityClass
+public class SensitivePropertyUtils {
+
+    /** Create: {@code ******} is never a real value. */
+    public void requireNoPlaceholder(List<Property> properties) {
+        String invalidProp = 
PropertySensitiveUtils.findPlaceholderProp(properties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating; please 
re-enter the value");
+        }
+    }
+
+    /**
+     * Update: restore DB plaintext for keep-original {@code ******}.
+     * Empty / null is a real empty value. {@code false→true} + {@code ******} 
is allowed;
+     * {@code true→false} + {@code ******} is rejected.
+     */
+    public List<Property> merge(List<Property> submittedProperties, 
List<Property> existingProperties) {
+        if (CollectionUtils.isEmpty(submittedProperties)) {
+            return submittedProperties;
+        }
+        String invalidProp = 
PropertySensitiveUtils.findInvalidSensitivePlaceholderProp(
+                submittedProperties, existingProperties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating, enabling, or 
disabling sensitive; please re-enter the value");
+        }
+        return 
PropertySensitiveUtils.mergeSensitiveValuePlaceholders(submittedProperties, 
existingProperties);
+    }
+
+    /** Start: {@code ******} is replaced with the matching definition global 
plaintext. */
+    public List<Property> restoreStartParams(List<Property> startParams, 
List<Property> globalParams) {
+        if (CollectionUtils.isEmpty(startParams)) {
+            return startParams;
+        }
+        Map<String, Property> globals = 
CollectionUtils.emptyIfNull(globalParams).stream()
+                .filter(Objects::nonNull)
+                .filter(property -> property.getProp() != null)
+                .collect(Collectors.toMap(Property::getProp, 
Function.identity(), (left, right) -> right));
+        List<Property> restored = new ArrayList<>();
+        for (Property startParam : startParams) {
+            if (startParam == null) {
+                continue;
+            }
+            if 
(!PropertySensitiveUtils.isSensitiveValuePlaceholder(startParam.getValue())) {
+                restored.add(startParam);
+                continue;
+            }
+            Property global = globals.get(startParam.getProp());
+            if (global != null) {
+                restored.add(PropertySensitiveUtils.copy(global));
+            }
+        }
+        return restored;
+    }
+
+    public String mergeLocalParams(String submittedTaskParams, String 
existingTaskParams) {
+        return rewriteLocalParams(submittedTaskParams, submitted -> {
+            if (StringUtils.isEmpty(existingTaskParams)) {
+                requireNoPlaceholder(submitted);
+                return submitted;
+            }
+            return merge(submitted, getLocalParams(existingTaskParams));
+        });
+    }
+
+    /**
+     * Copy then mask for HTTP responses. Do not mask Service return values in 
place.
+     * Nested {@link DagData} is remasked from the original (BeanUtils is 
shallow).
+     */
+    public Map<String, Map<String, Object>> mask(Map<String, Map<String, 
Object>> source) {
+        if (source == null) {
+            return null;
+        }
+        return maskLocalParamsMap(source);
+    }
+
+    public DagData mask(DagData source) {
+        if (source == null) {
+            return null;
+        }
+        DagData copy = new DagData();
+        BeanUtils.copyProperties(source, copy);
+        copy.setWorkflowDefinition(mask(source.getWorkflowDefinition()));
+        if (source.getTaskDefinitionList() != null) {
+            copy.setTaskDefinitionList(source.getTaskDefinitionList().stream()
+                    .map(SensitivePropertyUtils::mask)
+                    .collect(Collectors.toList()));
+        }
+        return copy;
+    }
+
+    public WorkflowInstance mask(WorkflowInstance source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowInstance copy = new WorkflowInstance();
+        BeanUtils.copyProperties(source, copy);
+        copy.setGlobalParams(maskGlobalParams(copy.getGlobalParams()));
+        copy.setVarPool(maskVarPool(copy.getVarPool()));
+        copy.setCommandParam(maskCommandParam(copy.getCommandParam()));
+        copy.setDagData(mask(source.getDagData()));
+        return copy;
+    }
+
+    public WorkflowDefinition mask(WorkflowDefinition source) {
+        if (source == null) {
+            return null;
+        }
+        if (source instanceof WorkflowDefinitionLog) {
+            return mask((WorkflowDefinitionLog) source);
+        }
+        WorkflowDefinition copy = new WorkflowDefinition();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public WorkflowDefinitionLog mask(WorkflowDefinitionLog source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowDefinitionLog copy = new WorkflowDefinitionLog();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinition mask(TaskDefinition source) {
+        if (source == null) {
+            return null;
+        }
+        if (source instanceof TaskDefinitionVO) {
+            return mask((TaskDefinitionVO) source);
+        }
+        if (source instanceof TaskDefinitionLog) {
+            return mask((TaskDefinitionLog) source);
+        }
+        TaskDefinition copy = new TaskDefinition();
+        BeanUtils.copyProperties(source, copy);
+        applyTaskDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinitionLog mask(TaskDefinitionLog source) {
+        if (source == null) {
+            return null;
+        }
+        TaskDefinitionLog copy = new TaskDefinitionLog();
+        BeanUtils.copyProperties(source, copy);
+        applyTaskDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinitionVO mask(TaskDefinitionVO source) {

Review Comment:
   ## CodeQL / Confusing overloading of methods
   
   Method SensitivePropertyUtils.mask(..) could be confused with overloaded 
method [mask](1), since dispatch depends on static types.
   
   [Show more 
details](https://github.com/apache/dolphinscheduler/security/code-scanning/5860)



##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/utils/SensitivePropertyUtils.java:
##########
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.api.utils;
+
+import static 
org.apache.dolphinscheduler.common.constants.Constants.LOCAL_PARAMS;
+import static 
org.apache.dolphinscheduler.plugin.task.api.TaskConstants.LOCAL_PARAMS_LIST;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ServiceException;
+import org.apache.dolphinscheduler.api.vo.TaskDefinitionVO;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
+import org.apache.dolphinscheduler.dao.entity.AbstractTaskInstanceContext;
+import org.apache.dolphinscheduler.dao.entity.DagData;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.TaskInstance;
+import org.apache.dolphinscheduler.dao.entity.TaskInstanceDependentDetails;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.WorkflowInstance;
+import org.apache.dolphinscheduler.extract.master.command.AbstractCommandParam;
+import org.apache.dolphinscheduler.extract.master.command.ICommandParam;
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+import org.apache.dolphinscheduler.plugin.task.api.utils.GlobalParameterUtils;
+import 
org.apache.dolphinscheduler.plugin.task.api.utils.PropertySensitiveUtils;
+import org.apache.dolphinscheduler.plugin.task.api.utils.VarPoolUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import lombok.experimental.UtilityClass;
+
+import org.springframework.beans.BeanUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * HTTP-layer helpers for {@link Property#isSensitive()}.
+ * Values stay plaintext in DB. Query masks them as {@code ******}.
+ * Create rejects {@code ******}; Update {@link #merge}s it back to the old 
value.
+ * Start: {@code ******} is replaced with the definition global plaintext.
+ */
+@UtilityClass
+public class SensitivePropertyUtils {
+
+    /** Create: {@code ******} is never a real value. */
+    public void requireNoPlaceholder(List<Property> properties) {
+        String invalidProp = 
PropertySensitiveUtils.findPlaceholderProp(properties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating; please 
re-enter the value");
+        }
+    }
+
+    /**
+     * Update: restore DB plaintext for keep-original {@code ******}.
+     * Empty / null is a real empty value. {@code false→true} + {@code ******} 
is allowed;
+     * {@code true→false} + {@code ******} is rejected.
+     */
+    public List<Property> merge(List<Property> submittedProperties, 
List<Property> existingProperties) {
+        if (CollectionUtils.isEmpty(submittedProperties)) {
+            return submittedProperties;
+        }
+        String invalidProp = 
PropertySensitiveUtils.findInvalidSensitivePlaceholderProp(
+                submittedProperties, existingProperties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating, enabling, or 
disabling sensitive; please re-enter the value");
+        }
+        return 
PropertySensitiveUtils.mergeSensitiveValuePlaceholders(submittedProperties, 
existingProperties);
+    }
+
+    /** Start: {@code ******} is replaced with the matching definition global 
plaintext. */
+    public List<Property> restoreStartParams(List<Property> startParams, 
List<Property> globalParams) {
+        if (CollectionUtils.isEmpty(startParams)) {
+            return startParams;
+        }
+        Map<String, Property> globals = 
CollectionUtils.emptyIfNull(globalParams).stream()
+                .filter(Objects::nonNull)
+                .filter(property -> property.getProp() != null)
+                .collect(Collectors.toMap(Property::getProp, 
Function.identity(), (left, right) -> right));
+        List<Property> restored = new ArrayList<>();
+        for (Property startParam : startParams) {
+            if (startParam == null) {
+                continue;
+            }
+            if 
(!PropertySensitiveUtils.isSensitiveValuePlaceholder(startParam.getValue())) {
+                restored.add(startParam);
+                continue;
+            }
+            Property global = globals.get(startParam.getProp());
+            if (global != null) {
+                restored.add(PropertySensitiveUtils.copy(global));
+            }
+        }
+        return restored;
+    }
+
+    public String mergeLocalParams(String submittedTaskParams, String 
existingTaskParams) {
+        return rewriteLocalParams(submittedTaskParams, submitted -> {
+            if (StringUtils.isEmpty(existingTaskParams)) {
+                requireNoPlaceholder(submitted);
+                return submitted;
+            }
+            return merge(submitted, getLocalParams(existingTaskParams));
+        });
+    }
+
+    /**
+     * Copy then mask for HTTP responses. Do not mask Service return values in 
place.
+     * Nested {@link DagData} is remasked from the original (BeanUtils is 
shallow).
+     */
+    public Map<String, Map<String, Object>> mask(Map<String, Map<String, 
Object>> source) {
+        if (source == null) {
+            return null;
+        }
+        return maskLocalParamsMap(source);
+    }
+
+    public DagData mask(DagData source) {
+        if (source == null) {
+            return null;
+        }
+        DagData copy = new DagData();
+        BeanUtils.copyProperties(source, copy);
+        copy.setWorkflowDefinition(mask(source.getWorkflowDefinition()));
+        if (source.getTaskDefinitionList() != null) {
+            copy.setTaskDefinitionList(source.getTaskDefinitionList().stream()
+                    .map(SensitivePropertyUtils::mask)
+                    .collect(Collectors.toList()));
+        }
+        return copy;
+    }
+
+    public WorkflowInstance mask(WorkflowInstance source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowInstance copy = new WorkflowInstance();
+        BeanUtils.copyProperties(source, copy);
+        copy.setGlobalParams(maskGlobalParams(copy.getGlobalParams()));
+        copy.setVarPool(maskVarPool(copy.getVarPool()));
+        copy.setCommandParam(maskCommandParam(copy.getCommandParam()));
+        copy.setDagData(mask(source.getDagData()));
+        return copy;
+    }
+
+    public WorkflowDefinition mask(WorkflowDefinition source) {

Review Comment:
   ## CodeQL / Confusing overloading of methods
   
   Method SensitivePropertyUtils.mask(..) could be confused with overloaded 
method [mask](1), since dispatch depends on static types.
   
   [Show more 
details](https://github.com/apache/dolphinscheduler/security/code-scanning/5862)



##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/utils/SensitivePropertyUtils.java:
##########
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.api.utils;
+
+import static 
org.apache.dolphinscheduler.common.constants.Constants.LOCAL_PARAMS;
+import static 
org.apache.dolphinscheduler.plugin.task.api.TaskConstants.LOCAL_PARAMS_LIST;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ServiceException;
+import org.apache.dolphinscheduler.api.vo.TaskDefinitionVO;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
+import org.apache.dolphinscheduler.dao.entity.AbstractTaskInstanceContext;
+import org.apache.dolphinscheduler.dao.entity.DagData;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.TaskInstance;
+import org.apache.dolphinscheduler.dao.entity.TaskInstanceDependentDetails;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.WorkflowInstance;
+import org.apache.dolphinscheduler.extract.master.command.AbstractCommandParam;
+import org.apache.dolphinscheduler.extract.master.command.ICommandParam;
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+import org.apache.dolphinscheduler.plugin.task.api.utils.GlobalParameterUtils;
+import 
org.apache.dolphinscheduler.plugin.task.api.utils.PropertySensitiveUtils;
+import org.apache.dolphinscheduler.plugin.task.api.utils.VarPoolUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import lombok.experimental.UtilityClass;
+
+import org.springframework.beans.BeanUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * HTTP-layer helpers for {@link Property#isSensitive()}.
+ * Values stay plaintext in DB. Query masks them as {@code ******}.
+ * Create rejects {@code ******}; Update {@link #merge}s it back to the old 
value.
+ * Start: {@code ******} is replaced with the definition global plaintext.
+ */
+@UtilityClass
+public class SensitivePropertyUtils {
+
+    /** Create: {@code ******} is never a real value. */
+    public void requireNoPlaceholder(List<Property> properties) {
+        String invalidProp = 
PropertySensitiveUtils.findPlaceholderProp(properties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating; please 
re-enter the value");
+        }
+    }
+
+    /**
+     * Update: restore DB plaintext for keep-original {@code ******}.
+     * Empty / null is a real empty value. {@code false→true} + {@code ******} 
is allowed;
+     * {@code true→false} + {@code ******} is rejected.
+     */
+    public List<Property> merge(List<Property> submittedProperties, 
List<Property> existingProperties) {
+        if (CollectionUtils.isEmpty(submittedProperties)) {
+            return submittedProperties;
+        }
+        String invalidProp = 
PropertySensitiveUtils.findInvalidSensitivePlaceholderProp(
+                submittedProperties, existingProperties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating, enabling, or 
disabling sensitive; please re-enter the value");
+        }
+        return 
PropertySensitiveUtils.mergeSensitiveValuePlaceholders(submittedProperties, 
existingProperties);
+    }
+
+    /** Start: {@code ******} is replaced with the matching definition global 
plaintext. */
+    public List<Property> restoreStartParams(List<Property> startParams, 
List<Property> globalParams) {
+        if (CollectionUtils.isEmpty(startParams)) {
+            return startParams;
+        }
+        Map<String, Property> globals = 
CollectionUtils.emptyIfNull(globalParams).stream()
+                .filter(Objects::nonNull)
+                .filter(property -> property.getProp() != null)
+                .collect(Collectors.toMap(Property::getProp, 
Function.identity(), (left, right) -> right));
+        List<Property> restored = new ArrayList<>();
+        for (Property startParam : startParams) {
+            if (startParam == null) {
+                continue;
+            }
+            if 
(!PropertySensitiveUtils.isSensitiveValuePlaceholder(startParam.getValue())) {
+                restored.add(startParam);
+                continue;
+            }
+            Property global = globals.get(startParam.getProp());
+            if (global != null) {
+                restored.add(PropertySensitiveUtils.copy(global));
+            }
+        }
+        return restored;
+    }
+
+    public String mergeLocalParams(String submittedTaskParams, String 
existingTaskParams) {
+        return rewriteLocalParams(submittedTaskParams, submitted -> {
+            if (StringUtils.isEmpty(existingTaskParams)) {
+                requireNoPlaceholder(submitted);
+                return submitted;
+            }
+            return merge(submitted, getLocalParams(existingTaskParams));
+        });
+    }
+
+    /**
+     * Copy then mask for HTTP responses. Do not mask Service return values in 
place.
+     * Nested {@link DagData} is remasked from the original (BeanUtils is 
shallow).
+     */
+    public Map<String, Map<String, Object>> mask(Map<String, Map<String, 
Object>> source) {
+        if (source == null) {
+            return null;
+        }
+        return maskLocalParamsMap(source);
+    }
+
+    public DagData mask(DagData source) {
+        if (source == null) {
+            return null;
+        }
+        DagData copy = new DagData();
+        BeanUtils.copyProperties(source, copy);
+        copy.setWorkflowDefinition(mask(source.getWorkflowDefinition()));
+        if (source.getTaskDefinitionList() != null) {
+            copy.setTaskDefinitionList(source.getTaskDefinitionList().stream()
+                    .map(SensitivePropertyUtils::mask)
+                    .collect(Collectors.toList()));
+        }
+        return copy;
+    }
+
+    public WorkflowInstance mask(WorkflowInstance source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowInstance copy = new WorkflowInstance();
+        BeanUtils.copyProperties(source, copy);
+        copy.setGlobalParams(maskGlobalParams(copy.getGlobalParams()));
+        copy.setVarPool(maskVarPool(copy.getVarPool()));
+        copy.setCommandParam(maskCommandParam(copy.getCommandParam()));
+        copy.setDagData(mask(source.getDagData()));
+        return copy;
+    }
+
+    public WorkflowDefinition mask(WorkflowDefinition source) {
+        if (source == null) {
+            return null;
+        }
+        if (source instanceof WorkflowDefinitionLog) {
+            return mask((WorkflowDefinitionLog) source);
+        }
+        WorkflowDefinition copy = new WorkflowDefinition();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public WorkflowDefinitionLog mask(WorkflowDefinitionLog source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowDefinitionLog copy = new WorkflowDefinitionLog();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinition mask(TaskDefinition source) {
+        if (source == null) {
+            return null;
+        }
+        if (source instanceof TaskDefinitionVO) {
+            return mask((TaskDefinitionVO) source);
+        }
+        if (source instanceof TaskDefinitionLog) {
+            return mask((TaskDefinitionLog) source);
+        }
+        TaskDefinition copy = new TaskDefinition();
+        BeanUtils.copyProperties(source, copy);
+        applyTaskDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinitionLog mask(TaskDefinitionLog source) {
+        if (source == null) {
+            return null;
+        }
+        TaskDefinitionLog copy = new TaskDefinitionLog();
+        BeanUtils.copyProperties(source, copy);
+        applyTaskDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinitionVO mask(TaskDefinitionVO source) {
+        if (source == null) {
+            return null;
+        }
+        TaskDefinitionVO copy = new TaskDefinitionVO();
+        BeanUtils.copyProperties(source, copy);
+        applyTaskDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskInstance mask(TaskInstance source) {

Review Comment:
   ## CodeQL / Confusing overloading of methods
   
   Method SensitivePropertyUtils.mask(..) could be confused with overloaded 
method [mask](1), since dispatch depends on static types.
   
   [Show more 
details](https://github.com/apache/dolphinscheduler/security/code-scanning/5859)



##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/utils/SensitivePropertyUtils.java:
##########
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.api.utils;
+
+import static 
org.apache.dolphinscheduler.common.constants.Constants.LOCAL_PARAMS;
+import static 
org.apache.dolphinscheduler.plugin.task.api.TaskConstants.LOCAL_PARAMS_LIST;
+
+import org.apache.dolphinscheduler.api.enums.Status;
+import org.apache.dolphinscheduler.api.exceptions.ServiceException;
+import org.apache.dolphinscheduler.api.vo.TaskDefinitionVO;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
+import org.apache.dolphinscheduler.dao.entity.AbstractTaskInstanceContext;
+import org.apache.dolphinscheduler.dao.entity.DagData;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.TaskInstance;
+import org.apache.dolphinscheduler.dao.entity.TaskInstanceDependentDetails;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinition;
+import org.apache.dolphinscheduler.dao.entity.WorkflowDefinitionLog;
+import org.apache.dolphinscheduler.dao.entity.WorkflowInstance;
+import org.apache.dolphinscheduler.extract.master.command.AbstractCommandParam;
+import org.apache.dolphinscheduler.extract.master.command.ICommandParam;
+import org.apache.dolphinscheduler.plugin.task.api.model.Property;
+import org.apache.dolphinscheduler.plugin.task.api.utils.GlobalParameterUtils;
+import 
org.apache.dolphinscheduler.plugin.task.api.utils.PropertySensitiveUtils;
+import org.apache.dolphinscheduler.plugin.task.api.utils.VarPoolUtils;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import lombok.experimental.UtilityClass;
+
+import org.springframework.beans.BeanUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * HTTP-layer helpers for {@link Property#isSensitive()}.
+ * Values stay plaintext in DB. Query masks them as {@code ******}.
+ * Create rejects {@code ******}; Update {@link #merge}s it back to the old 
value.
+ * Start: {@code ******} is replaced with the definition global plaintext.
+ */
+@UtilityClass
+public class SensitivePropertyUtils {
+
+    /** Create: {@code ******} is never a real value. */
+    public void requireNoPlaceholder(List<Property> properties) {
+        String invalidProp = 
PropertySensitiveUtils.findPlaceholderProp(properties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating; please 
re-enter the value");
+        }
+    }
+
+    /**
+     * Update: restore DB plaintext for keep-original {@code ******}.
+     * Empty / null is a real empty value. {@code false→true} + {@code ******} 
is allowed;
+     * {@code true→false} + {@code ******} is rejected.
+     */
+    public List<Property> merge(List<Property> submittedProperties, 
List<Property> existingProperties) {
+        if (CollectionUtils.isEmpty(submittedProperties)) {
+            return submittedProperties;
+        }
+        String invalidProp = 
PropertySensitiveUtils.findInvalidSensitivePlaceholderProp(
+                submittedProperties, existingProperties);
+        if (invalidProp != null) {
+            throw new ServiceException(Status.REQUEST_PARAMS_NOT_VALID_ERROR,
+                    "parameter '" + invalidProp
+                            + "' cannot use ****** when creating, enabling, or 
disabling sensitive; please re-enter the value");
+        }
+        return 
PropertySensitiveUtils.mergeSensitiveValuePlaceholders(submittedProperties, 
existingProperties);
+    }
+
+    /** Start: {@code ******} is replaced with the matching definition global 
plaintext. */
+    public List<Property> restoreStartParams(List<Property> startParams, 
List<Property> globalParams) {
+        if (CollectionUtils.isEmpty(startParams)) {
+            return startParams;
+        }
+        Map<String, Property> globals = 
CollectionUtils.emptyIfNull(globalParams).stream()
+                .filter(Objects::nonNull)
+                .filter(property -> property.getProp() != null)
+                .collect(Collectors.toMap(Property::getProp, 
Function.identity(), (left, right) -> right));
+        List<Property> restored = new ArrayList<>();
+        for (Property startParam : startParams) {
+            if (startParam == null) {
+                continue;
+            }
+            if 
(!PropertySensitiveUtils.isSensitiveValuePlaceholder(startParam.getValue())) {
+                restored.add(startParam);
+                continue;
+            }
+            Property global = globals.get(startParam.getProp());
+            if (global != null) {
+                restored.add(PropertySensitiveUtils.copy(global));
+            }
+        }
+        return restored;
+    }
+
+    public String mergeLocalParams(String submittedTaskParams, String 
existingTaskParams) {
+        return rewriteLocalParams(submittedTaskParams, submitted -> {
+            if (StringUtils.isEmpty(existingTaskParams)) {
+                requireNoPlaceholder(submitted);
+                return submitted;
+            }
+            return merge(submitted, getLocalParams(existingTaskParams));
+        });
+    }
+
+    /**
+     * Copy then mask for HTTP responses. Do not mask Service return values in 
place.
+     * Nested {@link DagData} is remasked from the original (BeanUtils is 
shallow).
+     */
+    public Map<String, Map<String, Object>> mask(Map<String, Map<String, 
Object>> source) {
+        if (source == null) {
+            return null;
+        }
+        return maskLocalParamsMap(source);
+    }
+
+    public DagData mask(DagData source) {
+        if (source == null) {
+            return null;
+        }
+        DagData copy = new DagData();
+        BeanUtils.copyProperties(source, copy);
+        copy.setWorkflowDefinition(mask(source.getWorkflowDefinition()));
+        if (source.getTaskDefinitionList() != null) {
+            copy.setTaskDefinitionList(source.getTaskDefinitionList().stream()
+                    .map(SensitivePropertyUtils::mask)
+                    .collect(Collectors.toList()));
+        }
+        return copy;
+    }
+
+    public WorkflowInstance mask(WorkflowInstance source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowInstance copy = new WorkflowInstance();
+        BeanUtils.copyProperties(source, copy);
+        copy.setGlobalParams(maskGlobalParams(copy.getGlobalParams()));
+        copy.setVarPool(maskVarPool(copy.getVarPool()));
+        copy.setCommandParam(maskCommandParam(copy.getCommandParam()));
+        copy.setDagData(mask(source.getDagData()));
+        return copy;
+    }
+
+    public WorkflowDefinition mask(WorkflowDefinition source) {
+        if (source == null) {
+            return null;
+        }
+        if (source instanceof WorkflowDefinitionLog) {
+            return mask((WorkflowDefinitionLog) source);
+        }
+        WorkflowDefinition copy = new WorkflowDefinition();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public WorkflowDefinitionLog mask(WorkflowDefinitionLog source) {
+        if (source == null) {
+            return null;
+        }
+        WorkflowDefinitionLog copy = new WorkflowDefinitionLog();
+        BeanUtils.copyProperties(source, copy);
+        applyWorkflowDefinitionMask(copy);
+        return copy;
+    }
+
+    public TaskDefinition mask(TaskDefinition source) {

Review Comment:
   ## CodeQL / Confusing overloading of methods
   
   Method SensitivePropertyUtils.mask(..) could be confused with overloaded 
method [mask](1), since dispatch depends on static types.
   
   [Show more 
details](https://github.com/apache/dolphinscheduler/security/code-scanning/5861)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to