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

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

commit d451fbf3aab7bc45e87651cbf4d524f41641d0ba
Author: Jan Høydahl <[email protected]>
AuthorDate: Mon Aug 3 11:24:53 2026 +0200

    SOLR-17697: Refactor AuthTool: extract AuthParams and parser-independent 
command methods (#4688)
    
    (cherry picked from commit f67f471340129e9ae4920e03998ecb383ff13b79)
---
 .../src/java/org/apache/solr/cli/AuthTool.java     | 406 +++++++++++----------
 1 file changed, 211 insertions(+), 195 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cli/AuthTool.java 
b/solr/core/src/java/org/apache/solr/cli/AuthTool.java
index df0133cfead..d4d7282b32b 100644
--- a/solr/core/src/java/org/apache/solr/cli/AuthTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/AuthTool.java
@@ -32,11 +32,13 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.lucene.util.Constants;
+import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.core.SolrCore;
@@ -103,6 +105,22 @@ public class AuthTool extends ToolBase {
               "This is where any authentication related configuration files, 
if any, would be placed.  Defaults to $SOLR_HOME.")
           .get();
 
+  /**
+   * Parameters for the auth sub-commands, independent of the command line 
parser.
+   *
+   * @param zkHost resolved ZooKeeper connection string, or null if {@code 
updateIncludeFileOnly} is
+   *     set or the connection string could not be resolved
+   */
+  record AuthParams(
+      boolean prompt,
+      String credentials,
+      String blockUnknown,
+      boolean updateIncludeFileOnly,
+      String solrIncludeFile,
+      String authConfDir,
+      boolean zkHostSpecified,
+      String zkHost) {}
+
   public AuthTool(ToolRuntime runtime) {
     super(runtime);
   }
@@ -147,201 +165,178 @@ public class AuthTool extends ToolBase {
         .addOptionGroup(getConnectionOptions());
   }
 
-  private void ensureArgumentIsValidBooleanIfPresent(CommandLine cli, Option 
option) {
-    if (cli.hasOption(option)) {
-      final String value = cli.getOptionValue(option);
-      if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) 
{
-        echo(
-            "Argument ["
-                + option.getLongOpt()
-                + "] must be either true or false, but was ["
-                + value
-                + "]");
-        runtime.exit(1);
-      }
+  private void ensureArgumentIsValidBooleanIfPresent(String optionName, String 
value) {
+    if (value != null && !"true".equalsIgnoreCase(value) && 
!"false".equalsIgnoreCase(value)) {
+      echo("Argument [" + optionName + "] must be either true or false, but 
was [" + value + "]");
+      runtime.exit(1);
     }
   }
 
-  private void handleBasicAuth(CommandLine cli) throws Exception {
-    String cmd = cli.getArgs()[0];
-    boolean prompt = Boolean.parseBoolean(cli.getOptionValue(PROMPT_OPTION, 
"false"));
-    boolean updateIncludeFileOnly =
-        Boolean.parseBoolean(cli.getOptionValue(UPDATE_INCLUDE_FILE_OPTION, 
"false"));
+  private void handleCommand(String cmd, AuthParams params) throws Exception {
     switch (cmd) {
-      case "enable":
-        {
-          if (!prompt && !cli.hasOption(CommonCLIOptions.CREDENTIALS_OPTION)) {
-            CLIO.out("Option --credentials or --prompt is required with 
enable.");
-            runtime.exit(1);
-          } else if (!prompt
-              && (cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION) == 
null
-                  || 
!cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION).contains(":"))) {
-            CLIO.out("Option --credentials is not in correct format.");
-            runtime.exit(1);
-          }
-
-          String zkHost = null;
-
-          if (!updateIncludeFileOnly) {
-            try {
-              zkHost = CLIUtils.getZkHost(cli);
-            } catch (Exception ex) {
-              if (cli.hasOption(CommonCLIOptions.ZK_HOST_OPTION)) {
-                CLIO.out(
-                    "Couldn't get ZooKeeper host. Please make sure that 
ZooKeeper is running and the correct zk-host has been passed in.");
-              } else {
-                CLIO.out(
-                    "Couldn't get ZooKeeper host. Please make sure Solr is 
running in cloud mode, or a zk-host has been passed in.");
-              }
-              runtime.exit(1);
-            }
-            if (zkHost == null) {
-              if (cli.hasOption(CommonCLIOptions.ZK_HOST_OPTION)) {
-                CLIO.out(
-                    "Couldn't get ZooKeeper host. Please make sure that 
ZooKeeper is running and the correct zk-host has been passed in.");
-              } else {
-                CLIO.out(
-                    "Couldn't get ZooKeeper host. Please make sure Solr is 
running in cloud mode, or a zk-host has been passed in.");
-              }
-              runtime.exit(1);
-            }
-
-            // check if security is already enabled or not
-            try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, 
zkHost)) {
-              checkSecurityJsonExists(zkClient);
-            }
-          }
-
-          String username, password;
-          if (cli.hasOption(CommonCLIOptions.CREDENTIALS_OPTION)) {
-            String credentials = 
cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION);
-            username = credentials.split(":")[0];
-            password = credentials.split(":")[1];
-          } else {
-            Console console = System.console();
-            // keep prompting until they've entered a non-empty username & 
password
-            do {
-              username = console.readLine("Enter username: ");
-            } while (username == null || username.trim().isEmpty());
-            username = username.trim();
-
-            do {
-              password = new String(console.readPassword("Enter password: "));
-            } while (password.isEmpty());
-          }
-
-          if (username.equals(password)
-              && !EnvUtils.getPropertyAsBool(
-                  Sha256AuthenticationProvider.ALLOW_USER_AS_PASSWORD_PROP, 
false)) {
-            CLIO.err(
-                "Error: username and password must not be identical."
-                    + " This credential would never authenticate.");
-            runtime.exit(1);
-          }
-
-          String resourceName = "security.json";
-          final URL resource = 
SolrCore.class.getClassLoader().getResource(resourceName);
-          if (null == resource) {
-            throw new IllegalArgumentException("invalid resource name: " + 
resourceName);
-          }
-
-          ObjectMapper mapper = new ObjectMapper();
-          JsonNode securityJson1 = mapper.readTree(resource.openStream());
-          // Only override blockUnknown if explicitly passed; otherwise let 
the template decide
-          if (cli.hasOption(BLOCK_UNKNOWN_OPTION)) {
-            boolean blockUnknown = 
Boolean.parseBoolean(cli.getOptionValue(BLOCK_UNKNOWN_OPTION));
-            ((ObjectNode) 
securityJson1.get("authentication")).put("blockUnknown", blockUnknown);
-          }
-          JsonNode credentialsNode = 
securityJson1.get("authentication").get("credentials");
-          ((ObjectNode) credentialsNode)
-              .put(username, 
Sha256AuthenticationProvider.getSaltedHashedValue(password));
-          JsonNode userRoleNode = 
securityJson1.get("authorization").get("user-role");
-          String[] predefinedRoles = {"superadmin", "admin", "search", 
"index"};
-          ArrayNode rolesNode = mapper.createArrayNode();
-          for (String role : predefinedRoles) {
-            rolesNode.add(role);
-          }
-          ((ObjectNode) userRoleNode).set(username, rolesNode);
-          String securityJson = securityJson1.toPrettyString();
-
-          if (!updateIncludeFileOnly) {
-            echoIfVerbose("Uploading following security.json: " + 
securityJson);
-            try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, 
zkHost)) {
-              zkClient.makePath(
-                  "/security.json", 
securityJson.getBytes(StandardCharsets.UTF_8), false);
-            }
-          }
-
-          String solrIncludeFilename = 
cli.getOptionValue(SOLR_INCLUDE_FILE_OPTION);
-          Path includeFile = Path.of(solrIncludeFilename);
-          if (Files.notExists(includeFile) || !Files.isWritable(includeFile)) {
-            CLIO.out(
-                "Solr include file " + solrIncludeFilename + " doesn't exist 
or is not writeable.");
-            printAuthEnablingInstructions(username, password);
-            runtime.exit(0);
-          }
-          String authConfDir = cli.getOptionValue(AUTH_CONF_DIR_OPTION);
-          Path basicAuthConfFile = Path.of(authConfDir, "basicAuth.conf");
-
-          if (!Files.isWritable(basicAuthConfFile.getParent())) {
-            CLIO.out("Cannot write to file: " + 
basicAuthConfFile.toAbsolutePath());
-            printAuthEnablingInstructions(username, password);
-            runtime.exit(0);
-          }
-
-          Files.writeString(
-              basicAuthConfFile,
-              "httpBasicAuthUser=" + username + "\nhttpBasicAuthPassword=" + 
password,
-              StandardCharsets.UTF_8);
-
-          // update the solr.in.sh file to contain the necessary 
authentication lines
-          updateIncludeFileEnableAuth(includeFile, basicAuthConfFile);
-          final String successMessage =
-              String.format(
-                  Locale.ROOT,
-                  "Successfully enabled basic auth with username [%s] assigned 
to all roles (superadmin, admin, index, search).",
-                  username);
-          echo(successMessage);
-          if (!updateIncludeFileOnly) {
-            Map<String, String> templateUsers = new LinkedHashMap<>();
-            templateUsers.put("admin", "admin, index, search");
-            templateUsers.put("index", "index, search");
-            templateUsers.put("search", "search");
-            templateUsers.remove(username);
-            CLIO.out(
-                "\nIMPORTANT: The following template users have been created 
with NO password set"
-                    + " and cannot log in until passwords are assigned:");
-            templateUsers.forEach((u, roles) -> CLIO.out("  - " + u + "  
(roles: " + roles + ")"));
-            CLIO.out(
-                "Set their passwords using the Admin UI Security page or the 
authentication API.");
-          }
-          return;
-        }
-      case "disable":
-        {
-          clearSecurityJson(cli, updateIncludeFileOnly);
-
-          String solrIncludeFilename = 
cli.getOptionValue(SOLR_INCLUDE_FILE_OPTION);
-          Path includeFile = Path.of(solrIncludeFilename);
-          if (Files.notExists(includeFile) || !Files.isWritable(includeFile)) {
-            CLIO.out(
-                "Solr include file " + solrIncludeFilename + " doesn't exist 
or is not writeable.");
-            CLIO.out(
-                "Security has been disabled. Please remove any SOLR_AUTH_TYPE 
or SOLR_AUTHENTICATION_OPTS configuration from solr.in.sh/solr.in.cmd.\n");
-            runtime.exit(0);
-          }
-
-          // update the solr.in.sh file to comment out the necessary 
authentication lines
-          updateIncludeFileDisableAuth(includeFile);
-          return;
-        }
-      default:
+      case "enable" -> enableBasicAuth(params);
+      case "disable" -> disableBasicAuth(params);
+      default -> {
         CLIO.out("Valid auth commands are: enable, disable.");
         runtime.exit(1);
+      }
+    }
+  }
+
+  private void enableBasicAuth(AuthParams params) throws Exception {
+    if (!params.prompt() && params.credentials() == null) {
+      CLIO.out("Option --credentials or --prompt is required with enable.");
+      runtime.exit(1);
+    } else if (!params.prompt() && !params.credentials().contains(":")) {
+      CLIO.out("Option --credentials is not in correct format.");
+      runtime.exit(1);
     }
 
-    CLIO.out("Options not understood.");
-    runtime.exit(1);
+    String zkHost = params.zkHost();
+
+    if (!params.updateIncludeFileOnly()) {
+      if (zkHost == null) {
+        printZkHostError(params.zkHostSpecified());
+        runtime.exit(1);
+      }
+
+      // check if security is already enabled or not
+      try (SolrZkClient zkClient = zkClient(zkHost)) {
+        checkSecurityJsonExists(zkClient);
+      }
+    }
+
+    String username, password;
+    if (params.credentials() != null) {
+      username = params.credentials().split(":")[0];
+      password = params.credentials().split(":")[1];
+    } else {
+      Console console = System.console();
+      // keep prompting until they've entered a non-empty username & password
+      do {
+        username = console.readLine("Enter username: ");
+      } while (username == null || username.trim().isEmpty());
+      username = username.trim();
+
+      do {
+        password = new String(console.readPassword("Enter password: "));
+      } while (password.isEmpty());
+    }
+
+    if (username.equals(password)
+        && !EnvUtils.getPropertyAsBool(
+            Sha256AuthenticationProvider.ALLOW_USER_AS_PASSWORD_PROP, false)) {
+      CLIO.err(
+          "Error: username and password must not be identical."
+              + " This credential would never authenticate.");
+      runtime.exit(1);
+    }
+
+    String resourceName = "security.json";
+    final URL resource = 
SolrCore.class.getClassLoader().getResource(resourceName);
+    if (null == resource) {
+      throw new IllegalArgumentException("invalid resource name: " + 
resourceName);
+    }
+
+    ObjectMapper mapper = new ObjectMapper();
+    JsonNode securityJson1 = mapper.readTree(resource.openStream());
+    // Only override blockUnknown if explicitly passed; otherwise let the 
template decide
+    if (params.blockUnknown() != null) {
+      boolean blockUnknown = Boolean.parseBoolean(params.blockUnknown());
+      ((ObjectNode) securityJson1.get("authentication")).put("blockUnknown", 
blockUnknown);
+    }
+    JsonNode credentialsNode = 
securityJson1.get("authentication").get("credentials");
+    ((ObjectNode) credentialsNode)
+        .put(username, 
Sha256AuthenticationProvider.getSaltedHashedValue(password));
+    JsonNode userRoleNode = 
securityJson1.get("authorization").get("user-role");
+    String[] predefinedRoles = {"superadmin", "admin", "search", "index"};
+    ArrayNode rolesNode = mapper.createArrayNode();
+    for (String role : predefinedRoles) {
+      rolesNode.add(role);
+    }
+    ((ObjectNode) userRoleNode).set(username, rolesNode);
+    String securityJson = securityJson1.toPrettyString();
+
+    if (!params.updateIncludeFileOnly()) {
+      echoIfVerbose("Uploading following security.json: " + securityJson);
+      try (SolrZkClient zkClient = zkClient(zkHost)) {
+        zkClient.makePath("/security.json", 
securityJson.getBytes(StandardCharsets.UTF_8), false);
+      }
+    }
+
+    Path includeFile = Path.of(params.solrIncludeFile());
+    if (Files.notExists(includeFile) || !Files.isWritable(includeFile)) {
+      CLIO.out(
+          "Solr include file " + params.solrIncludeFile() + " doesn't exist or 
is not writeable.");
+      printAuthEnablingInstructions(username, password);
+      runtime.exit(0);
+    }
+    Path basicAuthConfFile = Path.of(params.authConfDir(), "basicAuth.conf");
+
+    if (!Files.isWritable(basicAuthConfFile.getParent())) {
+      CLIO.out("Cannot write to file: " + basicAuthConfFile.toAbsolutePath());
+      printAuthEnablingInstructions(username, password);
+      runtime.exit(0);
+    }
+
+    Files.writeString(
+        basicAuthConfFile,
+        "httpBasicAuthUser=" + username + "\nhttpBasicAuthPassword=" + 
password,
+        StandardCharsets.UTF_8);
+
+    // update the solr.in.sh file to contain the necessary authentication lines
+    updateIncludeFileEnableAuth(includeFile, basicAuthConfFile);
+    final String successMessage =
+        String.format(
+            Locale.ROOT,
+            "Successfully enabled basic auth with username [%s] assigned to 
all roles (superadmin, admin, index, search).",
+            username);
+    echo(successMessage);
+    if (!params.updateIncludeFileOnly()) {
+      Map<String, String> templateUsers = new LinkedHashMap<>();
+      templateUsers.put("admin", "admin, index, search");
+      templateUsers.put("index", "index, search");
+      templateUsers.put("search", "search");
+      templateUsers.remove(username);
+      CLIO.out(
+          "\nIMPORTANT: The following template users have been created with NO 
password set"
+              + " and cannot log in until passwords are assigned:");
+      templateUsers.forEach((u, roles) -> CLIO.out("  - " + u + "  (roles: " + 
roles + ")"));
+      CLIO.out("Set their passwords using the Admin UI Security page or the 
authentication API.");
+    }
+  }
+
+  private void disableBasicAuth(AuthParams params) throws Exception {
+    clearSecurityJson(params);
+
+    Path includeFile = Path.of(params.solrIncludeFile());
+    if (Files.notExists(includeFile) || !Files.isWritable(includeFile)) {
+      CLIO.out(
+          "Solr include file " + params.solrIncludeFile() + " doesn't exist or 
is not writeable.");
+      CLIO.out(
+          "Security has been disabled. Please remove any SOLR_AUTH_TYPE or 
SOLR_AUTHENTICATION_OPTS configuration from solr.in.sh/solr.in.cmd.\n");
+      runtime.exit(0);
+    }
+
+    // update the solr.in.sh file to comment out the necessary authentication 
lines
+    updateIncludeFileDisableAuth(includeFile);
+  }
+
+  private void printZkHostError(boolean zkHostSpecified) {
+    if (zkHostSpecified) {
+      CLIO.out(
+          "Couldn't get ZooKeeper host. Please make sure that ZooKeeper is 
running and the correct zk-host has been passed in.");
+    } else {
+      CLIO.out(
+          "Couldn't get ZooKeeper host. Please make sure Solr is running in 
cloud mode, or a zk-host has been passed in.");
+    }
+  }
+
+  private static SolrZkClient zkClient(String zkHost) {
+    return new SolrZkClient.Builder()
+        .withUrl(zkHost)
+        .withTimeout(SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT, 
TimeUnit.MILLISECONDS)
+        .build();
   }
 
   private void checkSecurityJsonExists(SolrZkClient zkClient)
@@ -357,10 +352,9 @@ public class AuthTool extends ToolBase {
     }
   }
 
-  private void clearSecurityJson(CommandLine cli, boolean 
updateIncludeFileOnly) throws Exception {
-    String zkHost;
-    if (!updateIncludeFileOnly) {
-      zkHost = CLIUtils.getZkHost(cli);
+  private void clearSecurityJson(AuthParams params) throws Exception {
+    if (!params.updateIncludeFileOnly()) {
+      String zkHost = params.zkHost();
       if (zkHost == null) {
         runtime.print("ZK Host not found. Solr should be running in cloud 
mode.");
         runtime.exit(1);
@@ -368,7 +362,7 @@ public class AuthTool extends ToolBase {
 
       echoIfVerbose("Uploading following security.json: {}");
 
-      try (SolrZkClient zkClient = CLIUtils.getSolrZkClient(cli, zkHost)) {
+      try (SolrZkClient zkClient = zkClient(zkHost)) {
         zkClient.makePath("/security.json", 
"{}".getBytes(StandardCharsets.UTF_8), false);
       }
     }
@@ -470,13 +464,35 @@ public class AuthTool extends ToolBase {
 
   @Override
   public void runImpl(CommandLine cli) throws Exception {
-    ensureArgumentIsValidBooleanIfPresent(cli, BLOCK_UNKNOWN_OPTION);
-    ensureArgumentIsValidBooleanIfPresent(cli, UPDATE_INCLUDE_FILE_OPTION);
+    ensureArgumentIsValidBooleanIfPresent(
+        BLOCK_UNKNOWN_OPTION.getLongOpt(), 
cli.getOptionValue(BLOCK_UNKNOWN_OPTION));
+    ensureArgumentIsValidBooleanIfPresent(
+        UPDATE_INCLUDE_FILE_OPTION.getLongOpt(), 
cli.getOptionValue(UPDATE_INCLUDE_FILE_OPTION));
 
     String type = cli.getOptionValue(TYPE_OPTION, "basicAuth");
     // switch structure is here to support future auth options like oAuth
     if (type.equals("basicAuth")) {
-      handleBasicAuth(cli);
+      boolean updateIncludeFileOnly =
+          Boolean.parseBoolean(cli.getOptionValue(UPDATE_INCLUDE_FILE_OPTION, 
"false"));
+      String zkHost = null;
+      if (!updateIncludeFileOnly) {
+        try {
+          zkHost = CLIUtils.getZkHost(cli);
+        } catch (Exception e) {
+          echoIfVerbose("Could not resolve ZooKeeper host: " + e.getMessage());
+        }
+      }
+      AuthParams params =
+          new AuthParams(
+              Boolean.parseBoolean(cli.getOptionValue(PROMPT_OPTION, "false")),
+              cli.getOptionValue(CommonCLIOptions.CREDENTIALS_OPTION),
+              cli.getOptionValue(BLOCK_UNKNOWN_OPTION),
+              updateIncludeFileOnly,
+              cli.getOptionValue(SOLR_INCLUDE_FILE_OPTION),
+              cli.getOptionValue(AUTH_CONF_DIR_OPTION),
+              cli.hasOption(CommonCLIOptions.ZK_HOST_OPTION),
+              zkHost);
+      handleCommand(cli.getArgs()[0], params);
     } else {
       throw new IllegalStateException("Only type=basicAuth supported at the 
moment.");
     }

Reply via email to