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"/>