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

jlfsdtc pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git


The following commit(s) were added to refs/heads/kylin5 by this push:
     new 8107cf1e9b KYLIN-6093 Add permission checks to job, DDL, blacklist, 
metastore cleanup and metadata dump APIs
8107cf1e9b is described below

commit 8107cf1e9bceaa54a20d0b8522625847222e0fb3
Author: jlfsdtc <[email protected]>
AuthorDate: Tue Sep 1 14:48:33 2026 +0800

    KYLIN-6093 Add permission checks to job, DDL, blacklist, metastore cleanup 
and metadata dump APIs
    
    * KYLIN-6093 Add permission checks to job, DDL, blacklist and metadata dump 
APIs
    
    Enforce ACL at the service layer instead of relying on controllers:
    - Require global admin for job list/count without a project, Spark DDL
      execute/dropTable and metadata dump
    - Require project operation permission for batch job status update,
      discardJobs and destroyJobProcess
    - Require project write permission for SQL blacklist operations
    - Move forwardRequestToTargetNode from BaseController to BasicService, and
      move remoteUpdateJobStatus / metadata dump logic from JobController into
      JobInfoService and JobService so the checks cannot be bypassed
    
    Also run maven-help-plugin offline in exportProjectVersions.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    * KYLIN-6093 Add permission checks to metastore cleanup APIs
    
    Remove permitAll from /api/metastore/cleanup and 
/api/metastore/cleanup_storage
    in kylinSecurity.xml, and enforce global/project admin checks in 
MetaStoreService.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../src/main/resources/kylinSecurity.xml           |  2 -
 .../apache/kylin/rest/config/SecurityConfig.java   |  4 +-
 .../kylin/rest/controller/BaseController.java      |  8 ---
 .../apache/kylin/rest/service/BasicService.java    |  6 +++
 .../src/test/resources/kylinSecurity.xml           |  2 -
 .../src/main/resources/kylinSecurity.xml           |  2 -
 .../kylin/rest/controller/JobController.java       | 59 ++--------------------
 .../apache/kylin/rest/service/JobInfoService.java  | 27 +++++++++-
 .../org/apache/kylin/rest/service/JobService.java  | 52 +++++++++++++++++++
 .../kylin/rest/service/JobInfoServiceTest.java     | 52 +++++++++++++++++++
 .../apache/kylin/rest/service/JobServiceTest.java  | 21 ++++++++
 .../kylin/rest/service/SparkSourceService.java     |  7 +++
 .../kylin/rest/service/SparkSourceServiceTest.java | 15 ++++++
 .../kylin/rest/service/MetaStoreService.java       | 10 ++--
 .../kylin/rest/service/MetaStoreServiceTest.java   | 28 ++++++++++
 .../rest/service/MetaStoreTenantServiceTest.java   |  4 ++
 .../kylin/rest/controller/OpsController.java       |  1 +
 .../kylin/rest/controller/OpsControllerTest.java   | 13 +++++
 .../src/main/resources/kylinSecurity.xml           |  2 -
 .../rest/service/QuerySQLBlacklistService.java     | 19 ++++++-
 .../rest/service/QuerySQLBlacklistServiceTest.java | 38 ++++++++++++++
 .../src/main/resources/kylinSecurity.xml           |  2 -
 src/server/src/main/resources/kylinSecurity.xml    |  2 -
 .../src/test/resources/kylinSecurity.xml           |  2 -
 24 files changed, 292 insertions(+), 86 deletions(-)

diff --git a/src/common-booter/src/main/resources/kylinSecurity.xml 
b/src/common-booter/src/main/resources/kylinSecurity.xml
index 5ebd4caa5f..a2978ad1d8 100644
--- a/src/common-booter/src/main/resources/kylinSecurity.xml
+++ b/src/common-booter/src/main/resources/kylinSecurity.xml
@@ -252,8 +252,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git 
a/src/common-server/src/main/java/org/apache/kylin/rest/config/SecurityConfig.java
 
b/src/common-server/src/main/java/org/apache/kylin/rest/config/SecurityConfig.java
index d78b11bc4c..1b06aaedbb 100644
--- 
a/src/common-server/src/main/java/org/apache/kylin/rest/config/SecurityConfig.java
+++ 
b/src/common-server/src/main/java/org/apache/kylin/rest/config/SecurityConfig.java
@@ -74,8 +74,8 @@ public class SecurityConfig extends 
WebSecurityConfigurerAdapter {
         http.authorizeRequests()
                 .antMatchers("/api/streaming_jobs/spark", 
"/api/streaming_jobs/stats",
                 "/api/streaming_jobs/dataflow/**", 
"/api/epoch/maintenance_mode", "/api/health", "/api/health/**",
-                "/api/prometheus", "/api/monitor/spark/prometheus", 
"/api/metastore/cleanup",
-                "/api/metastore/cleanup_storage", "/api/epoch", 
"/api/broadcast/**", "/api/config/is_cloud",
+                "/api/prometheus", "/api/monitor/spark/prometheus",
+                "/api/epoch", "/api/broadcast/**", "/api/config/is_cloud",
                 "/api/system/license/file", "/api/system/license/content", 
"/api/system/license/trial",
                 "/api/system/license", "/api/system/diag/progress", 
"/api/system/roll_event_log",
                 "/api/user/authentication*/**", 
"/api/query/history_queries/table_names", "/api/models/model_info",
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/controller/BaseController.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/controller/BaseController.java
index 6320a6270c..e4cb386204 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/controller/BaseController.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/controller/BaseController.java
@@ -79,12 +79,10 @@ import org.apache.kylin.rest.response.ErrorResponse;
 import org.apache.kylin.rest.service.ProjectService;
 import org.apache.kylin.rest.service.UserService;
 import org.apache.kylin.rest.util.PagingUtil;
-import org.apache.kylin.tool.restclient.RestClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -406,10 +404,4 @@ public class BaseController {
         String local = AddressUtil.getLocalInstance();
         return targetNodes.size() > 1 || targetNodes.size() == 1 && 
!targetNodes.contains(local);
     }
-
-    protected void forwardRequestToTargetNode(byte[] requestEntity, 
HttpHeaders headers, String node, String url)
-            throws IOException {
-        RestClient client = new RestClient(node);
-        client.forwardPut(requestEntity, headers, url, true);
-    }
 }
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/BasicService.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/BasicService.java
index 1f7d82a96e..ec5ac3d401 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/BasicService.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/BasicService.java
@@ -176,4 +176,10 @@ public abstract class BasicService {
     public NProjectManager getProjectManager() {
         return NProjectManager.getInstance(getConfig());
     }
+
+    protected void forwardRequestToTargetNode(byte[] requestEntity, 
HttpHeaders headers, String node, String url)
+            throws IOException {
+        RestClient client = new RestClient(node);
+        client.forwardPut(requestEntity, headers, url, true);
+    }
 }
diff --git a/src/common-service/src/test/resources/kylinSecurity.xml 
b/src/common-service/src/test/resources/kylinSecurity.xml
index 930c58a545..df4f82873f 100644
--- a/src/common-service/src/test/resources/kylinSecurity.xml
+++ b/src/common-service/src/test/resources/kylinSecurity.xml
@@ -243,8 +243,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git a/src/data-loading-booter/src/main/resources/kylinSecurity.xml 
b/src/data-loading-booter/src/main/resources/kylinSecurity.xml
index e9a8dd291b..c95cac54bc 100644
--- a/src/data-loading-booter/src/main/resources/kylinSecurity.xml
+++ b/src/data-loading-booter/src/main/resources/kylinSecurity.xml
@@ -252,8 +252,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git 
a/src/data-loading-server/src/main/java/org/apache/kylin/rest/controller/JobController.java
 
b/src/data-loading-server/src/main/java/org/apache/kylin/rest/controller/JobController.java
index 08c8071f02..4d73a289df 100644
--- 
a/src/data-loading-server/src/main/java/org/apache/kylin/rest/controller/JobController.java
+++ 
b/src/data-loading-server/src/main/java/org/apache/kylin/rest/controller/JobController.java
@@ -26,13 +26,10 @@ import static 
org.apache.kylin.common.exception.code.ErrorCodeServer.JOB_TYPE_IL
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -41,7 +38,6 @@ import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
-import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.Pair;
 import org.apache.kylin.guava30.shaded.common.collect.Lists;
 import org.apache.kylin.job.execution.AbstractExecutable;
@@ -50,10 +46,6 @@ import org.apache.kylin.job.execution.ExecutableState;
 import org.apache.kylin.job.execution.JobTypeEnum;
 import org.apache.kylin.job.rest.JobFilter;
 import org.apache.kylin.job.util.JobContextUtil;
-import org.apache.kylin.metadata.cube.model.NDataflow;
-import org.apache.kylin.metadata.cube.model.NDataflowManager;
-import org.apache.kylin.metadata.view.LogicalView;
-import org.apache.kylin.metadata.view.LogicalViewManager;
 import org.apache.kylin.rest.request.JobErrorRequest;
 import org.apache.kylin.rest.request.JobUpdateRequest;
 import org.apache.kylin.rest.request.LoadGlutenCacheRequest;
@@ -70,8 +62,6 @@ import org.apache.kylin.rest.response.JobStatisticsResponse;
 import org.apache.kylin.rest.service.JobInfoService;
 import org.apache.kylin.rest.service.JobService;
 import org.apache.kylin.rest.service.RouteService;
-import org.apache.kylin.util.DumpInfo;
-import org.apache.kylin.util.MetadataDumpUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -183,7 +173,7 @@ public class JobController extends BaseController {
         Map<String, List<String>> nodeWithJobs = JobContextUtil
                 .splitJobIdsByScheduleInstance(jobUpdateRequest.getJobIds());
         if (needRouteToOtherInstance(nodeWithJobs, 
jobUpdateRequest.getAction())) {
-            return remoteUpdateJobStatus(jobUpdateRequest, headers, 
nodeWithJobs);
+            return jobInfoService.remoteUpdateJobStatus(jobUpdateRequest, 
headers, nodeWithJobs);
         }
         if (StringUtils.isBlank(jobUpdateRequest.getProject())
                 && CollectionUtils.isEmpty(jobUpdateRequest.getJobIds())) {
@@ -195,16 +185,6 @@ public class JobController extends BaseController {
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
     }
 
-    private EnvelopeResponse<String> remoteUpdateJobStatus(JobUpdateRequest 
jobUpdateRequest, HttpHeaders headers,
-            Map<String, List<String>> nodeWithJobs) throws IOException {
-        for (Map.Entry<String, List<String>> entry : nodeWithJobs.entrySet()) {
-            jobUpdateRequest.setJobIds(entry.getValue());
-            
forwardRequestToTargetNode(JsonUtil.writeValueAsBytes(jobUpdateRequest), 
headers, entry.getKey(),
-                    "/jobs/status");
-        }
-        return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
-    }
-
     @ApiOperation(value = "getJobDetail", tags = { "DW" }, notes = "Update 
Param: job_id")
     @GetMapping(value = "/{job_id:.+}/detail")
     @ResponseBody
@@ -422,50 +402,17 @@ public class JobController extends BaseController {
     @ApiOperation(value = "destroyJobProcess", tags = { "DW" })
     @ResponseBody
     public void destroyJobProcess(@RequestParam("project") String project) {
-        ExecutableManager.getInstance(KylinConfig.getInstanceFromEnv(), 
project).destroyAllProcess();
+        jobService.destroyJobProcess(project);
     }
 
     @PostMapping(value = "/replace_metadata")
     @ApiOperation(value = "replace_metadata", tags = { "DW" })
     @ResponseBody
     public EnvelopeResponse<String> updateDumpedMetadata(@RequestBody 
ReplaceMetaRequest request) throws Exception {
-        KylinConfig config = KylinConfig.getInstanceFromEnv();
-        Set<String> dumpList = new LinkedHashSet<>();
-        String project = request.getProject();
-        String modelId = request.getModelId();
-        String viewTable = request.getViewTable();
-        String distMetaUrl = request.getDistMetaUrl();
-
-        NDataflow df = NDataflowManager.getInstance(config, 
project).getDataflow(modelId);
-        dumpList.addAll(df.collectPrecalculationResource());
-        dumpList.addAll(getLogicalViewMetaDumpList(config, project, viewTable, 
modelId));
-
-        DumpInfo dumpInfo = new DumpInfo(project, distMetaUrl, dumpList, 
DumpInfo.DumpType.DATA_LOADING);
-        MetadataDumpUtil.dumpMetadata(dumpInfo);
+        jobService.updateDumpedMetadata(request);
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
     }
 
-    private Set<String> getLogicalViewMetaDumpList(KylinConfig config, String 
project, String viewTable,
-            String modelId) {
-        Set<String> dumpList = new LinkedHashSet<>();
-        if (!config.isDDLLogicalViewEnabled()) {
-            return dumpList;
-        }
-        LogicalViewManager viewManager = 
LogicalViewManager.getInstance(config);
-        if (StringUtils.isNotBlank(modelId)) {
-            Set<String> viewsMeta = 
viewManager.findLogicalViewsInModel(project, modelId).stream()
-                    
.map(LogicalView::getResourcePath).collect(Collectors.toSet());
-            dumpList.addAll(viewsMeta);
-        }
-        if (StringUtils.isNotBlank(viewTable)) {
-            LogicalView logicalView = 
viewManager.findLogicalViewInProject(project, viewTable);
-            if (logicalView != null) {
-                dumpList.add(logicalView.getResourcePath());
-            }
-        }
-        return dumpList;
-    }
-
     @ApiOperation(value = "startProfile", tags = { "DW" }, notes = "")
     @GetMapping(value = "/profile/start_project")
     @ResponseBody
diff --git 
a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobInfoService.java
 
b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobInfoService.java
index 071f1f0013..32953f9bf4 100644
--- 
a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobInfoService.java
+++ 
b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobInfoService.java
@@ -103,6 +103,7 @@ import org.apache.kylin.rest.aspect.Transaction;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.request.JobUpdateRequest;
 import org.apache.kylin.rest.request.SparkJobUpdateRequest;
+import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.response.ExecutableResponse;
 import org.apache.kylin.rest.response.ExecutableStepResponse;
 import org.apache.kylin.rest.util.AclEvaluate;
@@ -112,6 +113,7 @@ import org.apache.kylin.rest.util.SparkHistoryUIUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -252,6 +254,8 @@ public class JobInfoService extends BasicService implements 
JobSupporter {
     public List<ExecutableResponse> listJobs(final JobFilter jobFilter, int 
offset, int limit) {
         if (StringUtils.isNotEmpty(jobFilter.getProject())) {
             
aclEvaluate.checkProjectOperationPermission(jobFilter.getProject());
+        } else {
+            aclEvaluate.checkIsGlobalAdmin();
         }
         JobMapperFilter jobMapperFilter = 
JobFilterUtil.getJobMapperFilter(jobFilter, offset, limit, modelService,
                 tableExtService, projectService);
@@ -289,6 +293,8 @@ public class JobInfoService extends BasicService implements 
JobSupporter {
     public long countJobs(final JobFilter jobFilter) {
         if (StringUtils.isNotEmpty(jobFilter.getProject())) {
             
aclEvaluate.checkProjectOperationPermission(jobFilter.getProject());
+        } else {
+            aclEvaluate.checkIsGlobalAdmin();
         }
         JobMapperFilter jobMapperFilter = 
JobFilterUtil.getJobMapperFilter(jobFilter, 0, 0, modelService,
                 tableExtService, projectService);
@@ -446,6 +452,13 @@ public class JobInfoService extends BasicService 
implements JobSupporter {
 
     public void batchUpdateJobStatus(List<String> jobIds, String project, 
String action, List<String> filterStatuses)
             throws IOException {
+        final var executablePos = checkJobsAcl(jobIds, project, 
filterStatuses);
+        for (ExecutablePO executablePO : executablePos) {
+            updateJobStatus(executablePO.getId(), executablePO, 
executablePO.getProject(), action);
+        }
+    }
+
+    private List<ExecutablePO> checkJobsAcl(List<String> jobIds, String 
project, List<String> filterStatuses) {
         List<ExecutableState> filterStates = 
JobStatusUtil.mapJobStatusToScheduleState(filterStatuses);
         val executablePos = jobInfoDao.getExecutablePoByStatus(project, 
jobIds, filterStates);
         if (null == project) {
@@ -454,9 +467,18 @@ public class JobInfoService extends BasicService 
implements JobSupporter {
         } else {
             aclEvaluate.checkProjectOperationPermission(project);
         }
-        for (ExecutablePO executablePO : executablePos) {
-            updateJobStatus(executablePO.getId(), executablePO, 
executablePO.getProject(), action);
+        return executablePos;
+    }
+
+    public EnvelopeResponse<String> remoteUpdateJobStatus(JobUpdateRequest 
jobUpdateRequest, HttpHeaders headers,
+            Map<String, List<String>> nodeWithJobs) throws IOException {
+        checkJobsAcl(jobUpdateRequest.getJobIds(), 
jobUpdateRequest.getProject(), jobUpdateRequest.getStatuses());
+        for (Map.Entry<String, List<String>> entry : nodeWithJobs.entrySet()) {
+            jobUpdateRequest.setJobIds(entry.getValue());
+            
forwardRequestToTargetNode(JsonUtil.writeValueAsBytes(jobUpdateRequest), 
headers, entry.getKey(),
+                    "/jobs/status");
         }
+        return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, "", "");
     }
 
     @Transactional
@@ -1033,6 +1055,7 @@ public class JobInfoService extends BasicService 
implements JobSupporter {
     }
 
     public void discardJobs(String project, List<String> jobIdList) {
+        aclEvaluate.checkProjectOperationPermission(project);
         EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> {
             ExecutableManager executableManager = 
getManager(ExecutableManager.class, project);
             jobIdList.forEach(executableManager::discardJob);
diff --git 
a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobService.java
 
b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobService.java
index 51da68a86c..589d11b2c1 100644
--- 
a/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobService.java
+++ 
b/src/data-loading-service/src/main/java/org/apache/kylin/rest/service/JobService.java
@@ -19,9 +19,11 @@ package org.apache.kylin.rest.service;
 
 import java.io.InputStream;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import javax.servlet.http.HttpServletRequest;
@@ -47,9 +49,16 @@ import org.apache.kylin.job.domain.JobInfo;
 import org.apache.kylin.job.execution.ExecutableManager;
 import org.apache.kylin.job.execution.Output;
 import org.apache.kylin.job.rest.JobMapperFilter;
+import org.apache.kylin.metadata.cube.model.NDataflow;
+import org.apache.kylin.metadata.cube.model.NDataflowManager;
+import org.apache.kylin.metadata.view.LogicalView;
+import org.apache.kylin.metadata.view.LogicalViewManager;
+import org.apache.kylin.rest.request.ReplaceMetaRequest;
 import org.apache.kylin.rest.response.JobStatisticsResponse;
 import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.rest.util.BuildAsyncProfileHelper;
+import org.apache.kylin.util.DumpInfo;
+import org.apache.kylin.util.MetadataDumpUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -271,4 +280,47 @@ public class JobService extends BasicService {
             MsgPicker.setMsg("en");
         }
     }
+
+    public void destroyJobProcess(String project) {
+        aclEvaluate.checkProjectOperationPermission(project);
+        ExecutableManager.getInstance(KylinConfig.getInstanceFromEnv(), 
project).destroyAllProcess();
+    }
+
+    public void updateDumpedMetadata(ReplaceMetaRequest request) throws 
Exception {
+        String project = request.getProject();
+        aclEvaluate.checkProjectOperationPermission(project);
+        String modelId = request.getModelId();
+        String viewTable = request.getViewTable();
+        String distMetaUrl = request.getDistMetaUrl();
+
+        Set<String> dumpList = new LinkedHashSet<>();
+        KylinConfig config = KylinConfig.getInstanceFromEnv();
+        NDataflow df = NDataflowManager.getInstance(config, 
project).getDataflow(modelId);
+        dumpList.addAll(df.collectPrecalculationResource());
+        dumpList.addAll(getLogicalViewMetaDumpList(config, project, viewTable, 
modelId));
+
+        DumpInfo dumpInfo = new DumpInfo(project, distMetaUrl, dumpList, 
DumpInfo.DumpType.DATA_LOADING);
+        MetadataDumpUtil.dumpMetadata(dumpInfo);
+    }
+
+    private Set<String> getLogicalViewMetaDumpList(KylinConfig config, String 
project, String viewTable,
+            String modelId) {
+        Set<String> dumpList = new LinkedHashSet<>();
+        if (!config.isDDLLogicalViewEnabled()) {
+            return dumpList;
+        }
+        LogicalViewManager viewManager = 
LogicalViewManager.getInstance(config);
+        if (StringUtils.isNotBlank(modelId)) {
+            Set<String> viewsMeta = 
viewManager.findLogicalViewsInModel(project, modelId).stream()
+                    
.map(LogicalView::getResourcePath).collect(Collectors.toSet());
+            dumpList.addAll(viewsMeta);
+        }
+        if (StringUtils.isNotBlank(viewTable)) {
+            LogicalView logicalView = 
viewManager.findLogicalViewInProject(project, viewTable);
+            if (logicalView != null) {
+                dumpList.add(logicalView.getResourcePath());
+            }
+        }
+        return dumpList;
+    }
 }
diff --git 
a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobInfoServiceTest.java
 
b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobInfoServiceTest.java
index bc5e46210d..5639ad4b0f 100644
--- 
a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobInfoServiceTest.java
+++ 
b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobInfoServiceTest.java
@@ -115,6 +115,8 @@ import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import lombok.val;
@@ -170,6 +172,56 @@ public class JobInfoServiceTest extends LogOutputTestCase {
         cleanupTestMetadata();
     }
 
+    @Test
+    public void testListJobsWithoutProjectCheckIsGlobalAdmin() {
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate).checkIsGlobalAdmin();
+        JobFilter jobFilter = new JobFilter(Lists.newArrayList(), 
Lists.newArrayList(), 4, "", "", false, "", "",
+                true);
+        Assertions.assertThatThrownBy(() -> jobInfoService.listJobs(jobFilter))
+                .isInstanceOf(AccessDeniedException.class);
+        Assertions.assertThatThrownBy(() -> 
jobInfoService.countJobs(jobFilter))
+                .isInstanceOf(AccessDeniedException.class);
+        Mockito.verify(aclEvaluate, Mockito.times(2)).checkIsGlobalAdmin();
+    }
+
+    @Test
+    public void testDiscardJobsCheckProjectOperationPermission() {
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                .checkProjectOperationPermission(DEFAULT_PROJECT);
+        Assertions
+                .assertThatThrownBy(
+                        () -> jobInfoService.discardJobs(DEFAULT_PROJECT, 
Lists.newArrayList("mock_job_id")))
+                .isInstanceOf(AccessDeniedException.class);
+    }
+
+    @Test
+    public void testRemoteUpdateJobStatusCheckAcl() throws Exception {
+        
Mockito.doNothing().when(jobInfoService).forwardRequestToTargetNode(Mockito.any(),
 Mockito.any(),
+                Mockito.anyString(), Mockito.anyString());
+        JobUpdateRequest request = new JobUpdateRequest();
+        request.setProject(DEFAULT_PROJECT);
+        request.setJobIds(Lists.newArrayList("mock_job_id"));
+        request.setStatuses(Lists.newArrayList());
+        Map<String, List<String>> nodeWithJobs = Maps.newHashMap();
+        nodeWithJobs.put("127.0.0.1:7070", Lists.newArrayList("mock_job_id"));
+
+        val response = jobInfoService.remoteUpdateJobStatus(request, new 
HttpHeaders(), nodeWithJobs);
+        assertEquals(KylinException.CODE_SUCCESS, response.getCode());
+        
Mockito.verify(aclEvaluate).checkProjectOperationPermission(DEFAULT_PROJECT);
+        
Mockito.verify(jobInfoService).forwardRequestToTargetNode(Mockito.any(), 
Mockito.any(),
+                Mockito.eq("127.0.0.1:7070"), Mockito.eq("/jobs/status"));
+
+        // acl denied: no forwarding happens
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                .checkProjectOperationPermission(DEFAULT_PROJECT);
+        Assertions
+                .assertThatThrownBy(() -> 
jobInfoService.remoteUpdateJobStatus(request, new HttpHeaders(),
+                        nodeWithJobs))
+                .isInstanceOf(AccessDeniedException.class);
+        Mockito.verify(jobInfoService, 
Mockito.times(1)).forwardRequestToTargetNode(Mockito.any(), Mockito.any(),
+                Mockito.anyString(), Mockito.anyString());
+    }
+
     @Test
     public void testListJobs() throws Exception {
         val mockJobs = mockDetailJobs(true);
diff --git 
a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
 
b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
index f0f6a8ded5..011246ff95 100644
--- 
a/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
+++ 
b/src/data-loading-service/src/test/java/org/apache/kylin/rest/service/JobServiceTest.java
@@ -77,6 +77,7 @@ import org.apache.kylin.metadata.table.InternalTableDesc;
 import org.apache.kylin.metadata.table.InternalTableManager;
 import org.apache.kylin.profiler.ProfilerStatus;
 import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.request.ReplaceMetaRequest;
 import org.apache.kylin.rest.response.ExecutableResponse;
 import org.apache.kylin.rest.response.JobStatisticsResponse;
 import org.apache.kylin.rest.util.AclEvaluate;
@@ -98,6 +99,7 @@ import 
org.springframework.context.annotation.ClassPathScanningCandidateComponen
 import org.springframework.context.event.ContextClosedEvent;
 import org.springframework.core.type.filter.AssignableTypeFilter;
 import org.springframework.http.HttpHeaders;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.test.util.ReflectionTestUtils;
@@ -674,6 +676,25 @@ public class JobServiceTest extends 
NLocalFileMetadataTestCase {
         Mockito.when(executableManager.getAllJobs(Mockito.anyLong(), 
Mockito.anyLong())).thenReturn(jobs);
     }
 
+    @Test
+    public void testDestroyJobProcessCheckAcl() {
+        jobService.destroyJobProcess(project);
+        Mockito.verify(aclEvaluate).checkProjectOperationPermission(project);
+
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                .checkProjectOperationPermission(project);
+        Assert.assertThrows(AccessDeniedException.class, () -> 
jobService.destroyJobProcess(project));
+    }
+
+    @Test
+    public void testUpdateDumpedMetadataCheckAcl() {
+        ReplaceMetaRequest request = new ReplaceMetaRequest();
+        request.setProject(project);
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                .checkProjectOperationPermission(project);
+        Assert.assertThrows(AccessDeniedException.class, () -> 
jobService.updateDumpedMetadata(request));
+    }
+
     @Test
     public void testGetJobStats() throws ParseException {
         JobStatisticsResponse jobStats = jobService.getJobStats("default", 
Long.MIN_VALUE, Long.MAX_VALUE);
diff --git 
a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/SparkSourceService.java
 
b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/SparkSourceService.java
index 4718713c27..d5287aa2cf 100644
--- 
a/src/datasource-service/src/main/java/org/apache/kylin/rest/service/SparkSourceService.java
+++ 
b/src/datasource-service/src/main/java/org/apache/kylin/rest/service/SparkSourceService.java
@@ -44,6 +44,7 @@ import org.apache.kylin.rest.request.DDLRequest;
 import org.apache.kylin.rest.response.DDLResponse;
 import org.apache.kylin.rest.response.ExportTablesResponse;
 import org.apache.kylin.rest.response.TableNameResponse;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.source.ISourceMetadataExplorer;
 import org.apache.kylin.source.SourceFactory;
 import org.apache.spark.sql.AnalysisException;
@@ -60,6 +61,7 @@ import org.apache.spark.sql.catalyst.catalog.CatalogTable;
 import org.apache.spark.sql.delta.DeltaTableUtils;
 import org.apache.spark.sql.types.Metadata;
 import org.apache.spark.sql.types.StructField;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -90,7 +92,11 @@ public class SparkSourceService extends BasicService {
             + "        LO_SHIPMODE,\n" + "        LO_EXTENDEDPRICE*LO_DISCOUNT 
as V_REVENUE\n"
             + "        from SSB.LINEORDER";
 
+    @Autowired
+    private AclEvaluate aclEvaluate;
+
     public DDLResponse executeSQL(DDLRequest request) {
+        aclEvaluate.checkIsGlobalAdmin();
         List<String> sqlList = Arrays.asList(request.getSql().split(";"));
         if (!Strings.isNullOrEmpty(request.getDatabase())) {
             executeSQL("use " + request.getDatabase());
@@ -119,6 +125,7 @@ public class SparkSourceService extends BasicService {
     }
 
     public void dropTable(String database, String table) throws 
AnalysisException {
+        aclEvaluate.checkIsGlobalAdmin();
         SparkSession ss = SparderEnv.getSparkSession();
         if (ss.catalog().tableExists(database, table)) {
             val t = ss.catalog().getTable(database, table);
diff --git 
a/src/datasource-service/src/test/java/org/apache/kylin/rest/service/SparkSourceServiceTest.java
 
b/src/datasource-service/src/test/java/org/apache/kylin/rest/service/SparkSourceServiceTest.java
index 0373f12adf..e31dc52f17 100644
--- 
a/src/datasource-service/src/test/java/org/apache/kylin/rest/service/SparkSourceServiceTest.java
+++ 
b/src/datasource-service/src/test/java/org/apache/kylin/rest/service/SparkSourceServiceTest.java
@@ -36,6 +36,7 @@ import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.rest.request.DDLRequest;
 import org.apache.kylin.rest.response.DDLResponse;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.spark.sql.AnalysisException;
 import org.apache.spark.sql.DDLDesc;
 import org.apache.spark.sql.DdlOperation;
@@ -51,6 +52,8 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mockito;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import scala.Option;
 
@@ -60,11 +63,13 @@ public class SparkSourceServiceTest extends 
NLocalFileMetadataTestCase {
     private NProjectManager projectManager;
     @InjectMocks
     private final SparkSourceService sparkSourceService = Mockito.spy(new 
SparkSourceService());
+    private final AclEvaluate aclEvaluate = Mockito.mock(AclEvaluate.class);
     private TestingServer zkTestServer;
 
     @Before
     public void setUp() throws Exception {
         createTestMetadata();
+        ReflectionTestUtils.setField(sparkSourceService, "aclEvaluate", 
aclEvaluate);
         ss = SparkSession.builder().appName("local").master("local[1]")
                 .config("spark.sql.extensions", 
"io.delta.sql.DeltaSparkSessionExtension")
                 .config("spark.sql.catalog.spark_catalog", 
"org.apache.spark.sql.delta.catalog.DeltaCatalog")
@@ -315,4 +320,14 @@ public class SparkSourceServiceTest extends 
NLocalFileMetadataTestCase {
     public void testTableExists() {
         Assert.assertTrue(sparkSourceService.tableExists("default", 
"COUNTRY"));
     }
+
+    @Test
+    public void testCheckIsGlobalAdmin() {
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate).checkIsGlobalAdmin();
+        DDLRequest ddlRequest = new DDLRequest();
+        ddlRequest.setSql("show databases");
+        Assert.assertThrows(AccessDeniedException.class, () -> 
sparkSourceService.executeSQL(ddlRequest));
+        Assert.assertThrows(AccessDeniedException.class, () -> 
sparkSourceService.dropTable("default", "COUNTRY"));
+        Assert.assertTrue(sparkSourceService.tableExists("default", 
"COUNTRY"));
+    }
 }
diff --git 
a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/MetaStoreService.java
 
b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/MetaStoreService.java
index 95c0528530..cd59f12a37 100644
--- 
a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/MetaStoreService.java
+++ 
b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/MetaStoreService.java
@@ -502,9 +502,9 @@ public class MetaStoreService extends BasicService {
 
     @VisibleForTesting
     public static String getModelMetadataProjectName(Map<String, RawResource> 
rawResourceMap) {
-        RawResource raw = rawResourceMap.values().stream()
-                .filter(rawResource -> rawResource != null && 
rawResource.getProject() != null
-                        && rawResource.getMetaType() == 
MetadataType.MODEL).findAny().orElse(null);
+        RawResource raw = rawResourceMap.values().stream().filter(rawResource 
-> rawResource != null
+                && rawResource.getProject() != null && 
rawResource.getMetaType() == MetadataType.MODEL).findAny()
+                .orElse(null);
         if (raw == null) {
             throw new KylinException(MODEL_METADATA_FILE_ERROR, 
MsgPicker.getMsg().getModelMetadataPackageInvalid());
         }
@@ -880,19 +880,23 @@ public class MetaStoreService extends BasicService {
 
     public void cleanupMeta(String project) {
         if (project.equals(UnitOfWork.GLOBAL_UNIT)) {
+            aclEvaluate.checkIsGlobalAdmin();
             RoutineToolHelper.cleanGlobalSourceUsage();
             RoutineToolHelper.cleanQueryHistoriesAsync().join();
         } else {
+            aclEvaluate.checkProjectAdminPermission(project);
             RoutineToolHelper.cleanMetaByProject(project);
         }
     }
 
     public void cleanupStorage(String[] projectsToClean, boolean 
cleanupStorage) {
+        aclEvaluate.checkIsGlobalAdmin();
         
CleanTaskExecutorService.getInstance().cleanStorageForService(cleanupStorage, 
Arrays.asList(projectsToClean),
                 0D, 0);
     }
 
     public void cleanupStorage(StorageCleanupRequest request, 
HttpServletRequest servletRequest) {
+        aclEvaluate.checkIsGlobalAdmin();
         if (routeService.needRoute()) {
             String url = StringUtils.stripEnd(servletRequest.getRequestURI(), 
"/") + "/tenant_node";
             routeService.asyncRouteForMultiTenantMode(servletRequest, url);
diff --git 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreServiceTest.java
 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreServiceTest.java
index 6dda3482e6..c904ebb26d 100644
--- 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreServiceTest.java
+++ 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreServiceTest.java
@@ -88,8 +88,10 @@ import 
org.apache.kylin.metadata.recommendation.candidate.RawRecItem;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.request.ModelConfigRequest;
 import org.apache.kylin.rest.request.ModelImportRequest;
+import org.apache.kylin.rest.request.StorageCleanupRequest;
 import org.apache.kylin.rest.response.LoadTableResponse;
 import org.apache.kylin.rest.response.ModelPreviewResponse;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.junit.After;
@@ -103,6 +105,7 @@ import org.mockito.Mockito;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -1536,4 +1539,29 @@ public class MetaStoreServiceTest extends 
ServiceTestBase {
         metaStoreService.cleanupMeta(UnitOfWork.GLOBAL_UNIT);
         metaStoreService.cleanupMeta(PROJECT_DEFAULT);
     }
+
+    @Test
+    public void testCleanupCheckPermission() {
+        Object originAclEvaluate = 
ReflectionTestUtils.getField(metaStoreService, "aclEvaluate");
+        AclEvaluate aclEvaluate = Mockito.mock(AclEvaluate.class);
+        ReflectionTestUtils.setField(metaStoreService, "aclEvaluate", 
aclEvaluate);
+        try {
+            Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate).checkIsGlobalAdmin();
+            Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                    .checkProjectAdminPermission(PROJECT_DEFAULT);
+
+            Assert.assertThrows(AccessDeniedException.class,
+                    () -> 
metaStoreService.cleanupMeta(UnitOfWork.GLOBAL_UNIT));
+            Assert.assertThrows(AccessDeniedException.class, () -> 
metaStoreService.cleanupMeta(PROJECT_DEFAULT));
+            Assert.assertThrows(AccessDeniedException.class,
+                    () -> metaStoreService.cleanupStorage(new String[] {}, 
false));
+            Assert.assertThrows(AccessDeniedException.class,
+                    () -> metaStoreService.cleanupStorage(new 
StorageCleanupRequest(), null));
+
+            Mockito.verify(aclEvaluate, Mockito.times(3)).checkIsGlobalAdmin();
+            
Mockito.verify(aclEvaluate).checkProjectAdminPermission(PROJECT_DEFAULT);
+        } finally {
+            ReflectionTestUtils.setField(metaStoreService, "aclEvaluate", 
originAclEvaluate);
+        }
+    }
 }
diff --git 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreTenantServiceTest.java
 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreTenantServiceTest.java
index 5076bcdd3c..5eaaf22e1c 100644
--- 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreTenantServiceTest.java
+++ 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/MetaStoreTenantServiceTest.java
@@ -30,6 +30,7 @@ import org.apache.kylin.metadata.resourcegroup.ResourceGroup;
 import org.apache.kylin.metadata.resourcegroup.ResourceGroupManager;
 import org.apache.kylin.metadata.streaming.ReflectionUtils;
 import org.apache.kylin.rest.request.StorageCleanupRequest;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.tool.garbage.CleanTaskExecutorService;
 import org.apache.kylin.tool.garbage.PriorityExecutor;
 import org.junit.Assert;
@@ -66,6 +67,8 @@ public class MetaStoreTenantServiceTest {
     @Mock
     private RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
     @Mock
+    private AclEvaluate aclEvaluate = Mockito.mock(AclEvaluate.class);
+    @Mock
     private ResourceGroupManager rgManager = 
Mockito.mock(ResourceGroupManager.class);
     @Mock
     private KylinConfig kylinConfig = Mockito.mock(KylinConfig.class);
@@ -80,6 +83,7 @@ public class MetaStoreTenantServiceTest {
         
Mockito.when(kylinConfig.isKylinMultiTenantEnabled()).thenReturn(false);
 
         ReflectionUtils.setField(metaStoreService, "routeService", 
routeService);
+        ReflectionUtils.setField(metaStoreService, "aclEvaluate", aclEvaluate);
         ReflectionUtils.setField(routeService, "restTemplate", restTemplate);
 
         CleanTaskExecutorService.getInstance()
diff --git 
a/src/ops-server/src/main/java/org/apache/kylin/rest/controller/OpsController.java
 
b/src/ops-server/src/main/java/org/apache/kylin/rest/controller/OpsController.java
index 0d28102df7..c7b1fb9207 100644
--- 
a/src/ops-server/src/main/java/org/apache/kylin/rest/controller/OpsController.java
+++ 
b/src/ops-server/src/main/java/org/apache/kylin/rest/controller/OpsController.java
@@ -115,6 +115,7 @@ public class OpsController extends NBasicController {
     @GetMapping(value = "/metadata/dump")
     @ResponseBody
     public EnvelopeResponse<String> dumpMetadata(@RequestParam(value = 
"dump_path") String dumpPath) throws Exception {
+        getProjectStrAndCheckPermission(UnitOfWork.GLOBAL_UNIT);
         KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
         HDFSMetadataTool.cleanBeforeBackup(kylinConfig);
         KylinConfig backupConfig = kylinConfig.getMetadataBackupFromSystem() ? 
kylinConfig
diff --git 
a/src/ops-server/src/test/java/org/apache/kylin/rest/controller/OpsControllerTest.java
 
b/src/ops-server/src/test/java/org/apache/kylin/rest/controller/OpsControllerTest.java
index f6fc290964..aa40070387 100644
--- 
a/src/ops-server/src/test/java/org/apache/kylin/rest/controller/OpsControllerTest.java
+++ 
b/src/ops-server/src/test/java/org/apache/kylin/rest/controller/OpsControllerTest.java
@@ -34,7 +34,9 @@ import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.request.DiagPackageRequest;
 import org.apache.kylin.rest.request.DiagProgressRequest;
 import org.apache.kylin.rest.service.SystemService;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -43,6 +45,7 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.springframework.http.MediaType;
+import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.test.web.servlet.MockMvc;
@@ -61,6 +64,9 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
     @Mock
     private ClusterManager clusterManager;
 
+    @Mock
+    private AclEvaluate aclEvaluate;
+
     @InjectMocks
     private OpsController opsController = Mockito.spy(new OpsController());
 
@@ -206,6 +212,13 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
         Mockito.verify(opsController).updateDiagProgress(Mockito.any());
     }
 
+    @Test
+    public void testDumpMetadataCheckGlobalAdmin() throws Exception {
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate).checkIsGlobalAdmin();
+        Assert.assertThrows(AccessDeniedException.class, () -> 
opsController.dumpMetadata("/tmp/dump"));
+        Mockito.verify(aclEvaluate).checkIsGlobalAdmin();
+    }
+
     private String mockHost() {
         return "http://"; + AddressUtil.getLocalInstance();
     }
diff --git a/src/query-booter/src/main/resources/kylinSecurity.xml 
b/src/query-booter/src/main/resources/kylinSecurity.xml
index bbf156f8d3..33c0f8ff98 100644
--- a/src/query-booter/src/main/resources/kylinSecurity.xml
+++ b/src/query-booter/src/main/resources/kylinSecurity.xml
@@ -251,8 +251,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git 
a/src/query-service/src/main/java/org/apache/kylin/rest/service/QuerySQLBlacklistService.java
 
b/src/query-service/src/main/java/org/apache/kylin/rest/service/QuerySQLBlacklistService.java
index 44ea64afa9..4a04e19bea 100644
--- 
a/src/query-service/src/main/java/org/apache/kylin/rest/service/QuerySQLBlacklistService.java
+++ 
b/src/query-service/src/main/java/org/apache/kylin/rest/service/QuerySQLBlacklistService.java
@@ -28,23 +28,31 @@ import org.apache.kylin.query.blacklist.SQLBlacklistManager;
 import org.apache.kylin.rest.aspect.Transaction;
 import org.apache.kylin.rest.request.SQLBlacklistItemRequest;
 import org.apache.kylin.rest.request.SQLBlacklistRequest;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component("querySQLBlacklistService")
 public class QuerySQLBlacklistService extends BasicService {
 
+    @Autowired
+    private AclEvaluate aclEvaluate;
+
     private SQLBlacklistManager getSQLBlacklistManager() {
         return SQLBlacklistManager.getInstance(getConfig());
     }
 
     public SQLBlacklist getSqlBlacklist(String project) {
+        aclEvaluate.checkProjectWritePermission(project);
         return getSQLBlacklistManager().getSqlBlacklist(project);
     }
 
     @Transaction(project = 0)
     public SQLBlacklist saveSqlBlacklist(SQLBlacklistRequest 
sqlBlacklistRequest) throws IOException {
+        String project = sqlBlacklistRequest.getProject();
+        aclEvaluate.checkProjectWritePermission(project);
         SQLBlacklist sqlBlacklist = new SQLBlacklist();
-        sqlBlacklist.setProject(sqlBlacklistRequest.getProject());
+        sqlBlacklist.setProject(project);
         List<SQLBlacklistItemRequest> itemRequestList = 
sqlBlacklistRequest.getBlacklistItems();
         List<SQLBlacklistItem> itemList = Lists.newArrayList();
         if (null != itemRequestList) {
@@ -62,10 +70,12 @@ public class QuerySQLBlacklistService extends BasicService {
     }
 
     public SQLBlacklistItem getItemById(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest) {
+        aclEvaluate.checkProjectWritePermission(project);
         return getSQLBlacklistManager().getSqlBlacklistItemById(project, 
sqlBlacklistItemRequest.getId());
     }
 
     public SQLBlacklistItem getItemByRegex(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest) {
+        aclEvaluate.checkProjectWritePermission(project);
         String regex = sqlBlacklistItemRequest.getRegex();
         if (null == regex) {
             return null;
@@ -74,6 +84,7 @@ public class QuerySQLBlacklistService extends BasicService {
     }
 
     public SQLBlacklistItem getItemBySql(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest) {
+        aclEvaluate.checkProjectWritePermission(project);
         String sql = sqlBlacklistItemRequest.getSql();
         if (null == sql) {
             return null;
@@ -84,6 +95,7 @@ public class QuerySQLBlacklistService extends BasicService {
     @Transaction(project = 0)
     public SQLBlacklist addSqlBlacklistItem(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest)
             throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
         SQLBlacklistItem sqlBlacklistItem = new SQLBlacklistItem();
         sqlBlacklistItem.updateRandomUuid();
         sqlBlacklistItem.setRegex(sqlBlacklistItemRequest.getRegex());
@@ -93,6 +105,7 @@ public class QuerySQLBlacklistService extends BasicService {
     }
 
     public SQLBlacklistItem checkConflictRegex(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest) {
+        aclEvaluate.checkProjectWritePermission(project);
         String itemId = sqlBlacklistItemRequest.getId();
         String regex = sqlBlacklistItemRequest.getRegex();
         SQLBlacklist sqlBlacklist = 
getSQLBlacklistManager().getSqlBlacklist(project);
@@ -108,6 +121,7 @@ public class QuerySQLBlacklistService extends BasicService {
     }
 
     public SQLBlacklistItem checkConflictSql(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest) {
+        aclEvaluate.checkProjectWritePermission(project);
         String itemId = sqlBlacklistItemRequest.getId();
         String sql = sqlBlacklistItemRequest.getSql();
         SQLBlacklist sqlBlacklist = 
getSQLBlacklistManager().getSqlBlacklist(project);
@@ -125,6 +139,7 @@ public class QuerySQLBlacklistService extends BasicService {
     @Transaction(project = 0)
     public SQLBlacklist updateSqlBlacklistItem(String project, 
SQLBlacklistItemRequest sqlBlacklistItemRequest)
             throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
         SQLBlacklistItem sqlBlacklistItem = new SQLBlacklistItem();
         sqlBlacklistItem.setId(sqlBlacklistItemRequest.getId());
         sqlBlacklistItem.setRegex(sqlBlacklistItemRequest.getRegex());
@@ -135,11 +150,13 @@ public class QuerySQLBlacklistService extends 
BasicService {
 
     @Transaction(project = 0)
     public SQLBlacklist deleteSqlBlacklistItem(String project, String id) 
throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
         return getSQLBlacklistManager().deleteSqlBlacklistItem(project, id);
     }
 
     @Transaction(project = 0)
     public SQLBlacklist clearSqlBlacklist(String project) throws IOException {
+        aclEvaluate.checkProjectWritePermission(project);
         return getSQLBlacklistManager().clearBlacklist(project);
     }
 }
diff --git 
a/src/query-service/src/test/java/org/apache/kylin/rest/service/QuerySQLBlacklistServiceTest.java
 
b/src/query-service/src/test/java/org/apache/kylin/rest/service/QuerySQLBlacklistServiceTest.java
index 447ed2499c..d497cc8a75 100644
--- 
a/src/query-service/src/test/java/org/apache/kylin/rest/service/QuerySQLBlacklistServiceTest.java
+++ 
b/src/query-service/src/test/java/org/apache/kylin/rest/service/QuerySQLBlacklistServiceTest.java
@@ -24,8 +24,13 @@ import org.apache.kylin.guava30.shaded.common.collect.Lists;
 import org.apache.kylin.junit.annotation.MetadataInfo;
 import org.apache.kylin.rest.request.SQLBlacklistItemRequest;
 import org.apache.kylin.rest.request.SQLBlacklistRequest;
+import org.apache.kylin.rest.util.AclEvaluate;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import lombok.val;
 
@@ -34,6 +39,13 @@ class QuerySQLBlacklistServiceTest {
 
     private final QuerySQLBlacklistService service = new 
QuerySQLBlacklistService();
 
+    private final AclEvaluate aclEvaluate = Mockito.mock(AclEvaluate.class);
+
+    @BeforeEach
+    void setUp() {
+        ReflectionTestUtils.setField(service, "aclEvaluate", aclEvaluate);
+    }
+
     private SQLBlacklistRequest blacklistRequest(SQLBlacklistItemRequest... 
items) {
         val req = new SQLBlacklistRequest();
         req.setProject("default");
@@ -121,4 +133,30 @@ class QuerySQLBlacklistServiceTest {
         Assertions.assertNotNull(service.checkConflictSql("default", 
itemRequest(id1, null, "d", 0)));
     }
 
+    @Test
+    public void testCheckProjectWritePermission() {
+        Mockito.doThrow(new AccessDeniedException("Access is 
denied")).when(aclEvaluate)
+                .checkProjectWritePermission("default");
+
+        Assertions.assertThrows(AccessDeniedException.class, () -> 
service.getSqlBlacklist("default"));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> 
service.saveSqlBlacklist(blacklistRequest(itemRequest(null, "a", "b", 8))));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.getItemById("default", itemRequest("1", null, 
null, 0)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.getItemByRegex("default", itemRequest(null, "a", 
null, 0)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.getItemBySql("default", itemRequest(null, null, 
"b", 0)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.addSqlBlacklistItem("default", itemRequest(null, 
"a", "b", 8)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.checkConflictRegex("default", itemRequest("1", 
"a", null, 0)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.checkConflictSql("default", itemRequest("1", 
null, "b", 0)));
+        Assertions.assertThrows(AccessDeniedException.class,
+                () -> service.updateSqlBlacklistItem("default", 
itemRequest("1", "a", "b", 8)));
+        Assertions.assertThrows(AccessDeniedException.class, () -> 
service.deleteSqlBlacklistItem("default", "1"));
+        Assertions.assertThrows(AccessDeniedException.class, () -> 
service.clearSqlBlacklist("default"));
+    }
+
 }
diff --git a/src/rec-booter/src/main/resources/kylinSecurity.xml 
b/src/rec-booter/src/main/resources/kylinSecurity.xml
index 9f4134ddb6..6f81b9283c 100644
--- a/src/rec-booter/src/main/resources/kylinSecurity.xml
+++ b/src/rec-booter/src/main/resources/kylinSecurity.xml
@@ -252,8 +252,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git a/src/server/src/main/resources/kylinSecurity.xml 
b/src/server/src/main/resources/kylinSecurity.xml
index acdccdfd84..2870174e7b 100644
--- a/src/server/src/main/resources/kylinSecurity.xml
+++ b/src/server/src/main/resources/kylinSecurity.xml
@@ -256,8 +256,6 @@
             <scr:intercept-url pattern="/api/prometheus" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/spark/prometheus" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>
diff --git a/src/streaming-service/src/test/resources/kylinSecurity.xml 
b/src/streaming-service/src/test/resources/kylinSecurity.xml
index 07007e67d8..d97e25a208 100644
--- a/src/streaming-service/src/test/resources/kylinSecurity.xml
+++ b/src/streaming-service/src/test/resources/kylinSecurity.xml
@@ -251,8 +251,6 @@
             <scr:intercept-url pattern="/api/kg/health" access="permitAll"/>
             <scr:intercept-url pattern="/api/kg/health/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/monitor/alert" 
access="hasRole('ROLE_ADMIN')"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup" 
access="permitAll"/>
-            <scr:intercept-url pattern="/api/metastore/cleanup_storage" 
access="permitAll"/>
             <scr:intercept-url pattern="/api/epoch" access="permitAll"/>
             <scr:intercept-url pattern="/api/broadcast/**" access="permitAll"/>
             <scr:intercept-url pattern="/api/config/is_cloud" 
access="permitAll"/>

Reply via email to