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

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

commit 018327cdd021b12cb584563d958d1c1843282d29
Author: Li Yang <[email protected]>
AuthorDate: Sat Nov 7 14:05:22 2026 +0800

    KYLIN-6121 secure async query cleanup RPC
---
 .../apache/kylin/rest/service/RouteService.java    |  2 +-
 .../kylin/rest/service/RouteServiceTest.java       |  9 +++++++
 .../rest/controller/NAsyncQueryController.java     |  4 ++-
 .../rest/controller/NAsyncQueryControllerTest.java | 31 ++++++++++++++++++++--
 4 files changed, 42 insertions(+), 4 deletions(-)

diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/RouteService.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/RouteService.java
index 728ff87e74..869e1a2480 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/RouteService.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/RouteService.java
@@ -113,7 +113,7 @@ public class RouteService extends BasicService {
             throws Exception {
         String uri = StringUtils.stripEnd(request.getRequestURI(), "/");
         val url = "http://"; + instance + uri + "/tenant_node";
-        val response = generateTaskForRemoteHost(request, url);
+        val response = generateTaskForRemoteHost(request, url, new byte[0], 
getInternalRpcHeaders());
         val data = (Boolean) response.getData();
         log.info("deleteAllFolder instance[{}] result : [{}]", instance, data);
         if (data) {
diff --git 
a/src/common-service/src/test/java/org/apache/kylin/rest/service/RouteServiceTest.java
 
b/src/common-service/src/test/java/org/apache/kylin/rest/service/RouteServiceTest.java
index 23c4343c2c..819d4a5e13 100644
--- 
a/src/common-service/src/test/java/org/apache/kylin/rest/service/RouteServiceTest.java
+++ 
b/src/common-service/src/test/java/org/apache/kylin/rest/service/RouteServiceTest.java
@@ -39,6 +39,7 @@ import org.apache.kylin.common.KapConfig;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.msg.MsgPicker;
+import 
org.apache.kylin.common.persistence.transaction.BroadcastEventReadyNotifier;
 import org.apache.kylin.common.persistence.transaction.UnitOfWork;
 import org.apache.kylin.common.response.RestResponse;
 import org.apache.kylin.common.util.ExecutorServiceUtil;
@@ -69,6 +70,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -77,6 +79,7 @@ import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
+import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -173,8 +176,14 @@ public class RouteServiceTest {
     public void deleteAllFolder() throws Exception {
         val result = new CountDownLatch(1);
         val request = new MockHttpServletRequest();
+        
Mockito.when(kylinConfig.getBroadcastToken()).thenReturn("service-token");
         routeService.deleteAllFolder("127.0.0.1:8080", request, result);
         Assert.assertEquals(0, result.getCount());
+        ArgumentCaptor<HttpEntity> requestEntity = 
ArgumentCaptor.forClass(HttpEntity.class);
+        Mockito.verify(restTemplate).exchange(ArgumentMatchers.anyString(), 
ArgumentMatchers.any(HttpMethod.class),
+                requestEntity.capture(), ArgumentMatchers.<Class<byte[]>> 
any());
+        Assert.assertEquals("service-token",
+                
requestEntity.getValue().getHeaders().getFirst(BroadcastEventReadyNotifier.BROADCAST_TOKEN_HEADER));
     }
 
     @Test
diff --git 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NAsyncQueryController.java
 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NAsyncQueryController.java
index 620069975b..c50283b708 100644
--- 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NAsyncQueryController.java
+++ 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NAsyncQueryController.java
@@ -57,6 +57,7 @@ import org.apache.kylin.rest.request.AsyncQuerySQLRequest;
 import org.apache.kylin.rest.response.AsyncQueryResponse;
 import org.apache.kylin.rest.response.EnvelopeResponse;
 import org.apache.kylin.rest.response.SQLResponse;
+import org.apache.kylin.rest.security.InternalRpcSecurity;
 import org.apache.kylin.rest.service.AsyncQueryService;
 import org.apache.kylin.rest.service.QueryService;
 import org.apache.kylin.rest.util.AclEvaluate;
@@ -230,7 +231,8 @@ public class NAsyncQueryController extends NBasicController 
{
     @ApiOperation(value = "delete all folder", tags = { "QE" })
     @DeleteMapping(value = "/async_query/tenant_node")
     @ResponseBody
-    public EnvelopeResponse<Boolean> deleteAllFolder() throws Exception {
+    public EnvelopeResponse<Boolean> deleteAllFolder(HttpServletRequest 
request) throws Exception {
+        InternalRpcSecurity.requireGlobalAdminOrServiceToken(request);
         if (asyncQueryService.deleteAllFolder()) {
             return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, true, 
"");
         } else {
diff --git 
a/src/query-server/src/test/java/org/apache/kylin/rest/controller/NAsyncQueryControllerTest.java
 
b/src/query-server/src/test/java/org/apache/kylin/rest/controller/NAsyncQueryControllerTest.java
index bfad277739..3b95d4b977 100644
--- 
a/src/query-server/src/test/java/org/apache/kylin/rest/controller/NAsyncQueryControllerTest.java
+++ 
b/src/query-server/src/test/java/org/apache/kylin/rest/controller/NAsyncQueryControllerTest.java
@@ -39,6 +39,7 @@ import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.QueryContext;
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.msg.MsgPicker;
+import 
org.apache.kylin.common.persistence.transaction.BroadcastEventReadyNotifier;
 import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
 import org.apache.kylin.query.util.AsyncQueryUtil;
@@ -60,6 +61,7 @@ import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -550,27 +552,52 @@ public class NAsyncQueryControllerTest extends 
NLocalFileMetadataTestCase {
     @Test
     public void deleteAllFolder() throws Exception {
         Mockito.doReturn(true).when(asyncQueryService).deleteAllFolder();
+        val request = new MockHttpServletRequest();
 
         
mockMvc.perform(MockMvcRequestBuilders.delete("/api/async_query/tenant_node")
                         
.accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                 .andExpect(MockMvcResultMatchers.status().isOk());
 
-        val response = nAsyncQueryController.deleteAllFolder();
+        val response = nAsyncQueryController.deleteAllFolder(request);
         Assert.assertTrue(response.getData());
     }
 
     @Test
     public void deleteAllFolderFailed() throws Exception {
         Mockito.doReturn(false).when(asyncQueryService).deleteAllFolder();
+        val request = new MockHttpServletRequest();
 
         
mockMvc.perform(MockMvcRequestBuilders.delete("/api/async_query/tenant_node")
                         
.accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                 .andExpect(MockMvcResultMatchers.status().isOk());
 
-        val response = nAsyncQueryController.deleteAllFolder();
+        val response = nAsyncQueryController.deleteAllFolder(request);
         Assert.assertFalse(response.getData());
     }
 
+    @Test
+    public void deleteAllFolderRejectsLowPrivilegeUser() throws Exception {
+        Authentication otherUser = new TestingAuthenticationToken("OTHER", 
"OTHER", Constant.IDENTITY_USER);
+        SecurityContextHolder.getContext().setAuthentication(otherUser);
+
+        Assert.assertThrows(KylinException.class,
+                () -> nAsyncQueryController.deleteAllFolder(new 
MockHttpServletRequest()));
+        Mockito.verify(asyncQueryService, Mockito.never()).deleteAllFolder();
+    }
+
+    @Test
+    public void deleteAllFolderAcceptsServiceToken() throws Exception {
+        Authentication otherUser = new TestingAuthenticationToken("OTHER", 
"OTHER", Constant.IDENTITY_USER);
+        SecurityContextHolder.getContext().setAuthentication(otherUser);
+        Mockito.doReturn(true).when(asyncQueryService).deleteAllFolder();
+        val request = new MockHttpServletRequest();
+        request.addHeader(BroadcastEventReadyNotifier.BROADCAST_TOKEN_HEADER, 
getTestConfig().getBroadcastToken());
+
+        val response = nAsyncQueryController.deleteAllFolder(request);
+
+        Assert.assertTrue(response.getData());
+    }
+
     @Test
     public void testBatchDeleteOldResultWhenTimeFormatError() throws Exception 
{
         Mockito.doThrow(new ParseException("", 
0)).when(asyncQueryService).batchDelete(Mockito.any(), Mockito.any(),

Reply via email to