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));
+ }
}