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 22eb8fd5df [MINOR] fix API And add host check
22eb8fd5df is described below

commit 22eb8fd5dfdeffa3fc57bae6d5c82a019eece662
Author: jlf <[email protected]>
AuthorDate: Wed Aug 27 18:45:18 2025 +0800

    [MINOR] fix API And add host check
---
 src/common-server/pom.xml                          |   6 +
 .../kylin/rest/controller/NBasicController.java    |  26 +++
 .../kylin/rest/controller/NSystemController.java   |   1 +
 .../rest/controller/NBasicControllerTest.java      |  71 +++++++
 .../org/apache/kylin/rest/service/FileService.java |  27 ++-
 .../apache/kylin/rest/service/ProjectService.java  |  23 ++-
 .../apache/kylin/rest/service/FileServiceTest.java | 228 +++++++++++++++++++--
 .../apache/kylin/common/constant/Constants.java    |   3 +
 .../org/apache/kylin/common/util/AddressUtil.java  |  18 ++
 .../apache/kylin/common/util/AddressUtilTest.java  |  53 +++++
 .../resourcegroup/ResourceGroupManager.java        |  14 +-
 .../resourcegroup/ResourceGroupManagerTest.java    |  24 ++-
 .../kylin/rest/service/ProjectServiceTest.java     | 133 ++++++++++++
 .../kylin/rest/controller/OpsController.java       |  11 +-
 .../kylin/rest/controller/OpsControllerTest.java   |  19 +-
 .../apache/kylin/rest/MockClusterManagerTest.java  |  25 +++
 .../kylin/rest/controller/NQueryController.java    |   4 -
 .../kylin/rest/ResourceGroupLoadBalancer.java      |   2 +-
 .../apache/kylin/rest/cluster/ClusterManager.java  |   8 +
 19 files changed, 662 insertions(+), 34 deletions(-)

diff --git a/src/common-server/pom.xml b/src/common-server/pom.xml
index 182094e141..66963d85a1 100644
--- a/src/common-server/pom.xml
+++ b/src/common-server/pom.xml
@@ -38,6 +38,12 @@
             <groupId>org.apache.kylin</groupId>
             <artifactId>kylin-core-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.kylin</groupId>
+            <artifactId>kylin-core-metadata</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.kylin</groupId>
             <artifactId>kylin-core-metadata</artifactId>
diff --git 
a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
 
b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
index 1f2be3ab94..98c5b40fe9 100644
--- 
a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
+++ 
b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NBasicController.java
@@ -79,6 +79,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.KylinConfigBase;
 import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.exception.KylinRuntimeException;
 import org.apache.kylin.common.exception.ServerErrorCode;
 import org.apache.kylin.common.msg.Message;
 import org.apache.kylin.common.msg.MsgPicker;
@@ -96,7 +97,9 @@ import org.apache.kylin.metadata.model.NDataModelManager;
 import org.apache.kylin.metadata.model.TableDesc;
 import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.resourcegroup.ResourceGroupManager;
 import org.apache.kylin.metadata.streaming.KafkaConfigManager;
+import org.apache.kylin.rest.cluster.ClusterManager;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.exception.ForbiddenException;
 import org.apache.kylin.rest.exception.NotFoundException;
@@ -160,6 +163,9 @@ public class NBasicController {
     @Autowired
     protected UserService userService;
 
+    @Autowired
+    protected ClusterManager clusterManager;
+
     protected Logger getLogger() {
         return logger;
     }
@@ -701,4 +707,24 @@ public class NBasicController {
         int autoGrowCollectionLimit = 
KylinConfig.getInstanceFromEnv().getDataBinderAutoGrowCollectionLimit();
         binder.setAutoGrowCollectionLimit(autoGrowCollectionLimit);
     }
+
+    public void checkServer(String host) {
+        if (StringUtils.isBlank(host)) {
+            throw new KylinRuntimeException("Server cannot be null or empty");
+        }
+
+        val rgManager = 
ResourceGroupManager.getInstance(KylinConfig.getInstanceFromEnv());
+        val serverNotFoundInCluster = clusterManager.checkServer(host);
+
+        if (rgManager.isResourceGroupEnabled()) {
+            val serverNotFoundInResourceGroup = rgManager.checkServer(host);
+            if (serverNotFoundInCluster && serverNotFoundInResourceGroup) {
+                throw new KylinRuntimeException(String.format(Locale.ROOT, 
"Server <%s> not found", host));
+            }
+        } else {
+            if (serverNotFoundInCluster) {
+                throw new KylinRuntimeException(String.format(Locale.ROOT, 
"Server <%s> not found", host));
+            }
+        }
+    }
 }
diff --git 
a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
 
b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
index a9b19a8c7d..0f47deb3df 100644
--- 
a/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
+++ 
b/src/common-server/src/main/java/org/apache/kylin/rest/controller/NSystemController.java
@@ -190,6 +190,7 @@ public class NSystemController extends NBasicController {
     public EnvelopeResponse<String> broadcastMetadataBackup(@RequestBody 
MetadataBackupRequest request) {
         log.info("ResourceGroup[{}] broadcastMetadataBackup tmpFilePath : {}", 
request.getResourceGroupId(),
                 request.getTmpFilePath());
+        checkServer(request.getFromHost());
         fileService.saveBroadcastMetadataBackup(request.getBackupDir(), 
request.getTmpFilePath(),
                 request.getTmpFileSize(), request.getResourceGroupId(), 
request.getFromHost());
         return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
diff --git 
a/src/common-server/src/test/java/org/apache/kylin/rest/controller/NBasicControllerTest.java
 
b/src/common-server/src/test/java/org/apache/kylin/rest/controller/NBasicControllerTest.java
index 45b52958db..8d2f32f310 100644
--- 
a/src/common-server/src/test/java/org/apache/kylin/rest/controller/NBasicControllerTest.java
+++ 
b/src/common-server/src/test/java/org/apache/kylin/rest/controller/NBasicControllerTest.java
@@ -40,12 +40,17 @@ import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.exception.KylinRuntimeException;
 import org.apache.kylin.common.extension.KylinInfoExtension;
 import org.apache.kylin.common.msg.MsgPicker;
+import org.apache.kylin.common.util.AddressUtil;
 import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
 import org.apache.kylin.common.util.Pair;
 import org.apache.kylin.metadata.model.PartitionDesc;
 import org.apache.kylin.metadata.model.TableDesc;
+import org.apache.kylin.metadata.resourcegroup.ResourceGroupManagerTest;
+import org.apache.kylin.rest.cluster.ClusterManager;
+import org.apache.kylin.rest.cluster.MockClusterManager;
 import org.apache.kylin.rest.controller.fixture.FixtureController;
 import org.apache.kylin.rest.exception.ForbiddenException;
 import org.apache.kylin.rest.exception.NotFoundException;
@@ -56,6 +61,7 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
@@ -64,6 +70,7 @@ import 
org.powermock.core.classloader.annotations.PowerMockIgnore;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 import org.springframework.security.access.AccessDeniedException;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
@@ -333,4 +340,68 @@ public class NBasicControllerTest extends 
NLocalFileMetadataTestCase {
         Assert.assertEquals("ip", nBasicController.decodeHost("ip"));
     }
 
+    @Test
+    public void testCheckServerWithResourceGroupEnable() {
+        ClusterManager clusterManager = new MockClusterManager();
+
+        String project = "default";
+        
ResourceGroupManagerTest.mockResourceGroup(AddressUtil.getLocalInstance(), 
project);
+        ReflectionTestUtils.setField(nBasicController, "clusterManager", 
clusterManager);
+
+        // Test valid server - should not throw exception
+        Assertions.assertDoesNotThrow(() -> 
nBasicController.checkServer(AddressUtil.getLocalInstance()));
+        Assertions.assertDoesNotThrow(() -> 
nBasicController.checkServer("127.0.0.1:7070"));
+
+        // Test null host - should throw KylinRuntimeException
+        KylinRuntimeException nullException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(null));
+        Assertions.assertEquals("Server cannot be null or empty", 
nullException.getMessage());
+
+        // Test empty host - should throw KylinRuntimeException
+        KylinRuntimeException emptyException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(""));
+        Assertions.assertEquals("Server cannot be null or empty", 
emptyException.getMessage());
+        KylinRuntimeException emptyException2 = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(" "));
+        Assertions.assertEquals("Server cannot be null or empty", 
emptyException2.getMessage());
+
+        // Test host not found in servers - should throw KylinRuntimeException
+        KylinRuntimeException notFoundException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer("192.168.1.1:8080"));
+        Assertions.assertEquals("Server <192.168.1.1:8080> not found", 
notFoundException.getMessage());
+    }
+
+    @Test
+    public void testCheckServerWithoutResourceGroupEnable() {
+        ClusterManager clusterManager = new MockClusterManager();
+
+        ReflectionTestUtils.setField(nBasicController, "clusterManager", 
clusterManager);
+
+        // Test valid server - should not throw exception
+        Assertions.assertDoesNotThrow(() -> 
nBasicController.checkServer("127.0.0.1:7070"));
+
+        // Test null host - should throw KylinRuntimeException
+        KylinRuntimeException nullException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(null));
+        Assertions.assertEquals("Server cannot be null or empty", 
nullException.getMessage());
+
+        // Test empty host - should throw KylinRuntimeException
+        KylinRuntimeException emptyException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(""));
+        Assertions.assertEquals("Server cannot be null or empty", 
emptyException.getMessage());
+        KylinRuntimeException emptyException2 = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer(" "));
+        Assertions.assertEquals("Server cannot be null or empty", 
emptyException2.getMessage());
+
+        // Test host not found in servers - should throw KylinRuntimeException
+        KylinRuntimeException notFoundException = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> nBasicController.checkServer("192.168.1.1:8080"));
+        Assertions.assertEquals("Server <192.168.1.1:8080> not found", 
notFoundException.getMessage());
+
+        // Test current host not found in servers - should throw 
KylinRuntimeException
+        KylinRuntimeException notFoundException2 = 
Assertions.assertThrows(KylinRuntimeException.class,
+                () -> 
nBasicController.checkServer(AddressUtil.getLocalInstance()));
+        Assertions.assertEquals("Server <" + AddressUtil.getLocalInstance() + 
"> not found",
+                notFoundException2.getMessage());
+    }
 }
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/FileService.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/FileService.java
index dda580a0f4..894b87cb47 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/FileService.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/FileService.java
@@ -20,6 +20,7 @@ package org.apache.kylin.rest.service;
 
 import static org.apache.kylin.common.constant.Constants.BACKSLASH;
 import static org.apache.kylin.common.constant.Constants.METADATA_FILE;
+import static org.apache.kylin.common.constant.Constants.SYSTEM_TMP_DIR;
 import static 
org.apache.kylin.common.constant.HttpConstant.HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON;
 
 import java.io.File;
@@ -28,6 +29,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -60,12 +62,31 @@ import lombok.extern.slf4j.Slf4j;
 @Slf4j
 @Service
 public class FileService extends BasicService {
+    protected static final String METADATA_TMP_PREFIX = 
"KylinMetadataBackupTmp-";
+
     @Autowired
     @Qualifier("normalRestTemplate")
     private RestTemplate restTemplate;
 
+    public static String getSafeAbsolutePath(String filePath) {
+        val basePath = Paths.get(SYSTEM_TMP_DIR).toAbsolutePath().normalize();
+        val targetPath = Paths.get(filePath).toAbsolutePath().normalize();
+
+        if (!targetPath.startsWith(basePath)) {
+            throw new SecurityException("Path outside base directory: " + 
filePath);
+        }
+
+        val metadataTmpPrefix = Paths.get(basePath.toString(), 
METADATA_TMP_PREFIX).toAbsolutePath().normalize();
+        if (!StringUtils.startsWith(targetPath.toString(), 
metadataTmpPrefix.toString())) {
+            throw new SecurityException("Path not kylin metadata tmp 
directory: " + filePath);
+        }
+
+        return targetPath.toString();
+    }
+
     public InputStream getMetadataBackupFromTmpPath(String tmpFilePath, Long 
fileSize) throws IOException {
-        val metadataBackupTmp = new File(tmpFilePath);
+        val realTempPath = getSafeAbsolutePath(tmpFilePath);
+        val metadataBackupTmp = new File(realTempPath);
         if (metadataBackupTmp.isFile()) {
             if (metadataBackupTmp.length() != fileSize) {
                 throw new FileNotFoundException("Metadata backup temp file 
length does not right: " + tmpFilePath
@@ -84,7 +105,7 @@ public class FileService extends BasicService {
         val filePath = new Path(path);
         if (fileSystem.isFile(filePath)) {
             val fileStatus = fileSystem.getFileStatus(filePath);
-            val tempDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+            val tempDirectory = 
Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), 
METADATA_TMP_PREFIX).toFile();
             fileSystem.copyToLocalFile(false, filePath, new 
Path(tempDirectory.getAbsolutePath()), true);
             val tmpFile = new File(tempDirectory, METADATA_FILE);
             if (fileStatus.getLen() != tmpFile.length()) {
@@ -99,7 +120,7 @@ public class FileService extends BasicService {
     }
 
     public String saveMetadataBackupTmpFromRequest(Long fileSize, InputStream 
inputStream) throws IOException {
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         try (val os = new FileOutputStream(tmpFile)) {
             IOUtils.copy(inputStream, os);
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
index 516c07701d..3c5a1a8968 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/service/ProjectService.java
@@ -26,6 +26,7 @@ import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_PASS_
 import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_ENABLE_KEY;
 import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_SOURCE_NAME_KEY;
 import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_USER_KEY;
+import static org.apache.kylin.common.constant.Constants.MAX_FILENAME_LENGTH;
 import static 
org.apache.kylin.common.constant.NonCustomProjectLevelConfig.DATASOURCE_TYPE;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.DATABASE_NOT_EXIST;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.DUPLICATE_PROJECT_NAME;
@@ -33,6 +34,7 @@ import static 
org.apache.kylin.common.exception.ServerErrorCode.EMPTY_EMAIL;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.EMPTY_PARAMETER;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.FILE_TYPE_MISMATCH;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.INVALID_JDBC_SOURCE_CONFIG;
+import static 
org.apache.kylin.common.exception.ServerErrorCode.INVALID_KERBEROS_FILE;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.INVALID_PARAMETER;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.PERMISSION_DENIED;
 import static 
org.apache.kylin.common.exception.ServerErrorCode.PROJECT_DROP_FAILED;
@@ -172,6 +174,8 @@ public class ProjectService extends BasicService {
     private static final String SNAPSHOT_AUTO_REFRESH_TIME_MODES = "DAY, 
HOURS, MINUTE";
     private static final String KYLIN_QUERY_PUSHDOWN_RUNNER_CLASS_NAME = 
"kylin.query.pushdown.runner-class-name";
     private static final String DEFAULT_VAL = "default";
+    private static final Pattern INVALID_FILENAME_CHARS = 
Pattern.compile("[\\\\/:*?\"'<>|\\u0000-\\u001F\\s]|^[.]+$|[.]$");
+
     @Autowired
     UserService userService;
     @Autowired
@@ -1242,11 +1246,26 @@ public class ProjectService extends BasicService {
         return kFile;
     }
 
-    public File generateTempKeytab(String principal, MultipartFile keytabFile) 
throws IOException {
-        Message msg = MsgPicker.getMsg();
+    public static void checkPrincipal(String principal, Message msg) {
         if (null == principal || principal.isEmpty()) {
             throw new KylinException(EMPTY_PARAMETER, msg.getPrincipalEmpty());
         }
+
+        // principal length
+        if (principal.length() > MAX_FILENAME_LENGTH - 
KerberosLoginManager.TMP_KEYTAB_SUFFIX.length()) {
+            throw new KylinException(INVALID_KERBEROS_FILE, 
msg.getKerberosInfoError());
+        }
+
+        // invalid characters
+        if (INVALID_FILENAME_CHARS.matcher(principal).find()) {
+            throw new KylinException(INVALID_KERBEROS_FILE, 
msg.getKerberosInfoError());
+        }
+    }
+
+    public File generateTempKeytab(String principal, MultipartFile keytabFile) 
throws IOException {
+        Message msg = MsgPicker.getMsg();
+        checkPrincipal(principal, msg);
+
         val originalFilename = keytabFile.getOriginalFilename();
         if (originalFilename == null || !originalFilename.endsWith(".keytab")) 
{
             throw new KylinException(FILE_TYPE_MISMATCH, 
msg.getKeytabFileTypeMismatch());
diff --git 
a/src/common-service/src/test/java/org/apache/kylin/rest/service/FileServiceTest.java
 
b/src/common-service/src/test/java/org/apache/kylin/rest/service/FileServiceTest.java
index a4da407957..e2332ff5c0 100644
--- 
a/src/common-service/src/test/java/org/apache/kylin/rest/service/FileServiceTest.java
+++ 
b/src/common-service/src/test/java/org/apache/kylin/rest/service/FileServiceTest.java
@@ -19,10 +19,15 @@
 package org.apache.kylin.rest.service;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.kylin.common.constant.Constants.BACKSLASH;
 import static org.apache.kylin.common.constant.Constants.METADATA_FILE;
+import static org.apache.kylin.common.constant.Constants.SYSTEM_TMP_DIR;
+import static org.apache.kylin.rest.service.FileService.METADATA_TMP_PREFIX;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -34,6 +39,8 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Locale;
 import java.util.Objects;
 
 import org.apache.hadoop.fs.Path;
@@ -63,14 +70,14 @@ class FileServiceTest {
     @Test
     void getMetadataBackupFromTmpPath() throws IOException {
         InputStream is = null;
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), 
METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
         try {
             is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 0L);
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertEquals("Metadata backup temp file is not a file: " + 
tmpFilePath, e.getMessage());
         } finally {
             close(is);
@@ -88,7 +95,7 @@ class FileServiceTest {
             is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 0L);
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertEquals("Metadata backup temp file length does not right: " + 
tmpFilePath + ", length :" + 3,
                     e.getMessage());
         } finally {
@@ -115,7 +122,7 @@ class FileServiceTest {
             result = fileService.saveMetadataBackupInTmpPath(path.toString());
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertEquals("Metadata backup file is not a file: " + path, 
e.getMessage());
             assertNull(result);
         }
@@ -141,7 +148,7 @@ class FileServiceTest {
             fileService.saveMetadataBackupInTmpPath(path.toString());
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertTrue(e.getMessage().endsWith("HDFS backup file:" + path + ", 
len: " + 3));
         }
     }
@@ -149,7 +156,7 @@ class FileServiceTest {
     @Test
     void saveMetadataBackupTmpFromRequest() throws IOException {
         InputStream is = null;
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), 
METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
 
@@ -161,7 +168,7 @@ class FileServiceTest {
             fileService.saveMetadataBackupTmpFromRequest(0L, is);
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertTrue(e.getMessage().startsWith("Metadata backup temp file 
length does not right: "));
             assertTrue(e.getMessage().endsWith(" length :" + 3));
         } finally {
@@ -184,7 +191,7 @@ class FileServiceTest {
         val fileSystem = HadoopUtil.getWorkingFileSystem();
         val path = new 
Path(HadoopUtil.getBackupFolder(KylinConfig.getInstanceFromEnv()),
                 new Path(RandomUtil.randomUUIDStr(), METADATA_FILE));
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
         try (val os = new FileOutputStream(tmpFile)) {
@@ -195,7 +202,7 @@ class FileServiceTest {
             fileService.saveMetadataBackupInHDFS(path.toString(), tmpFilePath, 
0L);
             fail();
         } catch (IOException e) {
-            assertTrue(e instanceof FileNotFoundException);
+            assertInstanceOf(FileNotFoundException.class, e);
             assertEquals("Metadata backup temp file length does not right.\n 
Tmp file: " + tmpFilePath + " length: " + 0
                     + "\n DFS file: " + path + " length: " + 3, 
e.getMessage());
         }
@@ -207,7 +214,7 @@ class FileServiceTest {
 
     @Test
     void deleteTmpDir() throws IOException {
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
         try (val os = new FileOutputStream(tmpFile)) {
@@ -219,7 +226,7 @@ class FileServiceTest {
 
     @Test
     void downloadMetadataBackTmpFile() throws IOException {
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
         try (val os = new FileOutputStream(tmpFile)) {
@@ -236,7 +243,7 @@ class FileServiceTest {
 
     @Test
     void saveBroadcastMetadataBackup() throws IOException {
-        val tmpDirectory = 
Files.createTempDirectory("MetadataBackupTmp-").toFile();
+        val tmpDirectory = 
Files.createTempDirectory(METADATA_TMP_PREFIX).toFile();
         val tmpFile = new File(tmpDirectory, METADATA_FILE);
         val tmpFilePath = tmpFile.getAbsolutePath();
         try (val os = new FileOutputStream(tmpFile)) {
@@ -257,4 +264,199 @@ class FileServiceTest {
         val fileStatus = fileSystem.getFileStatus(path);
         assertEquals(3, fileStatus.getLen());
     }
-}
+
+    @Test
+    void saveAndGet() throws IOException {
+        InputStream is = null;
+        val tmpDirectory = 
Files.createTempDirectory(Paths.get(SYSTEM_TMP_DIR), 
METADATA_TMP_PREFIX).toFile();
+        val tmpFile = new File(tmpDirectory, METADATA_FILE);
+        val tmpFilePath = tmpFile.getAbsolutePath();
+
+        try (val os = new FileOutputStream(tmpFile)) {
+            os.write("123".getBytes(UTF_8));
+            is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L);
+        }
+
+        try {
+            is = fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L);
+
+            val tempPath = fileService.saveMetadataBackupTmpFromRequest(3L, 
is);
+            val tempFile = new File(tempPath);
+            assertEquals(3, tempFile.length());
+
+            try (val iss = 
fileService.getMetadataBackupFromTmpPath(tmpFilePath, 3L)) {
+                val tempPath2 = 
fileService.saveMetadataBackupTmpFromRequest(3L, iss);
+                val tempFile2 = new File(tempPath2);
+                assertEquals(3, tempFile2.length());
+            }
+        } finally {
+            close(is);
+        }
+    }
+
+    @Test
+    void testGetSafeAbsolutePath() {
+        val tmpPath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX);
+        val tmpDir = tmpPath.toAbsolutePath().normalize().toString();
+        val validFilePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"validFile.txt");
+        {
+            String filePath = tmpPath.toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), 
result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"subdir", "file.txt").toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), 
result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String result = FileService.getSafeAbsolutePath(SYSTEM_TMP_DIR + 
BACKSLASH + METADATA_TMP_PREFIX);
+
+            assertEquals(tmpDir, result);
+        }
+        {
+            String filePath = SYSTEM_TMP_DIR + BACKSLASH + METADATA_TMP_PREFIX 
+ "/./validFile.txt";
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"subdir1", "..", "subdir2", "file.txt")
+                    .toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            assertEquals(Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"subdir2", "file.txt").toAbsolutePath()
+                    .normalize().toString(), result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"level1", "level2", "level3", "file.txt")
+                    .toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(Paths.get(filePath).toAbsolutePath().normalize().toString(), 
result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String filePath = Paths
+                    .get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", 
tmpPath.getFileName().toString(), "validFile.txt")
+                    .toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+        {
+            String filePath = Paths
+                    .get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, "..", "..", 
tmpPath.toString(), "validFile.txt")
+                    .toString();
+            String result = FileService.getSafeAbsolutePath(filePath);
+
+            
assertEquals(validFilePath.toAbsolutePath().normalize().toString(), result);
+            assertTrue(result.startsWith(tmpDir));
+        }
+    }
+
+    @Test
+    void testGetSafeAbsolutePathErrorWithDot() {
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, "..", "..", "etc", 
"passwd").toString();
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+            assertTrue(exception.getMessage().contains(filePath));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"..", "..", "..", "etc", "passwd")
+                    .toString();
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+            assertTrue(exception.getMessage().contains(filePath));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, "..", 
"malicious.txt").toString();
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, METADATA_TMP_PREFIX, 
"..", "malicious.txt").toString();
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path not kylin 
metadata tmp directory"));
+        }
+        {
+            String filePath = String.format(Locale.ROOT, 
"%s/%s/subdir/../../../../../../etc/passwd", SYSTEM_TMP_DIR,
+                    METADATA_TMP_PREFIX);
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+        {
+            String filePath = "../../../etc/passwd";
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+    }
+
+    @Test
+    void testGetSafeAbsolutePathError() {
+        {
+            String filePath = "/etc/passwd";
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+            assertTrue(exception.getMessage().contains(filePath));
+        }
+        {
+            String filePath = "";
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+        {
+            // Test with null path
+            assertThrows(Exception.class, () -> {
+                FileService.getSafeAbsolutePath(null);
+            });
+        }
+        {
+            // Test path with only dots (current directory reference)
+            String filePath = ".";
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+        {
+            // Test path with only double dots (parent directory reference)
+            String filePath = "..";
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path outside base 
directory"));
+        }
+        {
+            String filePath = Paths.get(SYSTEM_TMP_DIR, 
"malicious.txt").toString();
+            SecurityException exception = assertThrows(SecurityException.class,
+                    () -> FileService.getSafeAbsolutePath(filePath));
+
+            assertTrue(exception.getMessage().contains("Path not kylin 
metadata tmp directory"));
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/src/core-common/src/main/java/org/apache/kylin/common/constant/Constants.java 
b/src/core-common/src/main/java/org/apache/kylin/common/constant/Constants.java
index a379fe5ba8..0be71c624d 100644
--- 
a/src/core-common/src/main/java/org/apache/kylin/common/constant/Constants.java
+++ 
b/src/core-common/src/main/java/org/apache/kylin/common/constant/Constants.java
@@ -52,10 +52,13 @@ public class Constants {
     public static final String ASYNC = "ASYNC";
 
     public static final String METADATA_FILE = "metadata.zip";
+    public static final String SYSTEM_TMP_DIR = "/tmp";
 
     public static final String CORE_META_DIR = "core_meta";
 
     public static final String CACHE_MODEL_COMMAND = "CACHE FILES %s SELECT * 
FROM '%s' CACHEPROPERTIES (recursive=true)";
     public static final String CACHE_TABLE_COMMAND = "CACHE DATA %s SELECT %s 
FROM '%s' %s";
     public static final String FILTER_COMMAND = "AFTER %s AS OF '%s'";
+
+    public static final int MAX_FILENAME_LENGTH = 255;
 }
diff --git 
a/src/core-common/src/main/java/org/apache/kylin/common/util/AddressUtil.java 
b/src/core-common/src/main/java/org/apache/kylin/common/util/AddressUtil.java
index 24eda85407..50f0686507 100644
--- 
a/src/core-common/src/main/java/org/apache/kylin/common/util/AddressUtil.java
+++ 
b/src/core-common/src/main/java/org/apache/kylin/common/util/AddressUtil.java
@@ -18,10 +18,14 @@
 package org.apache.kylin.common.util;
 
 import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.net.UnknownHostException;
+import java.util.Locale;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.exception.KylinRuntimeException;
 import org.springframework.cloud.commons.util.InetUtils;
 import org.springframework.cloud.commons.util.InetUtilsProperties;
 
@@ -127,4 +131,18 @@ public class AddressUtil {
             throw new IllegalArgumentException("Url contains disallowed chars, 
host: " + host);
         }
     }
+
+    public static String extractIpAndPort(String urlString) {
+        try {
+            URL url = new URL(urlString);
+            String host = url.getHost();
+            int port = url.getPort();
+            if (StringUtils.isEmpty(host)) {
+                throw new KylinRuntimeException(String.format(Locale.ROOT, 
"Invalid or illegal URL: %s", urlString));
+            }
+            return host + ":" + port;
+        } catch (MalformedURLException e) {
+            throw new KylinRuntimeException(String.format(Locale.ROOT, 
"Invalid or illegal URL: %s", urlString), e);
+        }
+    }
 }
diff --git 
a/src/core-common/src/test/java/org/apache/kylin/common/util/AddressUtilTest.java
 
b/src/core-common/src/test/java/org/apache/kylin/common/util/AddressUtilTest.java
index fd0dd5f2e3..80aec061e5 100644
--- 
a/src/core-common/src/test/java/org/apache/kylin/common/util/AddressUtilTest.java
+++ 
b/src/core-common/src/test/java/org/apache/kylin/common/util/AddressUtilTest.java
@@ -20,6 +20,7 @@ package org.apache.kylin.common.util;
 import static org.apache.kylin.common.util.TestUtils.getTestConfig;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.exception.KylinRuntimeException;
 import org.apache.kylin.junit.annotation.MetadataInfo;
 import org.junit.Rule;
 import org.junit.jupiter.api.Assertions;
@@ -106,4 +107,56 @@ class AddressUtilTest {
         
Assertions.assertTrue(AddressUtil.isSameHost(hostInfoFetcher.getHostname()));
         Assertions.assertFalse(AddressUtil.isSameHost("unknown"));
     }
+
+    @Test
+    void testExtractIpAndPort() {
+        // Test valid HTTP URL with port
+        Assertions.assertEquals("example.com:8080", 
AddressUtil.extractIpAndPort("http://example.com:8080/path";));
+
+        // Test valid HTTPS URL with port
+        Assertions.assertEquals("example.com:443", 
AddressUtil.extractIpAndPort("https://example.com:443/path";));
+
+        // Test URL without explicit port (default port -1)
+        Assertions.assertEquals("example.com:-1", 
AddressUtil.extractIpAndPort("http://example.com/path";));
+
+        // Test IP address with port
+        Assertions.assertEquals("192.168.1.1:9090", 
AddressUtil.extractIpAndPort("http://192.168.1.1:9090";));
+
+        // Test localhost with port
+        Assertions.assertEquals("localhost:7070", 
AddressUtil.extractIpAndPort("http://localhost:7070";));
+
+        // Test URL with query parameters
+        Assertions.assertEquals("example.com:8080",
+                
AddressUtil.extractIpAndPort("http://example.com:8080/path?param=value";));
+
+        // Test URL with fragment
+        Assertions.assertEquals("example.com:8080",
+                
AddressUtil.extractIpAndPort("http://example.com:8080/path#section";));
+
+        Assertions.assertEquals("example .com:8080",
+                AddressUtil.extractIpAndPort("http://example 
.com:8080/path#section"));
+    }
+
+    @Test
+    void testExtractIpAndPortMalformedUrl() {
+        // Test malformed URL - missing protocol
+        Assertions.assertThrows(KylinRuntimeException.class, () -> 
AddressUtil.extractIpAndPort("example.com:8080"));
+
+        // Test malformed URL - empty string
+        Assertions.assertThrows(KylinRuntimeException.class, () -> 
AddressUtil.extractIpAndPort(""));
+
+        // Test malformed URL - null (will throw NPE before reaching method)
+        Assertions.assertThrows(Exception.class, () -> 
AddressUtil.extractIpAndPort(null));
+
+        // Test malformed URL - invalid protocol format
+        Assertions.assertThrows(KylinRuntimeException.class,
+                () -> AddressUtil.extractIpAndPort("http:example.com:8080"));
+
+        // Test malformed URL - incomplete protocol
+        Assertions.assertThrows(KylinRuntimeException.class,
+                () -> AddressUtil.extractIpAndPort("http:/example.com:8080"));
+
+        // Test malformed URL - unclosed bracket in host
+        Assertions.assertThrows(KylinRuntimeException.class, () -> 
AddressUtil.extractIpAndPort("http://[invalid";));
+    }
 }
diff --git 
a/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
 
b/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
index d75d29bf3f..db6c960bdf 100644
--- 
a/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
+++ 
b/src/core-metadata/src/main/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManager.java
@@ -21,6 +21,7 @@ package org.apache.kylin.metadata.resourcegroup;
 import java.util.List;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.persistence.MetadataType;
 import org.apache.kylin.common.persistence.ResourceStore;
@@ -104,7 +105,7 @@ public class ResourceGroupManager {
         updater.modify(copy);
         return updateResourceGroup(copy);
     }
-    
+
     public List<String> getInstancesForProject(String project) {
         ResourceGroup resourceGroup = getResourceGroup();
         List<String> ids = 
resourceGroup.getResourceGroupMappingInfoList().stream()
@@ -149,4 +150,15 @@ public class ResourceGroupManager {
         crud.save(resourceGroup);
         return resourceGroup;
     }
+
+    public boolean checkServer(String host) {
+        if (StringUtils.isBlank(host)) {
+            return true;
+        }
+        if (isResourceGroupEnabled()) {
+            return 
getResourceGroup().getKylinInstances().stream().map(KylinInstance::getInstance)
+                    .noneMatch(server -> StringUtils.equals(server, host));
+        }
+        return true;
+    }
 }
diff --git 
a/src/core-metadata/src/test/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManagerTest.java
 
b/src/core-metadata/src/test/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManagerTest.java
index c48325f1d6..77651e9f95 100644
--- 
a/src/core-metadata/src/test/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManagerTest.java
+++ 
b/src/core-metadata/src/test/java/org/apache/kylin/metadata/resourcegroup/ResourceGroupManagerTest.java
@@ -34,6 +34,7 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
 
 import lombok.val;
 
@@ -118,7 +119,7 @@ public class ResourceGroupManagerTest extends 
NLocalFileMetadataTestCase {
         Assert.assertEquals(project, 
rgManager.listProjectWithPermission().get(0));
     }
 
-    private void mockResourceGroup(String host, String project) {
+    public static void mockResourceGroup(String host, String project) {
         ResourceGroupManager manager = 
ResourceGroupManager.getInstance(getTestConfig());
         manager.updateResourceGroup(copyForWrite -> {
             copyForWrite.setResourceGroupEnabled(true);
@@ -136,4 +137,25 @@ public class ResourceGroupManagerTest extends 
NLocalFileMetadataTestCase {
             
copyForWrite.setResourceGroupMappingInfoList(Collections.singletonList(mappingInfo));
         });
     }
+
+    @Test
+    public void testCheckServer() {
+        String project = "default";
+        mockResourceGroup(AddressUtil.getLocalInstance(), project);
+        ResourceGroupManager manager = 
ResourceGroupManager.getInstance(getTestConfig());
+
+        // Test valid server
+        
Assertions.assertFalse(manager.checkServer(AddressUtil.getLocalInstance()));
+
+        // Test null host
+        Assertions.assertTrue(manager.checkServer(null));
+
+        // Test empty host
+        Assertions.assertTrue(manager.checkServer(""));
+        Assertions.assertTrue(manager.checkServer(" "));
+
+        // Test host not found in servers
+        Assertions.assertTrue(manager.checkServer("192.168.1.1:8080"));
+    }
+
 }
diff --git 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
index f3540fb522..4745d6c63e 100644
--- 
a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
+++ 
b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ProjectServiceTest.java
@@ -21,6 +21,7 @@ package org.apache.kylin.rest.service;
 import static org.apache.kylin.common.constant.Constants.HIDDEN_VALUE;
 import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_DRIVER_KEY;
 import static 
org.apache.kylin.common.constant.Constants.KYLIN_SOURCE_JDBC_PASS_KEY;
+import static org.apache.kylin.common.constant.Constants.MAX_FILENAME_LENGTH;
 import static 
org.apache.kylin.metadata.model.MaintainModelType.MANUAL_MAINTAIN;
 
 import java.io.IOException;
@@ -36,6 +37,8 @@ import java.util.concurrent.FutureTask;
 
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.common.msg.Message;
+import org.apache.kylin.common.msg.MsgPicker;
 import org.apache.kylin.common.persistence.transaction.UnitOfWork;
 import org.apache.kylin.common.util.EncryptUtil;
 import org.apache.kylin.common.util.JsonUtil;
@@ -77,6 +80,7 @@ import org.apache.kylin.rest.response.ProjectConfigResponse;
 import org.apache.kylin.rest.response.StorageVolumeInfoResponse;
 import org.apache.kylin.rest.response.UserProjectPermissionResponse;
 import org.apache.kylin.rest.security.AclPermissionEnum;
+import org.apache.kylin.rest.security.KerberosLoginManager;
 import org.apache.kylin.rest.service.task.QueryHistoryMetaUpdateScheduler;
 import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.rest.util.AclUtil;
@@ -99,6 +103,7 @@ import 
org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.web.multipart.MultipartFile;
 
+import alluxio.shaded.client.org.apache.commons.lang3.StringUtils;
 import lombok.val;
 import lombok.var;
 import lombok.extern.slf4j.Slf4j;
@@ -1013,6 +1018,134 @@ public class ProjectServiceTest extends 
NLocalFileMetadataTestCase {
         Assert.assertThrows(KylinException.class, () -> 
projectService.generateTempKeytab("test", multipartFile));
     }
 
+    @Test
+    public void testCheckPrincipal() {
+        Message msg = MsgPicker.getMsg();
+        testCheckPrincipalNormal(msg);
+        testCheckPrincipalWithEmpty(msg);
+        testCheckPrincipalWithFileNameLengthLimitation(msg);
+        testCheckPrincipalWithIllegalPunctuationSymbols(msg);
+        testCheckPrincipalWithControlCharacters(msg);
+        testCheckPrincipalWithWhitespace(msg);
+        testCheckPrincipalWithDot(msg);
+        testCheckPrincipalWithVariousEffectiveCharacterCombinations(msg);
+    }
+
+    private void testCheckPrincipalNormal(Message msg) {
+        // Test normal condition
+        ProjectService.checkPrincipal("validuser", msg);
+        ProjectService.checkPrincipal("user123", msg);
+        ProjectService.checkPrincipal("user_name", msg);
+        ProjectService.checkPrincipal("user-name", msg);
+    }
+
+    private void testCheckPrincipalWithEmpty(Message msg) {
+        // Test null and empty string
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal(null, msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("", msg));
+    }
+
+    private void testCheckPrincipalWithFileNameLengthLimitation(Message msg) {
+        // Test length exceeds limit(MAX_FILENAME_LENGTH - 
TMP_KEYTAB_SUFFIX.length())
+        int maxLength = MAX_FILENAME_LENGTH - 
KerberosLoginManager.TMP_KEYTAB_SUFFIX.length();
+        String longPrincipal = StringUtils.repeat("a", maxLength + 1);
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal(longPrincipal, msg));
+        // Test boundary length (just reached limit)
+        String boundaryPrincipal = StringUtils.repeat("a", maxLength);
+        ProjectService.checkPrincipal(boundaryPrincipal, msg);
+    }
+
+    private void testCheckPrincipalWithIllegalPunctuationSymbols(Message msg) {
+        // Test backslash and forward slash
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\\name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user/name", msg));
+
+        // Test colon
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user:name", msg));
+
+        // Test asterisks and question marks
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user*name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user?name", msg));
+
+        // Test double quotes and single quotes
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\"name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user'name", msg));
+
+        // Test angle brackets and vertical line
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user<name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user>name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user|name", msg));
+    }
+
+    private void testCheckPrincipalWithControlCharacters(Message msg) {
+        // Test control characters(\u0000-\u001F)
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\u0000name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\u0001name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\u000Fname", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\u001Fname", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\tname", msg)); // \u0009
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\nname", msg)); //
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user\rname", msg)); //
+    }
+
+    private void testCheckPrincipalWithWhitespace(Message msg) {
+        // Test whitespace characters
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user name", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal(" username", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("username ", msg));
+    }
+
+    private void testCheckPrincipalWithDot(Message msg) {
+        // Test only the case with periods.(^[.]+$)
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal(".", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("..", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("...", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("....", msg));
+
+        // Test cases ending with a period([.]$)
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("username.", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("user.name.", msg));
+        Assert.assertThrows(KylinException.class, () -> 
ProjectService.checkPrincipal("a.", msg));
+
+        // Test valid cases with dots (not all dots, not at the end)
+        ProjectService.checkPrincipal("user.name", msg);
+        ProjectService.checkPrincipal("user.sub.domain", msg);
+        ProjectService.checkPrincipal(".validuser", msg); // 以点开头但不是全点
+        ProjectService.checkPrincipal("user.123", msg);
+    }
+
+    private void 
testCheckPrincipalWithVariousEffectiveCharacterCombinations(Message msg) {
+        // Test various effective character combinations
+        ProjectService.checkPrincipal("user123", msg);
+        ProjectService.checkPrincipal("user_name", msg);
+        ProjectService.checkPrincipal("user-name", msg);
+        ProjectService.checkPrincipal("USER", msg);
+        ProjectService.checkPrincipal("User", msg);
+        ProjectService.checkPrincipal("123user", msg);
+        ProjectService.checkPrincipal("user123name", msg);
+        ProjectService.checkPrincipal("user_123-name", msg);
+        ProjectService.checkPrincipal("a", msg);
+        ProjectService.checkPrincipal("A", msg);
+        ProjectService.checkPrincipal("1", msg);
+        ProjectService.checkPrincipal("_", msg);
+        ProjectService.checkPrincipal("-", msg);
+        ProjectService.checkPrincipal("u0", msg);
+        ProjectService.checkPrincipal("u0000", msg);
+        ProjectService.checkPrincipal("u001F", msg);
+        ProjectService.checkPrincipal("1F", msg);
+
+        // Unicode character (non-control character)
+        ProjectService.checkPrincipal("用户名", msg);
+        ProjectService.checkPrincipal("usuário", msg);
+
+        // Containing Dot but Valid
+        ProjectService.checkPrincipal("user.domain", msg);
+        ProjectService.checkPrincipal(".user", msg);
+        ProjectService.checkPrincipal("u.s.e.r", msg);
+        ProjectService.checkPrincipal("u.s..e.r", msg);
+        ProjectService.checkPrincipal("u.s...e.r", msg);
+    }
+
     @Test
     public void testCleanupGarbage() throws Exception {
         QueryHistoryMetaUpdateScheduler qhMetaUpdateScheduler = 
QueryHistoryMetaUpdateScheduler.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 07a9aa6c3d..0d28102df7 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
@@ -43,7 +43,6 @@ import org.apache.kylin.guava30.shaded.common.collect.Maps;
 import org.apache.kylin.helper.MetadataToolHelper;
 import org.apache.kylin.metadata.asynctask.MetadataRestoreTask;
 import org.apache.kylin.metadata.project.ProjectInstance;
-import org.apache.kylin.rest.cluster.ClusterManager;
 import org.apache.kylin.rest.request.DiagPackageRequest;
 import org.apache.kylin.rest.request.DiagProgressRequest;
 import org.apache.kylin.rest.request.MaintenanceModeRequest;
@@ -80,16 +79,13 @@ import io.swagger.annotations.ApiOperation;
 @Controller
 @RequestMapping(value = "/api/system", produces = { 
HTTP_VND_APACHE_KYLIN_JSON, HTTP_VND_APACHE_KYLIN_V4_PUBLIC_JSON })
 public class OpsController extends NBasicController {
-    
+
     private static String DEPRECATED_MAINTENANCE_MODE = "Maintenance mode has 
been deprecated.";
 
     @Autowired
     @Qualifier("systemService")
     private SystemService systemService;
 
-    @Autowired
-    private ClusterManager clusterManager;
-
     @Autowired
     private AclEvaluate aclEvaluate;
 
@@ -151,6 +147,7 @@ public class OpsController extends NBasicController {
                     diagPackageRequest.getJobId(), 
diagPackageRequest.getProject(), headers);
             return new EnvelopeResponse<>(CODE_SUCCESS, uuid, "");
         } else {
+            checkServer(AddressUtil.extractIpAndPort(host));
             String url = host + "/kylin/api/system/diag";
             return generateTaskForRemoteHost(request, url);
         }
@@ -170,6 +167,7 @@ public class OpsController extends NBasicController {
                     queryDiagPackageRequest.getProject(), headers);
             return new EnvelopeResponse<>(CODE_SUCCESS, uuid, "");
         } else {
+            checkServer(AddressUtil.extractIpAndPort(host));
             String url = host + "/kylin/api/system/diag/query";
             return generateTaskForRemoteHost(request, url);
         }
@@ -195,6 +193,7 @@ public class OpsController extends NBasicController {
         if (StringUtils.isEmpty(host) || 
KylinConfig.getInstanceFromEnv().getMicroServiceMode() != null) {
             return systemService.getExtractorStatus(id, project);
         } else {
+            checkServer(AddressUtil.extractIpAndPort(host));
             String url = host + "/kylin/api/system/diag/status?id=" + id;
             if (StringUtils.isNotEmpty(project)) {
                 url = url + "&project=" + project;
@@ -215,6 +214,7 @@ public class OpsController extends NBasicController {
             setDownloadResponse(systemService.getDiagPackagePath(id, project), 
MediaType.APPLICATION_OCTET_STREAM_VALUE,
                     response);
         } else {
+            checkServer(AddressUtil.extractIpAndPort(host));
             String url = host + "/kylin/api/system/diag?id=" + id;
             if (StringUtils.isNotEmpty(project)) {
                 url = url + "&project=" + project;
@@ -234,6 +234,7 @@ public class OpsController extends NBasicController {
             systemService.stopDiagTask(id);
             return new EnvelopeResponse<>(CODE_SUCCESS, "", "");
         } else {
+            checkServer(AddressUtil.extractIpAndPort(host));
             String url = host + "/kylin/api/system/diag?id=" + id;
             return generateTaskForRemoteHost(request, url);
         }
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 243d5e58ef..f6fc290964 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
@@ -25,9 +25,11 @@ import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.Random;
 
+import org.apache.kylin.common.util.AddressUtil;
 import org.apache.kylin.common.util.JsonUtil;
 import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
 import org.apache.kylin.junit.rule.TransactionExceptedException;
+import org.apache.kylin.rest.cluster.ClusterManager;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.request.DiagPackageRequest;
 import org.apache.kylin.rest.request.DiagProgressRequest;
@@ -56,6 +58,9 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
     @Mock
     private SystemService systemService;
 
+    @Mock
+    private ClusterManager clusterManager;
+
     @InjectMocks
     private OpsController opsController = Mockito.spy(new OpsController());
 
@@ -83,7 +88,7 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
         DiagPackageRequest request = new DiagPackageRequest();
         Mockito.doAnswer(x -> 
null).when(opsController).generateTaskForRemoteHost(Mockito.any(), 
Mockito.any());
         
mockMvc.perform(MockMvcRequestBuilders.post("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
-                
.accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)).param("host", 
"ip")
+                
.accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)).param("host", 
mockHost())
                 .content(JsonUtil.writeValueAsString(request)))
                 .andExpect(MockMvcResultMatchers.status().is5xxServerError());
 
@@ -152,7 +157,7 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
     public void testGetRemoteDumpDiagPackage() throws Exception {
         Mockito.doAnswer(x -> 
null).when(opsController).generateTaskForRemoteHost(Mockito.any(), 
Mockito.anyString());
         
mockMvc.perform(MockMvcRequestBuilders.get("/api/system/diag/status").contentType(MediaType.APPLICATION_JSON)
-                .param("id", "id").param("host", "ip").param("project", 
"project")
+                .param("id", "id").param("host", mockHost()).param("project", 
"project")
                 .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                 .andExpect(MockMvcResultMatchers.status().isOk());
         
Mockito.verify(opsController).getRemotePackageStatus(Mockito.anyString(), 
Mockito.anyString(),
@@ -163,7 +168,7 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
     public void testRemoteDownloadPackage() throws Exception {
         
Mockito.doNothing().when(opsController).downloadFromRemoteHost(Mockito.any(), 
Mockito.any(), Mockito.any());
         
mockMvc.perform(MockMvcRequestBuilders.get("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
-                .param("id", "id").param("host", "ip").param("project", 
"project")
+                .param("id", "id").param("host", mockHost()).param("project", 
"project")
                 .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                 .andExpect(MockMvcResultMatchers.status().isOk());
         
Mockito.verify(opsController).remoteDownloadPackage(Mockito.anyString(), 
Mockito.anyString(),
@@ -185,7 +190,8 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
     public void testRemoteStopPackage() throws Exception {
         Mockito.doAnswer(x -> 
null).when(opsController).generateTaskForRemoteHost(Mockito.any(), 
Mockito.anyString());
         
mockMvc.perform(MockMvcRequestBuilders.delete("/api/system/diag").contentType(MediaType.APPLICATION_JSON)
-                .param("host", "ip").param("id", 
"id").accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
+                .param("host", mockHost()).param("id", "id")
+                .accept(MediaType.parseMediaType(HTTP_VND_APACHE_KYLIN_JSON)))
                 .andExpect(MockMvcResultMatchers.status().isOk());
         Mockito.verify(opsController).remoteStopPackage(Mockito.anyString(), 
Mockito.anyString(), Mockito.any());
     }
@@ -199,4 +205,9 @@ public class OpsControllerTest extends 
NLocalFileMetadataTestCase {
                 
.content(JsonUtil.writeValueAsString(request))).andExpect(MockMvcResultMatchers.status().isOk());
         Mockito.verify(opsController).updateDiagProgress(Mockito.any());
     }
+
+    private String mockHost() {
+        return "http://"; + AddressUtil.getLocalInstance();
+    }
+
 }
diff --git 
a/src/ops-service/src/test/java/org/apache/kylin/rest/MockClusterManagerTest.java
 
b/src/ops-service/src/test/java/org/apache/kylin/rest/MockClusterManagerTest.java
new file mode 100644
index 0000000000..390015757c
--- /dev/null
+++ 
b/src/ops-service/src/test/java/org/apache/kylin/rest/MockClusterManagerTest.java
@@ -0,0 +1,25 @@
+package org.apache.kylin.rest;
+
+import org.apache.kylin.rest.cluster.ClusterManager;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class MockClusterManagerTest {
+    @Test
+    void testCheckServer() {
+        ClusterManager clusterManager = new MockClusterManager();
+
+        // Test valid server
+        Assertions.assertFalse(clusterManager.checkServer("127.0.0.1:7070"));
+
+        // Test null host
+        Assertions.assertTrue(clusterManager.checkServer(null));
+
+        // Test empty host
+        Assertions.assertTrue(clusterManager.checkServer(""));
+        Assertions.assertTrue(clusterManager.checkServer(" "));
+
+        // Test host not found in servers
+        Assertions.assertTrue(clusterManager.checkServer("192.168.1.1:8080"));
+    }
+}
diff --git 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
index 57b6b76b16..87ec31ac25 100644
--- 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
+++ 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
@@ -72,7 +72,6 @@ import 
org.apache.kylin.metadata.query.util.QueryHisTransformStandardUtil;
 import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
 import org.apache.kylin.metadata.querymeta.TableMetaWithType;
 import org.apache.kylin.query.plugin.profiler.AsyncProfiling;
-import org.apache.kylin.rest.cluster.ClusterManager;
 import org.apache.kylin.rest.exception.ForbiddenException;
 import org.apache.kylin.rest.exception.InternalErrorException;
 import org.apache.kylin.rest.model.Query;
@@ -145,9 +144,6 @@ public class NQueryController extends NBasicController {
     @Qualifier("queryHistoryService")
     private QueryHistoryService queryHistoryService;
 
-    @Autowired
-    private ClusterManager clusterManager;
-
     @Autowired
     private QueryCacheManager queryCacheManager;
 
diff --git 
a/src/server/src/main/java/org/apache/kylin/rest/ResourceGroupLoadBalancer.java 
b/src/server/src/main/java/org/apache/kylin/rest/ResourceGroupLoadBalancer.java
index 60b9814241..12712d88dd 100644
--- 
a/src/server/src/main/java/org/apache/kylin/rest/ResourceGroupLoadBalancer.java
+++ 
b/src/server/src/main/java/org/apache/kylin/rest/ResourceGroupLoadBalancer.java
@@ -73,7 +73,7 @@ public class ResourceGroupLoadBalancer implements 
ReactorServiceInstanceLoadBala
                 .collect(Collectors.toList());
         String project = 
ProjectInfoParser.parseProjectInfo(httpServletRequest).getFirst();
         if (rgManager.isResourceGroupEnabled() && 
!project.equals(UnitOfWork.GLOBAL_UNIT)) {
-            jobNodes.retainAll(rgManager.getInstancesForProject(project));
+            jobNodes = rgManager.getInstancesForProject(project);
         }
 
         if (jobNodes.isEmpty()) {
diff --git 
a/src/tool/src/main/java/org/apache/kylin/rest/cluster/ClusterManager.java 
b/src/tool/src/main/java/org/apache/kylin/rest/cluster/ClusterManager.java
index 5919b91caf..3d1c454316 100644
--- a/src/tool/src/main/java/org/apache/kylin/rest/cluster/ClusterManager.java
+++ b/src/tool/src/main/java/org/apache/kylin/rest/cluster/ClusterManager.java
@@ -20,6 +20,7 @@ package org.apache.kylin.rest.cluster;
 import java.util.List;
 import java.util.Locale;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.kylin.common.exception.KylinRuntimeException;
 import org.apache.kylin.rest.response.ServerInfoResponse;
 import org.apache.kylin.rest.util.SpringContext;
@@ -44,4 +45,11 @@ public interface ClusterManager {
     static ClusterManager getInstance() {
         return 
SpringContext.getApplicationContext().getBean(ClusterManager.class);
     }
+
+    default boolean checkServer(String host) {
+        if (StringUtils.isBlank(host)) {
+            return true;
+        }
+        return getServers().stream().noneMatch(server -> 
StringUtils.equals(server.getHost(), host));
+    }
 }

Reply via email to