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> </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":"*"};