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

dsmiley pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new d2f8faf5705 Refactor: move isFileForbiddenInConfigSets  (#4193)
d2f8faf5705 is described below

commit d2f8faf5705a955f46d40af98630520ff732e10c
Author: David Smiley <[email protected]>
AuthorDate: Thu Mar 19 22:45:59 2026 -0400

    Refactor: move isFileForbiddenInConfigSets  (#4193)
    
    CLI configSet upload no longer blocks certain file types.  The user of the 
CLI tool must already have permissions to touch ZooKeeper.  Enforcement of such 
remains at Solr's ConfigSet HTTP endpoint.
    
    Co-authored-by: copilot-swe-agent[bot] 
<[email protected]>
    Co-authored-by: dsmiley <[email protected]>
---
 changelog/unreleased/move-configset-methods.yml    |  7 ++++
 .../org/apache/solr/cloud/ZkConfigSetService.java  | 26 +++++++++++--
 .../org/apache/solr/core/ConfigSetService.java     | 28 ++++++++++++++
 .../solr/core/FileSystemConfigSetService.java      |  5 +--
 .../org/apache/solr/core/backup/BackupManager.java |  5 +--
 .../solr/handler/configsets/UploadConfigSet.java   |  3 +-
 .../org/apache/solr/cloud/TestConfigSetsAPI.java   |  7 ++--
 .../solr/common/cloud/ZkMaintenanceUtils.java      | 43 ++--------------------
 .../solr/common/cloud/TestZkConfigSetService.java  |  4 +-
 9 files changed, 71 insertions(+), 57 deletions(-)

diff --git a/changelog/unreleased/move-configset-methods.yml 
b/changelog/unreleased/move-configset-methods.yml
new file mode 100644
index 00000000000..428ed169cf3
--- /dev/null
+++ b/changelog/unreleased/move-configset-methods.yml
@@ -0,0 +1,7 @@
+title: CLI configSet upload no longer blocks certain file types.  The user of 
the CLI tool must already have permissions to touch ZooKeeper.  Enforcement of 
such remains at Solr's ConfigSet HTTP endpoint.
+type: other
+authors:
+  - name: David Smiley
+links:
+  - name: PR#4193
+    url: https://github.com/apache/solr/pull/4193
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java 
b/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java
index 99b4fb2d675..a0b9880c4ca 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java
@@ -18,7 +18,11 @@ package org.apache.solr.cloud;
 
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -168,6 +172,22 @@ public class ZkConfigSetService extends ConfigSetService {
 
   @Override
   public void uploadConfig(String configName, Path dir) throws IOException {
+    Files.walkFileTree(
+        dir,
+        new SimpleFileVisitor<>() {
+          @Override
+          public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) {
+            String filename = file.getFileName().toString();
+            if (ConfigSetService.isFileForbiddenInConfigSets(filename)) {
+              throw new SolrException(
+                  SolrException.ErrorCode.BAD_REQUEST,
+                  "The file type provided for upload, '"
+                      + filename
+                      + "', is forbidden for use in uploading configsets.");
+            }
+            return FileVisitResult.CONTINUE;
+          }
+        });
     zkClient.uploadToZK(
         dir, CONFIGS_ZKNODE + "/" + configName, 
ConfigSetService.UPLOAD_FILENAME_EXCLUDE_PATTERN);
   }
@@ -178,7 +198,7 @@ public class ZkConfigSetService extends ConfigSetService {
       throws IOException {
     String filePath = CONFIGS_ZKNODE + "/" + configName + "/" + fileName;
     try {
-      if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(fileName)) {
+      if (ConfigSetService.isFileForbiddenInConfigSets(fileName)) {
         throw new SolrException(
             SolrException.ErrorCode.BAD_REQUEST,
             "The file type provided for upload, '"
@@ -324,11 +344,11 @@ public class ZkConfigSetService extends ConfigSetService {
 
   private void copyData(String fromZkFilePath, String toZkFilePath)
       throws KeeperException, InterruptedException {
-    if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(fromZkFilePath)) {
+    if (ConfigSetService.isFileForbiddenInConfigSets(fromZkFilePath)) {
       log.warn(
           "Skipping copy of file in ZK, as the source file is a forbidden 
type: {}",
           fromZkFilePath);
-    } else if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(toZkFilePath)) {
+    } else if (ConfigSetService.isFileForbiddenInConfigSets(toZkFilePath)) {
       log.warn(
           "Skipping download of file from ZK, as the target file is a 
forbidden type: {}",
           toZkFilePath);
diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java 
b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
index 40145779a0d..409809a7d98 100644
--- a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
+++ b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java
@@ -26,6 +26,7 @@ import java.nio.file.Path;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Pattern;
 import org.apache.solr.cloud.ZkConfigSetService;
 import org.apache.solr.cloud.ZkController;
@@ -33,6 +34,7 @@ import org.apache.solr.common.ConfigNode;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.IndexSchemaFactory;
@@ -46,6 +48,32 @@ public abstract class ConfigSetService {
   public static final String UPLOAD_FILENAME_EXCLUDE_REGEX = "^\\..*$";
   public static final Pattern UPLOAD_FILENAME_EXCLUDE_PATTERN =
       Pattern.compile(UPLOAD_FILENAME_EXCLUDE_REGEX);
+
+  public static final String FORBIDDEN_FILE_TYPES_PROP = 
"solr.configset.forbidden.file.types";
+  public static final Set<String> DEFAULT_FORBIDDEN_FILE_TYPES =
+      Set.of("class", "java", "jar", "tgz", "zip", "tar", "gz");
+
+  private static final Set<String> USE_FORBIDDEN_FILE_TYPES;
+
+  static {
+    String userForbiddenFileTypes = 
EnvUtils.getProperty(FORBIDDEN_FILE_TYPES_PROP);
+    USE_FORBIDDEN_FILE_TYPES =
+        StrUtils.isNullOrEmpty(userForbiddenFileTypes)
+            ? DEFAULT_FORBIDDEN_FILE_TYPES
+            : Set.of(userForbiddenFileTypes.split(","));
+  }
+
+  /**
+   * Determine if a file path is forbidden for use in configsets based on its 
file extension.
+   *
+   * @param filePath the file path or name to check
+   * @return true if the file extension is among the forbidden types
+   */
+  public static boolean isFileForbiddenInConfigSets(String filePath) {
+    int lastDot = filePath.lastIndexOf('.');
+    return lastDot >= 0 && 
USE_FORBIDDEN_FILE_TYPES.contains(filePath.substring(lastDot + 1));
+  }
+
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static ConfigSetService createConfigSetService(CoreContainer 
coreContainer) {
diff --git 
a/solr/core/src/java/org/apache/solr/core/FileSystemConfigSetService.java 
b/solr/core/src/java/org/apache/solr/core/FileSystemConfigSetService.java
index 811e6d91fee..496e9fa86a4 100644
--- a/solr/core/src/java/org/apache/solr/core/FileSystemConfigSetService.java
+++ b/solr/core/src/java/org/apache/solr/core/FileSystemConfigSetService.java
@@ -35,7 +35,6 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.util.FileTypeMagicUtil;
 import org.apache.solr.util.FileUtils;
@@ -155,7 +154,7 @@ public class FileSystemConfigSetService extends 
ConfigSetService {
   public void uploadFileToConfig(
       String configName, String fileName, byte[] data, boolean 
overwriteOnExists)
       throws IOException {
-    if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(fileName)) {
+    if (ConfigSetService.isFileForbiddenInConfigSets(fileName)) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
           "The file type provided for upload, '"
@@ -234,7 +233,7 @@ public class FileSystemConfigSetService extends 
ConfigSetService {
             @Override
             public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs)
                 throws IOException {
-              if 
(ZkMaintenanceUtils.isFileForbiddenInConfigSets(file.getFileName().toString())) 
{
+              if 
(ConfigSetService.isFileForbiddenInConfigSets(file.getFileName().toString())) {
                 log.warn(
                     "Not including uploading file to config, as it is a 
forbidden type: {}",
                     file.getFileName());
diff --git a/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java 
b/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
index 2525bc36ad8..f47df829f74 100644
--- a/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
+++ b/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
@@ -37,7 +37,6 @@ import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.ConfigSetService;
@@ -344,7 +343,7 @@ public class BackupManager {
       // checking for '/' is correct for a directory since 
ConfigSetService#getAllConfigFiles
       // always separates file paths with '/'
       if (!filePath.endsWith("/")) {
-        if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(filePath)) {
+        if (ConfigSetService.isFileForbiddenInConfigSets(filePath)) {
           log.warn(
               "Not including zookeeper file in backup, as it is a forbidden 
type: {}", filePath);
         } else {
@@ -385,7 +384,7 @@ public class BackupManager {
       switch (t) {
         case FILE:
           {
-            if (ZkMaintenanceUtils.isFileForbiddenInConfigSets(filePath)) {
+            if (ConfigSetService.isFileForbiddenInConfigSets(filePath)) {
               log.warn(
                   "Not including zookeeper file in restore, as it is a 
forbidden type: {}", file);
             } else {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java 
b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
index d87c38154f5..617d5c8af22 100644
--- a/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/UploadConfigSet.java
@@ -30,7 +30,6 @@ import java.util.zip.ZipInputStream;
 import org.apache.solr.client.api.endpoint.ConfigsetsApi;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.PermissionName;
@@ -129,7 +128,7 @@ public class UploadConfigSet extends ConfigSetAPIBase 
implements ConfigsetsApi.U
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
           "The file path provided for upload, '" + singleFilePath + "', is not 
valid.");
-    } else if 
(ZkMaintenanceUtils.isFileForbiddenInConfigSets(fixedSingleFilePath)
+    } else if 
(ConfigSetService.isFileForbiddenInConfigSets(fixedSingleFilePath)
         || FileTypeMagicUtil.isFileForbiddenInConfigset(data)) {
       throw new SolrException(
           SolrException.ErrorCode.BAD_REQUEST,
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java 
b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
index a09737174ae..5dc13d0ca3a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
@@ -84,7 +84,6 @@ import 
org.apache.solr.client.solrj.response.schema.SchemaResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.params.CollectionParams.CollectionAction;
 import org.apache.solr.common.params.ConfigSetParams;
 import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
@@ -583,7 +582,7 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
       assertEquals(
           400, uploadConfigSet(configsetName, configsetSuffix, null, true, 
false, v2, true, false));
 
-      for (String fileEnding : 
ZkMaintenanceUtils.DEFAULT_FORBIDDEN_FILE_TYPES) {
+      for (String fileEnding : ConfigSetService.DEFAULT_FORBIDDEN_FILE_TYPES) {
         String f = configPath + "/test." + fileEnding;
         assertFalse(
             "Expecting file " + f + " to not exist, because it has a forbidden 
file type",
@@ -804,7 +803,7 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
             .withTimeout(AbstractZkTestCase.TIMEOUT, TimeUnit.MILLISECONDS)
             .withConnTimeOut(45000, TimeUnit.MILLISECONDS)
             .build()) {
-      for (String fileType : ZkMaintenanceUtils.DEFAULT_FORBIDDEN_FILE_TYPES) {
+      for (String fileType : ConfigSetService.DEFAULT_FORBIDDEN_FILE_TYPES) {
         ignoreException("is forbidden for use in configSets");
         assertEquals(
             "Can't upload a configset file with a forbidden type: " + fileType,
@@ -1385,7 +1384,7 @@ public class TestConfigSetsAPI extends SolrCloudTestCase {
     try (ZipOutputStream zout = new ZipOutputStream(out)) {
       if (Files.isRegularFile(fileOrDirectory)) {
         // Create entries with given file, one for each forbidden endding
-        for (String fileType : 
ZkMaintenanceUtils.DEFAULT_FORBIDDEN_FILE_TYPES) {
+        for (String fileType : ConfigSetService.DEFAULT_FORBIDDEN_FILE_TYPES) {
           zout.putNextEntry(new ZipEntry("test." + fileType));
 
           try (InputStream in = Files.newInputStream(fileOrDirectory)) {
diff --git 
a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
 
b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
index 0d98a65da7c..6438e84bbfc 100644
--- 
a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
+++ 
b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
@@ -29,12 +29,9 @@ import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
-import java.util.Set;
 import java.util.function.Predicate;
 import java.util.regex.Pattern;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -345,13 +342,6 @@ public class ZkMaintenanceUtils {
                   filenameExclusions);
               return FileVisitResult.CONTINUE;
             }
-            if (isFileForbiddenInConfigSets(filename)) {
-              throw new SolrException(
-                  SolrException.ErrorCode.BAD_REQUEST,
-                  "The file type provided for upload, '"
-                      + filename
-                      + "', is forbidden for use in uploading configsets.");
-            }
             // TODO: Cannot check MAGIC header for file since FileTypeGuesser 
is in core
             String zkNode = createZkNodeName(zkPath, rootPath, file);
             try {
@@ -443,13 +433,9 @@ public class ZkMaintenanceUtils {
       if (children.size() == 0) {
         // If we didn't copy data down, then we also didn't create the file. 
But we still need a
         // marker on the local disk so create an empty file.
-        if (isFileForbiddenInConfigSets(zkPath)) {
-          log.warn("Skipping download of file from ZK, as it is a forbidden 
type: {}", zkPath);
-        } else {
-          // TODO: Cannot check MAGIC header for file since FileTypeGuesser is 
in core
-          if (copyDataDown(zkClient, zkPath, file) == 0) {
-            Files.createFile(file);
-          }
+        // TODO: Cannot check MAGIC header for file since FileTypeGuesser is 
in core
+        if (copyDataDown(zkClient, zkPath, file) == 0) {
+          Files.createFile(file);
         }
       } else {
         Files.createDirectories(file); // Make parent dir.
@@ -581,29 +567,6 @@ public class ZkMaintenanceUtils {
     return ret;
   }
 
-  public static final String FORBIDDEN_FILE_TYPES_PROP = 
"solr.configset.forbidden.file.types";
-  public static final Set<String> DEFAULT_FORBIDDEN_FILE_TYPES =
-      Set.of("class", "java", "jar", "tgz", "zip", "tar", "gz");
-  private static volatile Set<String> USE_FORBIDDEN_FILE_TYPES = null;
-
-  public static boolean isFileForbiddenInConfigSets(String filePath) {
-    // Try to set the forbidden file types just once, since it is set by 
SysProp/EnvVar
-    if (USE_FORBIDDEN_FILE_TYPES == null) {
-      synchronized (DEFAULT_FORBIDDEN_FILE_TYPES) {
-        if (USE_FORBIDDEN_FILE_TYPES == null) {
-          String userForbiddenFileTypes = 
EnvUtils.getProperty(FORBIDDEN_FILE_TYPES_PROP);
-          if (StrUtils.isNullOrEmpty(userForbiddenFileTypes)) {
-            USE_FORBIDDEN_FILE_TYPES = DEFAULT_FORBIDDEN_FILE_TYPES;
-          } else {
-            USE_FORBIDDEN_FILE_TYPES = 
Set.of(userForbiddenFileTypes.split(","));
-          }
-        }
-      }
-    }
-    int lastDot = filePath.lastIndexOf('.');
-    return lastDot >= 0 && 
USE_FORBIDDEN_FILE_TYPES.contains(filePath.substring(lastDot + 1));
-  }
-
   /**
    * Create a persistent znode with no data if it does not already exist
    *
diff --git 
a/solr/solrj-zookeeper/src/test/org/apache/solr/common/cloud/TestZkConfigSetService.java
 
b/solr/solrj-zookeeper/src/test/org/apache/solr/common/cloud/TestZkConfigSetService.java
index 9928d543210..d7ed83b4d61 100644
--- 
a/solr/solrj-zookeeper/src/test/org/apache/solr/common/cloud/TestZkConfigSetService.java
+++ 
b/solr/solrj-zookeeper/src/test/org/apache/solr/common/cloud/TestZkConfigSetService.java
@@ -131,7 +131,7 @@ public class TestZkConfigSetService extends SolrTestCaseJ4 {
       Files.write(tempConfig.resolve("file1"), overwritten);
       configSetService.uploadConfig("testconfig", tempConfig);
 
-      assertEquals(2, configSetService.listConfigs().size());
+      assertEquals(1, configSetService.listConfigs().size());
       Path download2 = createTempDir("download2");
       configSetService.downloadConfig("testconfig", download2);
       byte[] checkdata2 = Files.readAllBytes(download2.resolve("file1"));
@@ -139,7 +139,7 @@ public class TestZkConfigSetService extends SolrTestCaseJ4 {
 
       // uploading same files to a new name creates a new config
       configSetService.uploadConfig("config2", tempConfig);
-      assertEquals(3, configSetService.listConfigs().size());
+      assertEquals(2, configSetService.listConfigs().size());
 
       // Test copying a config works in both flavors
       configSetService.copyConfig("config2", "config2copy");

Reply via email to