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

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


The following commit(s) were added to refs/heads/branch_9x by this push:
     new a97b796  SOLR-11623 Every request handler - PermissionNameProvider 
(Take 2) (#427)
a97b796 is described below

commit a97b796463232402b2a5b37013e2c6ad9a570c8f
Author: Jan Høydahl <jan...@users.noreply.github.com>
AuthorDate: Fri Jan 14 13:11:51 2022 +0100

    SOLR-11623 Every request handler - PermissionNameProvider (Take 2) (#427)
    
    (cherry picked from commit 68a600e026254c63304a2c2f9e395e5e62876002)
---
 solr/CHANGES.txt                                   |  3 +
 .../handler/DocumentAnalysisRequestHandler.java    |  6 ++
 .../apache/solr/handler/DumpRequestHandler.java    | 25 +++++-
 .../solr/handler/FieldAnalysisRequestHandler.java  |  6 ++
 .../apache/solr/handler/MoreLikeThisHandler.java   |  8 +-
 .../solr/handler/NotFoundRequestHandler.java       |  6 ++
 .../apache/solr/handler/PingRequestHandler.java    | 18 +++++
 .../apache/solr/handler/ReplicationHandler.java    |  6 ++
 .../apache/solr/handler/RequestHandlerBase.java    | 10 +--
 .../solr/handler/admin/HealthCheckHandler.java     |  6 ++
 .../org/apache/solr/handler/admin/InfoHandler.java | 14 ++++
 .../apache/solr/handler/admin/LoggingHandler.java  |  9 +++
 .../solr/handler/admin/LukeRequestHandler.java     |  6 ++
 .../handler/admin/MetricsCollectorHandler.java     |  6 ++
 .../solr/handler/admin/PluginInfoHandler.java      |  6 ++
 .../handler/admin/PropertiesRequestHandler.java    |  6 ++
 .../handler/admin/SegmentsInfoRequestHandler.java  |  6 ++
 .../solr/handler/admin/SolrInfoMBeanHandler.java   |  6 ++
 .../solr/handler/admin/SystemInfoHandler.java      |  7 +-
 .../solr/handler/admin/ThreadDumpHandler.java      |  7 ++
 .../solr/handler/admin/ZookeeperInfoHandler.java   | 23 +++++-
 .../solr/handler/admin/ZookeeperReadAPI.java       | 89 ++++++++++++++++------
 .../solr/handler/admin/ZookeeperStatusHandler.java |  6 ++
 .../solr/handler/tagger/TaggerRequestHandler.java  |  6 ++
 .../solr/search/function/FileFloatSource.java      |  6 ++
 .../solr/security/PermissionNameProvider.java      |  7 +-
 .../org/apache/solr/security/PublicKeyHandler.java |  5 ++
 .../security/RuleBasedAuthorizationPluginBase.java |  1 +
 .../org/apache/solr/BasicFunctionalityTest.java    |  8 +-
 .../core/MockQuerySenderListenerReqHandler.java    |  6 ++
 .../test/org/apache/solr/core/SolrCoreTest.java    |  6 ++
 .../handler/ThrowErrorOnInitRequestHandler.java    |  6 ++
 .../solr/handler/admin/MetricsHandlerTest.java     |  6 ++
 .../src/test/org/apache/solr/pkg/TestPackages.java |  6 ++
 .../BaseTestRuleBasedAuthorizationPlugin.java      | 11 ++-
 .../src/major-changes-in-solr-9.adoc               |  2 +
 .../src/rule-based-authorization-plugin.adoc       |  3 +-
 solr/webapp/web/index.html                         |  7 ++
 solr/webapp/web/js/angular/app.js                  |  4 +
 solr/webapp/web/js/angular/controllers/security.js |  3 +-
 40 files changed, 332 insertions(+), 46 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 81173e4..c0a7fb2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -189,6 +189,9 @@ when told to. The admin UI now tells it to. (Nazerke 
Seidan, David Smiley)
 
 * SOLR-15278: Add V2 equivalent to allow deleting async collection list of 
statuses. (Eric Pugh)
 
+* SOLR-11623: Every request handler in Solr now implements 
PermissionNameProvider to explicitly decide on what security
+  permissions are required to access the handler (janhoy, Hrishikesh Gadre, 
David Smiley)
+
 * SOLR-15608: Remove deprecated methods, classes and constructors from solrj 
clients (janhoy)
 
 * SOLR-15705: A delete-by-id command is forwarded to all shards when using the 
CompositeId router with a router field
diff --git 
a/solr/core/src/java/org/apache/solr/handler/DocumentAnalysisRequestHandler.java
 
b/solr/core/src/java/org/apache/solr/handler/DocumentAnalysisRequestHandler.java
index d461d2f..74a00dd 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/DocumentAnalysisRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/DocumentAnalysisRequestHandler.java
@@ -46,6 +46,7 @@ import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.common.EmptyEntityResolver;
+import org.apache.solr.security.AuthorizationContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -345,4 +346,9 @@ public class DocumentAnalysisRequestHandler extends 
AnalysisRequestHandlerBase {
     }
     return stream;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
index 87dacef..1f2ee41 100644
--- a/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/DumpRequestHandler.java
@@ -18,6 +18,7 @@ package org.apache.solr.handler;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -28,14 +29,21 @@ import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.util.plugin.SolrCoreAware;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
 
-public class DumpRequestHandler extends RequestHandlerBase
+public class DumpRequestHandler extends RequestHandlerBase implements 
SolrCoreAware
 {
+  private SolrCore solrCore;
+  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   @Override
   @SuppressWarnings({"unchecked"})
@@ -127,4 +135,19 @@ public class DumpRequestHandler extends RequestHandlerBase
       if (nl!=null) subpaths = nl.getAll("subpath");
     }
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    if (solrCore != null && solrCore.getSolrConfig().isEnableRemoteStreams()) {
+      log.warn("Dump request handler requires config-read permission when 
remote streams are enabled");
+      return Name.CONFIG_READ_PERM;
+    } else {
+      return Name.ALL;
+    }
+  }
+
+  @Override
+  public void inform(SolrCore core) {
+    this.solrCore = core;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/FieldAnalysisRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/FieldAnalysisRequestHandler.java
index 8e1ce26..df48a0f 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/FieldAnalysisRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/FieldAnalysisRequestHandler.java
@@ -29,6 +29,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.commons.io.IOUtils;
+import org.apache.solr.security.AuthorizationContext;
 
 import java.io.Reader;
 import java.io.IOException;
@@ -232,4 +233,9 @@ public class FieldAnalysisRequestHandler extends 
AnalysisRequestHandlerBase {
 
     return analyzeResults;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java 
b/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java
index 217066b..82d6179 100644
--- a/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/MoreLikeThisHandler.java
@@ -66,6 +66,7 @@ import org.apache.solr.search.SolrQueryTimeoutImpl;
 import org.apache.solr.search.SolrReturnFields;
 import org.apache.solr.search.SortSpec;
 import org.apache.solr.search.SyntaxError;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.SolrPluginUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -274,7 +275,12 @@ public class MoreLikeThisHandler extends RequestHandlerBase
         SolrQueryTimeoutImpl.reset();
       }
   }
-  
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.READ_PERM;
+  }
+
   public static class InterestingTerm
   {
     public Term term;
diff --git 
a/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java
index 511edbe..1165083 100644
--- a/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/NotFoundRequestHandler.java
@@ -19,6 +19,7 @@ package org.apache.solr.handler;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 
 import static org.apache.solr.common.params.CommonParams.PATH;
 
@@ -35,4 +36,9 @@ public class NotFoundRequestHandler extends 
RequestHandlerBase{
   public String getDescription() {
     return "No Operation";
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
index c3a2021..d8cb84e 100644
--- a/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/PingRequestHandler.java
@@ -34,11 +34,15 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.plugin.SolrCoreAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.common.params.CommonParams.DISTRIB;
+import static org.apache.solr.common.params.CommonParams.ENABLE;
+import static org.apache.solr.common.params.CommonParams.DISABLE;
+import static org.apache.solr.common.params.CommonParams.ACTION;
 
 /**
  * Ping Request Handler for reporting SolrCore health to a Load Balancer.
@@ -133,6 +137,20 @@ public class PingRequestHandler extends RequestHandlerBase 
implements SolrCoreAw
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static final String HEALTHCHECK_FILE_PARAM = "healthcheckFile";
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    String action = request.getParams().get(ACTION, 
"").strip().toLowerCase(Locale.ROOT);
+    // Modifying the health check file requires more permission than just 
doing a ping
+    switch (action) {
+      case ENABLE:
+      case DISABLE:
+        return Name.CONFIG_EDIT_PERM;
+      default:
+        return Name.HEALTH_PERM;
+    }
+  }
+
   protected enum ACTIONS {STATUS, ENABLE, DISABLE, PING};
   
   private String healthFileName = null;
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java 
b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index 71fe0f4..f47681c 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -96,6 +96,7 @@ import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.SolrIndexWriter;
 import org.apache.solr.update.VersionInfo;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
@@ -133,6 +134,11 @@ public class ReplicationHandler extends RequestHandlerBase 
implements SolrCoreAw
   
   private volatile boolean closed = false;
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.READ_PERM;
+  }
+
   private static final class CommitVersionInfo {
     public final long version;
     public final long generation;
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java 
b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index d761c55..8156c19 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -43,6 +43,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SyntaxError;
+import org.apache.solr.security.PermissionNameProvider;
 import org.apache.solr.util.SolrPluginUtils;
 import org.apache.solr.util.TestInjection;
 import org.slf4j.Logger;
@@ -51,9 +52,10 @@ import org.slf4j.LoggerFactory;
 import static org.apache.solr.core.RequestParams.USEPARAM;
 
 /**
- *
+ * Base class for all request handlers.
  */
-public abstract class RequestHandlerBase implements SolrRequestHandler, 
SolrInfoBean, NestedRequestHandler, ApiSupport {
+public abstract class RequestHandlerBase implements
+    SolrRequestHandler, SolrInfoBean, NestedRequestHandler, ApiSupport, 
PermissionNameProvider {
 
   protected NamedList<?> initArgs = null;
   protected SolrParams defaults;
@@ -332,6 +334,4 @@ public abstract class RequestHandlerBase implements 
SolrRequestHandler, SolrInfo
   public Collection<Api> getApis() {
     return ImmutableList.of(new ApiBag.ReqHandlerToApi(this, 
ApiBag.constructSpec(pluginInfo)));
   }
-}
-
-
+}
\ No newline at end of file
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
index 5c2831d..e657a8b 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/HealthCheckHandler.java
@@ -35,6 +35,7 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.admin.api.NodeHealthAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -281,4 +282,9 @@ public class HealthCheckHandler extends RequestHandlerBase {
   public Collection<Api> getApis() {
     return AnnotatedApi.getApis(new NodeHealthAPI(this));
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.HEALTH_PERM;
+  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
index 235a615..0f01a8c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
@@ -25,6 +25,7 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 
 import java.util.Collection;
 import java.util.Locale;
@@ -168,4 +169,17 @@ public class InfoHandler extends RequestHandlerBase  {
     list.addAll(handlers.get("health").getApis());
     return list.build();
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    // Delegate permission to the actual handler
+    String path = request.getResource();
+    String lastPath = path.substring(path.lastIndexOf("/") +1 );
+    RequestHandlerBase handler = 
handlers.get(lastPath.toLowerCase(Locale.ROOT));
+    if (handler != null) {
+      return handler.getPermissionName(request);
+    } else {
+      return null;
+    }
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
index c86a908..380db40 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/LoggingHandler.java
@@ -31,6 +31,7 @@ import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.LoggerInfo;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.plugin.SolrCoreAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -182,4 +183,12 @@ public class LoggingHandler extends RequestHandlerBase 
implements SolrCoreAware
     return Boolean.TRUE;
   }
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    if (request.getParams().get("set") != null) {
+      return Name.CONFIG_EDIT_PERM; // Change log level
+    } else {
+      return Name.CONFIG_READ_PERM;
+    }
+  }
 }
\ No newline at end of file
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
index 7f6f8cb..ae76eaa 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/LukeRequestHandler.java
@@ -79,6 +79,7 @@ import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.SolrIndexWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -105,6 +106,11 @@ public class LukeRequestHandler extends RequestHandlerBase
 
   static final int HIST_ARRAY_SIZE = 33;
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.READ_PERM;
+  }
+
   private static enum ShowStyle {
     ALL,
     DOC,
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
index a445c0d..8308820 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/MetricsCollectorHandler.java
@@ -40,6 +40,7 @@ import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.reporters.solr.SolrReporter;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.AddUpdateCommand;
 import org.apache.solr.update.CommitUpdateCommand;
 import org.apache.solr.update.DeleteUpdateCommand;
@@ -137,6 +138,11 @@ public class MetricsCollectorHandler extends 
RequestHandlerBase {
     return "Handler for collecting and aggregating SolrCloud metric reports.";
   }
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
+
   private static class MetricUpdateProcessor extends UpdateRequestProcessor {
     private final SolrMetricManager metricManager;
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/PluginInfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/PluginInfoHandler.java
index 7d9c3d7..d5ede4a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/PluginInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/PluginInfoHandler.java
@@ -25,6 +25,7 @@ import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -82,4 +83,9 @@ public class PluginInfoHandler extends RequestHandlerBase
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
index ce35a35..b2d0d4b 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/PropertiesRequestHandler.java
@@ -24,6 +24,7 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.admin.api.NodePropertiesAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.RedactionUtils;
 
 import java.io.IOException;
@@ -89,4 +90,9 @@ public class PropertiesRequestHandler extends 
RequestHandlerBase
   public Boolean registerV2() {
     return Boolean.TRUE;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.CONFIG_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
 
b/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
index a5da9f1..05d4d75 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/SegmentsInfoRequestHandler.java
@@ -32,6 +32,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.SolrIndexWriter;
 import org.apache.solr.util.RefCounted;
 import org.slf4j.Logger;
@@ -434,4 +435,9 @@ public class SegmentsInfoRequestHandler extends 
RequestHandlerBase {
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SolrInfoMBeanHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/SolrInfoMBeanHandler.java
index bbf3c30..80502c6 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SolrInfoMBeanHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SolrInfoMBeanHandler.java
@@ -28,6 +28,7 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.response.BinaryResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 
 import java.io.StringReader;
 import java.text.NumberFormat;
@@ -295,4 +296,9 @@ public class SolrInfoMBeanHandler extends 
RequestHandlerBase {
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
index 033b9d3..87c5d48 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SystemInfoHandler.java
@@ -29,6 +29,7 @@ import org.apache.solr.handler.admin.api.NodeSystemInfoAPI;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.AuthorizationPlugin;
 import org.apache.solr.security.RuleBasedAuthorizationPluginBase;
 import org.apache.solr.util.RTimer;
@@ -432,7 +433,11 @@ public class SystemInfoHandler extends RequestHandlerBase
   public Boolean registerV2() {
     return Boolean.TRUE;
   }
-  
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.CONFIG_READ_PERM;
+  }
 }
 
 
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
index 5a6f3e5..e58142f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ThreadDumpHandler.java
@@ -35,6 +35,8 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 
+import org.apache.solr.security.AuthorizationContext;
+
 import static org.apache.solr.common.params.CommonParams.ID;
 import static org.apache.solr.common.params.CommonParams.NAME;
 
@@ -186,4 +188,9 @@ public class ThreadDumpHandler extends RequestHandlerBase {
   public Boolean registerV2() {
     return Boolean.TRUE;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
index 1c54f10..41fd300 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
@@ -46,6 +46,7 @@ import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.OnReconnect;
 import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.MapSolrParams;
@@ -58,6 +59,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.JSONResponseWriter;
 import org.apache.solr.response.RawResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.SimplePostTool.BAOS;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.WatchedEvent;
@@ -80,6 +82,7 @@ import static org.apache.solr.common.params.CommonParams.WT;
  * @since solr 4.0
  */
 public final class ZookeeperInfoHandler extends RequestHandlerBase {
+  private static final String PARAM_DETAIL = "detail";
   private final CoreContainer cores;
 
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -103,6 +106,18 @@ public final class ZookeeperInfoHandler extends 
RequestHandlerBase {
     return Category.ADMIN;
   }
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    var params = request.getParams();
+    String path = params.get(PATH, "");
+    String detail = params.get(PARAM_DETAIL, "false");
+    if ("/security.json".equalsIgnoreCase(path) && 
"true".equalsIgnoreCase(detail)) {
+      return Name.SECURITY_READ_PERM;
+    } else {
+      return Name.ZK_READ_PERM;
+    }
+  }
+
   /**
    * Enumeration of ways to filter collections on the graph panel.
    */
@@ -184,15 +199,15 @@ public final class ZookeeperInfoHandler extends 
RequestHandlerBase {
       boolean hasDownedShard = false; // means one or more shards is down
       boolean replicaInRecovery = false;
 
-      Map<String, Object> shards = (Map<String, Object>) 
collectionState.get("shards");
+      Map<String, Object> shards = (Map<String, Object>) 
collectionState.get(DocCollection.SHARDS);
       for (Object o : shards.values()) {
         boolean hasActive = false;
         Map<String, Object> shard = (Map<String, Object>) o;
-        Map<String, Object> replicas = (Map<String, Object>) 
shard.get("replicas");
+        Map<String, Object> replicas = (Map<String, Object>) 
shard.get(Slice.REPLICAS);
         for (Object value : replicas.values()) {
           Map<String, Object> replicaState = (Map<String, Object>) value;
           Replica.State coreState = Replica.State.getState((String) 
replicaState.get(ZkStateReader.STATE_PROP));
-          String nodeName = (String) replicaState.get("node_name");
+          String nodeName = (String) 
replicaState.get(ZkStateReader.NODE_NAME_PROP);
 
           // state can lie to you if the node is offline, so need to reconcile 
with live_nodes too
           if (!liveNodes.contains(nodeName))
@@ -377,7 +392,7 @@ public final class ZookeeperInfoHandler extends 
RequestHandlerBase {
       throw new SolrException(ErrorCode.BAD_REQUEST, "Illegal parameter 
\"addr\"");
     }
 
-    String detailS = params.get("detail");
+    String detailS = params.get(PARAM_DETAIL);
     boolean detail = detailS != null && detailS.equals("true");
 
     String dumpS = params.get("dump");
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
index 0220ca5..3b3d51f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
@@ -45,6 +45,7 @@ import org.apache.zookeeper.data.Stat;
 import static org.apache.solr.common.params.CommonParams.OMIT_HEADER;
 import static org.apache.solr.common.params.CommonParams.WT;
 import static org.apache.solr.response.RawResponseWriter.CONTENT;
+import static 
org.apache.solr.security.PermissionNameProvider.Name.SECURITY_READ_PERM;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.ZK_READ_PERM;
 
 /**
@@ -58,41 +59,42 @@ import static 
org.apache.solr.security.PermissionNameProvider.Name.ZK_READ_PERM;
 
 public class ZookeeperReadAPI {
   private final CoreContainer coreContainer;
+  private final SolrParams rawWtParams;
 
   public ZookeeperReadAPI(CoreContainer coreContainer) {
     this.coreContainer = coreContainer;
+    Map<String, String> map = new HashMap<>(1);
+    map.put(WT, "raw");
+    map.put(OMIT_HEADER, "true");
+    rawWtParams = new MapSolrParams(map);
   }
+
+  /**
+   * Request contents of a znode, except security.json
+   */
   @EndPoint(path = "/cluster/zk/data/*",
       method = SolrRequest.METHOD.GET,
       permission = ZK_READ_PERM)
   public void readNode(SolrQueryRequest req, SolrQueryResponse rsp) {
     String path = req.getPathTemplateValues().get("*");
     if (path == null || path.isEmpty()) path = "/";
-    byte[] d = null;
-    try {
-      d = coreContainer.getZkController().getZkClient().getData(path, null, 
null, false);
-    } catch (KeeperException.NoNodeException e) {
-      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such 
node: " + path);
-    } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
"Unexpected error", e);
-    }
-    if (d == null || d.length == 0) {
-      rsp.add(path, null);
-      return;
-    }
-
-    Map<String, String> map = new HashMap<>(1);
-    map.put(WT, "raw");
-    map.put(OMIT_HEADER, "true");
-    req.setParams(SolrParams.wrapDefaults(new MapSolrParams(map), 
req.getParams()));
-
-    String mime = BinaryResponseParser.BINARY_CONTENT_TYPE;
+    readNodeAndAddToResponse(path, req, rsp);
+  }
 
-    if (d[0] == '{') mime = CommonParams.JSON_MIME;
-    if (d[0] == '<' || d[1] == '?') mime = XMLResponseParser.XML_CONTENT_TYPE;
-    rsp.add(CONTENT, new ContentStreamBase.ByteArrayStream(d, null, mime));
+  /**
+   * Request contents of the security.json node
+   */
+  @EndPoint(path = "/cluster/zk/data/security.json",
+      method = SolrRequest.METHOD.GET,
+      permission = SECURITY_READ_PERM)
+  public void readSecurityJsonNode(SolrQueryRequest req, SolrQueryResponse 
rsp) {
+    String path = "/security.json";
+    readNodeAndAddToResponse(path, req, rsp);
   }
 
+  /**
+   * List the children of a certain zookeeper znode
+   */
   @EndPoint(path = "/cluster/zk/ls/*",
       method = SolrRequest.METHOD.GET,
       permission = ZK_READ_PERM)
@@ -130,6 +132,49 @@ public class ZookeeperReadAPI {
     }
   }
 
+  /**
+   * Simple mime type guessing based on first character of the response
+   */
+  private String guessMime(byte firstByte) {
+    switch(firstByte) {
+      case '{':
+        return CommonParams.JSON_MIME;
+      case '<':
+      case '?':
+        return XMLResponseParser.XML_CONTENT_TYPE;
+      default:
+        return BinaryResponseParser.BINARY_CONTENT_TYPE;
+    }
+  }
+
+  /**
+   * Reads content of a znode and adds it to the response
+   */
+  private void readNodeAndAddToResponse(String zkPath, SolrQueryRequest req, 
SolrQueryResponse rsp) {
+    byte[] d = readPathFromZookeeper(zkPath);
+    if (d == null || d.length == 0) {
+      rsp.add(zkPath, null);
+      return;
+    }
+    req.setParams(SolrParams.wrapDefaults(rawWtParams, req.getParams()));
+    rsp.add(CONTENT, new ContentStreamBase.ByteArrayStream(d, null, 
guessMime(d[0])));
+  }
+
+  /**
+   * Reads a single node from zookeeper and return as byte array
+   */
+  private byte[] readPathFromZookeeper(String path) {
+    byte[] d;
+    try {
+      d = coreContainer.getZkController().getZkClient().getData(path, null, 
null, false);
+    } catch (KeeperException.NoNodeException e) {
+      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such 
node: " + path);
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
"Unexpected error", e);
+    }
+    return d;
+  }
+
   private void printStat(MapWriter.EntryWriter ew, Stat stat) throws 
IOException {
     ew.put("version", stat.getVersion());
     ew.put("aversion", stat.getAversion());
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
index 26f6a62..f0332b5 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperStatusHandler.java
@@ -40,6 +40,7 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -330,4 +331,9 @@ public class ZookeeperStatusHandler extends 
RequestHandlerBase {
     }
     return true;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.CONFIG_READ_PERM;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/tagger/TaggerRequestHandler.java 
b/solr/core/src/java/org/apache/solr/handler/tagger/TaggerRequestHandler.java
index 903f0ae..1bb0cc0 100644
--- 
a/solr/core/src/java/org/apache/solr/handler/tagger/TaggerRequestHandler.java
+++ 
b/solr/core/src/java/org/apache/solr/handler/tagger/TaggerRequestHandler.java
@@ -70,6 +70,7 @@ import org.apache.solr.search.QParser;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.search.SolrReturnFields;
 import org.apache.solr.search.SyntaxError;
+import org.apache.solr.security.AuthorizationContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -239,6 +240,11 @@ public class TaggerRequestHandler extends 
RequestHandlerBase {
     rsp.add("response", getDocList(rows, matchDocIdsBS));
   }
 
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.READ_PERM;
+  }
+
   private static class InputStringLazy implements Callable<String> {
     final Reader inputReader;
     String inputString;
diff --git 
a/solr/core/src/java/org/apache/solr/search/function/FileFloatSource.java 
b/solr/core/src/java/org/apache/solr/search/function/FileFloatSource.java
index 19ae494..f42fe54 100644
--- a/solr/core/src/java/org/apache/solr/search/function/FileFloatSource.java
+++ b/solr/core/src/java/org/apache/solr/search/function/FileFloatSource.java
@@ -47,6 +47,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.processor.UpdateRequestProcessor;
 import org.apache.solr.util.VersionedFile;
 import org.slf4j.Logger;
@@ -362,5 +363,10 @@ public class FileFloatSource extends ValueSource {
     public String getDescription() {
       return "Reload readerCache request handler";
     }
+
+    @Override
+    public Name getPermissionName(AuthorizationContext request) {
+      return Name.UPDATE_PERM;
+    }
   }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java 
b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
index bac5e8a..3f6137c 100644
--- a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
@@ -33,6 +33,7 @@ import static java.util.stream.Collectors.toMap;
  * at request time
  */
 public interface PermissionNameProvider {
+  // 'null' means the permission applies to system-level, while '*' means any 
collection
   enum Name {
     COLL_EDIT_PERM("collection-admin-edit", null),
     COLL_READ_PERM("collection-admin-read", null),
@@ -47,14 +48,14 @@ public interface PermissionNameProvider {
     SCHEMA_EDIT_PERM("schema-edit", "*"),
     SECURITY_EDIT_PERM("security-edit", null),
     SECURITY_READ_PERM("security-read", null),
-    METRICS_READ_PERM("metrics-read", null),
+    METRICS_READ_PERM("metrics-read", unmodifiableSet(new 
HashSet<>(asList("*", null)))),
+    HEALTH_PERM("health", unmodifiableSet(new HashSet<>(asList("*", null)))),
     FILESTORE_READ_PERM("filestore-read", null),
     FILESTORE_WRITE_PERM("filestore-write", null),
     PACKAGE_EDIT_PERM("package-edit", null),
     PACKAGE_READ_PERM("package-read", null),
 
-    ALL("all", unmodifiableSet(new HashSet<>(asList("*", null))))
-    ;
+    ALL("all", unmodifiableSet(new HashSet<>(asList("*", null))));
     final String name;
     final Set<String> collName;
 
diff --git a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java 
b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
index 208fe6c..a0d7d01 100644
--- a/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
+++ b/solr/core/src/java/org/apache/solr/security/PublicKeyHandler.java
@@ -77,4 +77,9 @@ public class PublicKeyHandler extends RequestHandlerBase {
   public Category getCategory() {
     return Category.ADMIN;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git 
a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
 
b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
index 46e80b8..cdcee91 100644
--- 
a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
+++ 
b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java
@@ -173,6 +173,7 @@ public abstract class RuleBasedAuthorizationPluginBase 
implements AuthorizationP
       log.trace("'ALL' perm applies to all requests; perm applies.");
       return true; //'ALL' applies to everything!
     } else if (! (context.getHandler() instanceof PermissionNameProvider)) {
+      // TODO: Is this code path needed anymore, now that all handlers 
implement the interface? context.getHandler returns Object and is not documented
       if (log.isTraceEnabled()) {
         log.trace("Request handler [{}] is not a PermissionNameProvider, perm 
doesnt apply", context.getHandler());
       }
diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java 
b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
index 9b9cdc5..55946f3 100644
--- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
+++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java
@@ -53,6 +53,7 @@ import org.apache.solr.schema.IndexSchemaFactory;
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.search.DocIterator;
 import org.apache.solr.search.DocList;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.BaseTestHarness;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -455,7 +456,12 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 
{
   public void testRequestHandlerBaseException() {
     final String tmp = "BOO! ignore_exception";
     SolrRequestHandler handler = new RequestHandlerBase() {
-        @Override
+      @Override
+      public Name getPermissionName(AuthorizationContext request) {
+        return Name.ALL;
+      }
+
+      @Override
         public String getDescription() { return tmp; }
         @Override
         public void handleRequestBody
diff --git 
a/solr/core/src/test/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
 
b/solr/core/src/test/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
index 1dcad73..4970f47 100644
--- 
a/solr/core/src/test/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
+++ 
b/solr/core/src/test/org/apache/solr/core/MockQuerySenderListenerReqHandler.java
@@ -21,6 +21,7 @@ import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.security.AuthorizationContext;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -58,4 +59,9 @@ public class MockQuerySenderListenerReqHandler extends 
RequestHandlerBase {
     String result = null;
     return result;
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java 
b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
index 6c70bd3..3ab5b9d 100644
--- a/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/core/SolrCoreTest.java
@@ -27,6 +27,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SolrIndexSearcher;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.update.SolrCoreState;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.util.RefCounted;
@@ -360,4 +361,9 @@ class EmptyRequestHandler extends RequestHandlerBase
   }
 
   @Override public String getDescription() { return null; }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git 
a/solr/core/src/test/org/apache/solr/handler/ThrowErrorOnInitRequestHandler.java
 
b/solr/core/src/test/org/apache/solr/handler/ThrowErrorOnInitRequestHandler.java
index 39c758e..0ac4bf2 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/ThrowErrorOnInitRequestHandler.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/ThrowErrorOnInitRequestHandler.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 
 /**
  * throws a {@link java.lang.Error} on init for testing purposes
@@ -48,4 +49,9 @@ public class ThrowErrorOnInitRequestHandler extends 
RequestHandlerBase
     }
     throw new Error("Doing my job, throwing a java.lang.Error");
   }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.ALL;
+  }
 }
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
index adcc305..a2e3a2a 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
@@ -37,6 +37,7 @@ import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -534,5 +535,10 @@ public class MetricsHandlerTest extends SolrTestCaseJ4 {
     public Boolean registerV2() {
       return Boolean.FALSE;
     }
+
+    @Override
+    public Name getPermissionName(AuthorizationContext request) {
+      return Name.METRICS_READ_PERM;
+    }
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java 
b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
index de68236..4c91daa 100644
--- a/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
+++ b/solr/core/src/test/org/apache/solr/pkg/TestPackages.java
@@ -52,6 +52,7 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.QParser;
 import org.apache.solr.search.QParserPlugin;
+import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.util.LogLevel;
 import org.apache.solr.util.SimplePostTool;
 import org.apache.solr.util.plugin.SolrCoreAware;
@@ -669,6 +670,11 @@ public class TestPackages extends SolrCloudTestCase {
     public String getDescription() {
       return "test";
     }
+
+    @Override
+    public Name getPermissionName(AuthorizationContext request) {
+      return Name.ALL;
+    }
   }
 
   public static class C2 extends QParserPlugin implements ResourceLoaderAware {
diff --git 
a/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
 
b/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
index 5874fad..57c2001 100644
--- 
a/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
+++ 
b/solr/core/src/test/org/apache/solr/security/BaseTestRuleBasedAuthorizationPlugin.java
@@ -45,7 +45,6 @@ import 
org.apache.solr.security.AuthorizationContext.CollectionRequest;
 import org.apache.solr.security.AuthorizationContext.RequestType;
 import org.apache.solr.util.LogLevel;
 import org.hamcrest.core.IsInstanceOf;
-import org.hamcrest.core.IsNot;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -213,14 +212,14 @@ public class BaseTestRuleBasedAuthorizationPlugin extends 
SolrTestCaseJ4 {
         "userPrincipal", "tim",
         "handler", new ReplicationHandler(),
         "collectionRequests", singletonList(new CollectionRequest("mycoll")) )
-        , FORBIDDEN);
+        , STATUS_OK); // Replication requires "READ" permission, which Tim has
 
     checkRules(Map.of("resource", ReplicationHandler.PATH,
         "httpMethod", "POST",
         "userPrincipal", "cio",
         "handler", new ReplicationHandler(),
         "collectionRequests", singletonList(new CollectionRequest("mycoll")) )
-        , STATUS_OK);
+        , FORBIDDEN); // User cio has role 'su' which does not have 'read' 
permission
 
     checkRules(Map.of("resource", "/admin/collections",
         "userPrincipal", "tim",
@@ -353,7 +352,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends 
SolrTestCaseJ4 {
         , STATUS_OK);
 
     handler = new PropertiesRequestHandler();
-    assertThat(handler, new IsNot<>(new 
IsInstanceOf(PermissionNameProvider.class)));
+    assertThat(handler, new IsInstanceOf(PermissionNameProvider.class));
     checkRules(Map.of("resource", "/admin/info/properties",
         "userPrincipal", "dev",
         "requestType", RequestType.UNKNOWN,
@@ -385,7 +384,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends 
SolrTestCaseJ4 {
         , STATUS_OK);
 
     handler = new PropertiesRequestHandler();
-    assertThat(handler, new IsNot<>(new 
IsInstanceOf(PermissionNameProvider.class)));
+    assertThat(handler, new IsInstanceOf(PermissionNameProvider.class));
     checkRules(Map.of("resource", "/admin/info/properties",
         "userPrincipal", "dev",
         "requestType", RequestType.UNKNOWN,
@@ -416,7 +415,7 @@ public class BaseTestRuleBasedAuthorizationPlugin extends 
SolrTestCaseJ4 {
         , FORBIDDEN);
 
     handler = new PropertiesRequestHandler();
-    assertThat(handler, new IsNot<>(new 
IsInstanceOf(PermissionNameProvider.class)));
+    assertThat(handler, new IsInstanceOf(PermissionNameProvider.class));
     checkRules(Map.of("resource", "/admin/info/properties",
         "userPrincipal", "dev",
         "requestType", RequestType.UNKNOWN,
diff --git a/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc 
b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
index 5ec3cd3..156ef92 100644
--- a/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
+++ b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
@@ -83,6 +83,8 @@ All the usages are replaced by 
BaseHttpSolrClient.RemoteSolrException and BaseHt
 * SOLR-15809: Get rid of blacklist/whitelist terminology. JWTAuthPlugin 
parameter `algWhitelist` is now `algAllowlist`. The old parameter will still
   work in 9.x. Environment variables `SOLR_IP_WHITELIST` and 
`SOLR_IP_BLACKLIST` are no longer supported, but replaced with 
`SOLR_IP_ALLOWLIST` and `SOLR_IP_DENYLIST`.
 
+* SOLR-11623: Every request handler in Solr now implements 
PermissionNameProvider. Any custom or 3rd party request handler must also do 
this
+
 
 == New Features & Enhancements
 
diff --git a/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc 
b/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
index 68634d6..2d4f796 100644
--- a/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
+++ b/solr/solr-ref-guide/src/rule-based-authorization-plugin.adoc
@@ -406,7 +406,8 @@ If edit permissions should only be applied to specific 
collections, a custom per
 * *config-read*: this permission is allowed to read a collection's 
configuration using the <<config-api.adoc#,Config API>>, the 
<<request-parameters-api.adoc#,Request Parameters API>>, 
<<configsets-api.adoc#configsets-list,Configsets API>>, the Admin UI's 
<<configuration-files.adoc#files-screen,Files Screen>>, and other APIs 
accessing configuration.
 Note that this allows configuration read permissions for _all_ collections.
 If read permissions should only be applied to specific collections, a custom 
permission would need to be created.
-* *metrics-read*: this permission allows access to Solr's 
<<metrics-reporting.adoc#metrics-api,Metrics API>>.
+* *metrics-read*: this permission allows access to Solr's 
<<metrics-reporting.adoc#metrics-api,Metrics API>>, some 
<<implicit-requesthandlers#admin-handlers,implicit admin handlers>> such as 
`solr/<collection>/admin/mbeans` and `solr/<collection>/admin/segments` as well 
as other admin APIs exposing metrics.
+* *health*: this permission allows access to Solr's 
<<implicit-requesthandlers#admin-handlers,Health Check and Ping>> endpoints, 
typically used to monitor whether a node or core is healthy.
 * *core-admin-edit*: Core admin commands that can mutate the system state.
 * *core-admin-read*: Read operations on the core admin API
 * *collection-admin-edit*: this permission is allowed to edit a collection's 
configuration using the <<collections-api.adoc#,Collections API>>.
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index 7e90c2b..64a5f8e 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -120,6 +120,13 @@ limitations under the License.
 
       </div>
 
+      <div class="header-message" id="authz-failures" 
ng-show="showAuthzFailures" ng-cloak>
+
+        <h2>Permission failure</h2>
+        <p>Your user does not have the necessary role(s) to perform this 
action.</p>
+
+      </div>
+
       <div id="loading" class="loader universal-loader" 
loading-status-message>&nbsp;</div>
 
       <div id="connection-box" connection-message>
diff --git a/solr/webapp/web/js/angular/app.js 
b/solr/webapp/web/js/angular/app.js
index 1d04f97..437a7e2 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -387,6 +387,7 @@ solrAdminApp.config([
     if (activeRequests == 0) {
       $rootScope.$broadcast('loadingStatusInactive');
     }
+    $rootScope.showAuthzFailures = false;
     if ($rootScope.retryCount>0) {
       $rootScope.connectionRecovered = true;
       $rootScope.retryCount=0;
@@ -442,6 +443,9 @@ solrAdminApp.config([
         sessionStorage.setItem("auth.location", $location.path());
         $location.path('/login');
       }
+    } else if (rejection.status === 403 && !isHandledBySchemaDesigner) {
+      // No permission
+      $rootScope.showAuthzFailures = true;
     } else {
       // schema designer prefers to handle errors itself
       if (!isHandledBySchemaDesigner) {
diff --git a/solr/webapp/web/js/angular/controllers/security.js 
b/solr/webapp/web/js/angular/controllers/security.js
index b1bae4d..c95469c 100644
--- a/solr/webapp/web/js/angular/controllers/security.js
+++ b/solr/webapp/web/js/angular/controllers/security.js
@@ -142,9 +142,10 @@ solrAdminApp.controller('SecurityController', function 
($scope, $timeout, $cooki
     return (!obj || (Array.isArray(obj) && obj.length === 0)) ? "null" : 
$scope.displayList(obj);
   };
 
+  // TODO: Read this list from Solr to avoid duplication
   $scope.predefinedPermissions = ["collection-admin-edit", 
"collection-admin-read", "core-admin-read", "core-admin-edit", "zk-read",
     "read", "update", "all", "config-edit", "config-read", "schema-read", 
"schema-edit", "security-edit", "security-read",
-    "metrics-read", "filestore-read", "filestore-write", "package-edit", 
"package-read"].sort();
+    "metrics-read", "health", "filestore-read", "filestore-write", 
"package-edit", "package-read"].sort();
 
   $scope.predefinedPermissionCollection = {"read":"*", "update":"*", 
"config-edit":"*", "config-read":"*", "schema-edit":"*", "schema-read":"*"};
 

Reply via email to