You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2016/03/23 20:18:47 UTC

lucene-solr:apiv2: SOLR-8029: enable schema enforcement for commands

Repository: lucene-solr
Updated Branches:
  refs/heads/apiv2 38d542226 -> ba5dc7503


SOLR-8029: enable schema enforcement for commands


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/ba5dc750
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/ba5dc750
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/ba5dc750

Branch: refs/heads/apiv2
Commit: ba5dc7503e9e86ae636ef5f9d9e2852c4ddeb619
Parents: 38d5422
Author: Noble Paul <no...@apache.org>
Authored: Thu Mar 24 00:48:31 2016 +0530
Committer: Noble Paul <no...@apache.org>
Committed: Thu Mar 24 00:48:31 2016 +0530

----------------------------------------------------------------------
 solr/core/src/java/org/apache/solr/api/Api.java | 18 +++++++-
 .../src/java/org/apache/solr/api/ApiBag.java    | 42 ++++++++++++++-----
 .../java/org/apache/solr/api/V2HttpCall.java    |  6 +++
 .../apache/solr/handler/admin/InfoHandler.java  | 11 ++---
 .../solr/handler/admin/SecurityConfHandler.java | 43 +++++++++++++++-----
 .../solr/request/SolrQueryRequestBase.java      |  8 +++-
 .../org/apache/solr/servlet/HttpSolrCall.java   |  9 +++-
 .../org/apache/solr/servlet/ResponseUtils.java  |  6 +++
 solr/core/src/resources/apispec/node.Info.json  |  1 -
 9 files changed, 112 insertions(+), 32 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/api/Api.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/Api.java b/solr/core/src/java/org/apache/solr/api/Api.java
index 131b2f4..384a829 100644
--- a/solr/core/src/java/org/apache/solr/api/Api.java
+++ b/solr/core/src/java/org/apache/solr/api/Api.java
@@ -18,19 +18,35 @@ package org.apache.solr.api;
  */
 
 
-import java.util.concurrent.Callable;
+import java.util.Map;
 
+import com.google.common.collect.ImmutableMap;
 import org.apache.solr.common.util.Map2;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.JsonSchemaValidator;
 
 public abstract class Api implements SpecProvider {
   protected SpecProvider spec;
+  protected volatile Map<String, JsonSchemaValidator> commandSchema;
 
   protected Api(SpecProvider spec) {
     this.spec = spec;
   }
 
+  public Map<String, JsonSchemaValidator> getCommandSchema() {
+    if (commandSchema == null) {
+      synchronized (this) {
+        if(commandSchema == null) {
+          Map2 commands = getSpec().getMap("commands", null);
+          commandSchema = commands != null ?
+              ImmutableMap.copyOf(ApiBag.getPartsedSchema(commands)) :
+              ImmutableMap.of();
+        }
+      }
+    }
+    return commandSchema;
+  }
 
   public abstract void call(SolrQueryRequest req , SolrQueryResponse rsp);
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/api/ApiBag.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java b/solr/core/src/java/org/apache/solr/api/ApiBag.java
index f5bb47f..64c247a 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.io.Reader;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -39,6 +40,7 @@ import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.PermissionNameProvider;
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.PathTrie;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -149,11 +151,24 @@ public class ApiBag {
       }
     };
   }
+  public static Map<String, JsonSchemaValidator> getPartsedSchema(Map2 commands) {
+    Map<String,JsonSchemaValidator> validators =  new HashMap<>();
+    for (Object o : commands.entrySet()) {
+      Map.Entry cmd = (Map.Entry) o;
+      try {
+        validators.put((String) cmd.getKey(), new JsonSchemaValidator((Map) cmd.getValue()));
+      } catch (Exception e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in spi spec" , e);
+      }
+    }
+    return validators;
+  }
+
 
   private void verifyCommands(Map2 spec) {
     Map2 commands = spec.getMap("commands", null);
     if (commands == null) return;
-    //TODO do verify
+    getPartsedSchema(commands);
 
   }
 
@@ -217,7 +232,7 @@ public class ApiBag {
   public static class ReqHandlerToApi extends Api implements PermissionNameProvider {
     SolrRequestHandler rh;
 
-    protected ReqHandlerToApi(SolrRequestHandler rh, SpecProvider spec) {
+    public ReqHandlerToApi(SolrRequestHandler rh, SpecProvider spec) {
       super(spec);
       this.rh = rh;
     }
@@ -269,26 +284,29 @@ public class ApiBag {
     }
   }
 
-  public static List<CommandOperation> getCommandOperations(Reader reader, Map2 spec, boolean validate) {
+  public static List<CommandOperation> getCommandOperations(Reader reader, Map<String,JsonSchemaValidator> validators, boolean validate) {
     List<CommandOperation> parsedCommands = null;
     try {
       parsedCommands = CommandOperation.parse(reader);
     } catch (IOException e) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
     }
-    if (spec == null || !validate) {    // no validation possible because we do not have a spec
+    if (validators == null || !validate) {    // no validation possible because we do not have a spec
       return parsedCommands;
     }
 
-    Map2 cmds = spec.getMap("commands", NOT_NULL);
     List<CommandOperation> commandsCopy = CommandOperation.clone(parsedCommands);
 
     for (CommandOperation cmd : commandsCopy) {
-      if (!cmds.containsKey(cmd.name)) {
-        cmd.addError(formatString("Unknown operation ''{0}'' in path ''{1}''", cmd.name,
-            spec.getMap("url", NOT_NULL).get("paths")));
+      JsonSchemaValidator validator = validators.get(cmd.name);
+      if (validator == null) {
+        cmd.addError(formatString("Unknown operation ''{0}'' available ops are ''{1}''", cmd.name,
+            validators.keySet()));
+        continue;
+      } else {
+        List<String> errs = validator.validateJson(cmd.getCommandData());
+        if(errs != null) for (String err : errs) cmd.addError(err);
       }
-      //TODO validation
 
     }
     List<Map> errs = CommandOperation.captureErrors(commandsCopy);
@@ -298,13 +316,17 @@ public class ApiBag {
     return commandsCopy;
   }
 
-  static class ExceptionWithErrObject extends SolrException {
+  public static class ExceptionWithErrObject extends SolrException {
     private List<Map> errs;
 
     public ExceptionWithErrObject(ErrorCode code, String msg, List<Map> errs) {
       super(code, msg);
       this.errs = errs;
     }
+
+    public List<Map> getErrs(){
+      return errs;
+    }
   }
 
   public static class LazyLoadedApi extends Api {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
index 430e014..f513b69 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -46,6 +46,7 @@ import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.servlet.HttpSolrCall;
 import org.apache.solr.servlet.SolrDispatchFilter;
 import org.apache.solr.servlet.SolrRequestParsers;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.PathTrie;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -286,4 +287,9 @@ public class V2HttpCall extends HttpSolrCall {
   protected Map2 getSpec() {
     return api == null ? null : api.getSpec();
   }
+
+  @Override
+  protected Map<String, JsonSchemaValidator> getValidators() {
+    return api == null ? null : api.getCommandSchema();
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/handler/admin/InfoHandler.java
----------------------------------------------------------------------
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 dcfb1e0..acbb395 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
@@ -22,6 +22,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.solr.api.ApiBag.ReqHandlerToApi;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CoreContainer;
@@ -35,6 +36,8 @@ import org.apache.solr.api.ApiSupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.util.Collections.singletonList;
+import static org.apache.solr.api.ApiBag.getSpec;
 import static org.apache.solr.common.params.CommonParams.PATH;
 
 public class InfoHandler extends RequestHandlerBase implements ApiSupport {
@@ -144,13 +147,7 @@ public class InfoHandler extends RequestHandlerBase implements ApiSupport {
 
   @Override
   public Collection<Api> getApis() {
-    return Collections.singletonList(new Api(ApiBag.getSpec("node.Info")) {
-      @Override
-      public void call(SolrQueryRequest req, SolrQueryResponse rsp) {
-        handle(req, rsp, req.getPath());
-
-      }
-    });
+    return singletonList(new ReqHandlerToApi(this, getSpec("node.Info")));
   }
 
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index b06a017..7de562c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -44,6 +44,7 @@ import org.apache.solr.util.CommandOperation;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.ApiBag;
 import org.apache.solr.api.SpecProvider;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.zookeeper.KeeperException;
 
 public class SecurityConfHandler extends RequestHandlerBase implements PermissionNameProvider {
@@ -184,6 +185,9 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
 
 
   private Collection<Api> apis;
+  private AuthenticationPlugin authcPlugin;
+  private AuthorizationPlugin authzPlugin;
+
   @Override
   public Collection<Api> getApis() {
     if (apis == null) {
@@ -194,23 +198,40 @@ public class SecurityConfHandler extends RequestHandlerBase implements Permissio
           final SpecProvider authzCommands = ApiBag.getSpec("cluster.security.authorization.Commands");
           apis.add(ApiBag.wrapRequestHandler(this, ApiBag.getSpec("cluster.security.authentication")));
           apis.add(ApiBag.wrapRequestHandler(this, ApiBag.getSpec("cluster.security.authorization")));
-          apis.add(ApiBag.wrapRequestHandler(this, () -> {
+          SpecProvider authcSpecProvider = () -> {
             AuthenticationPlugin authcPlugin = cores.getAuthenticationPlugin();
             return authcPlugin != null && authcPlugin instanceof SpecProvider ?
                 ((SpecProvider) authcPlugin).getSpec() :
-                ApiBag.getSpec("cluster.security.authentication.Commands").getSpec();
-          }));
-
-//          AuthorizationPlugin authzPlugin = cores.getAuthorizationPlugin();
-//          apis.add(ApiBag.wrapRequestHandler(this, authzPlugin != null && authzPlugin instanceof SpecProvider ? ((SpecProvider) authzPlugin) : authzCommands));
-
-          apis.add(ApiBag.wrapRequestHandler(this, () -> {
+                authcCommands.getSpec();
+          };
+
+          apis.add(new ApiBag.ReqHandlerToApi(this, authcSpecProvider) {
+            @Override
+            public synchronized Map<String, JsonSchemaValidator> getCommandSchema() {
+              //it is possible that the Auhentication plugin is modified since the last call. invalidate the
+              // the cached commandSchema
+              if(SecurityConfHandler.this.authcPlugin != cores.getAuthenticationPlugin()) commandSchema = null;
+              SecurityConfHandler.this.authcPlugin = cores.getAuthenticationPlugin();
+              return super.getCommandSchema();
+            }
+          });
+
+          SpecProvider authzSpecProvider = () -> {
             AuthorizationPlugin authzPlugin = cores.getAuthorizationPlugin();
             return authzPlugin != null && authzPlugin instanceof SpecProvider ?
                 ((SpecProvider) authzPlugin).getSpec() :
-                ApiBag.getSpec("cluster.security.authorization.Commands").getSpec();
-          }));
-
+                authzCommands.getSpec();
+          };
+          apis.add(new ApiBag.ReqHandlerToApi(this, authzSpecProvider) {
+            @Override
+            public synchronized Map<String, JsonSchemaValidator> getCommandSchema() {
+              //it is possible that the Authorization plugin is modified since the last call. invalidate the
+              // the cached commandSchema
+              if(SecurityConfHandler.this.authzPlugin != cores.getAuthorizationPlugin()) commandSchema = null;
+              SecurityConfHandler.this.authzPlugin = cores.getAuthorizationPlugin();
+              return super.getCommandSchema();
+            }
+          });
 
           this.apis = ImmutableList.copyOf(apis);
         }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
index e94b51d..42d0814 100644
--- a/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
+++ b/solr/core/src/java/org/apache/solr/request/SolrQueryRequestBase.java
@@ -24,6 +24,7 @@ import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.servlet.HttpSolrCall;
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.RTimerTree;
 import org.apache.solr.util.RefCounted;
 import org.apache.solr.schema.IndexSchema;
@@ -35,6 +36,7 @@ import java.io.Closeable;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.security.Principal;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.HashMap;
@@ -203,7 +205,7 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl
       if (contentStreams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No content stream");
       for (ContentStream contentStream : contentStreams) {
         parsedCommands = ApiBag.getCommandOperations(new InputStreamReader((InputStream) contentStream, UTF_8),
-            getSpec(), validateInput);
+            getValidators(), validateInput);
       }
 
     }
@@ -214,4 +216,8 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl
   protected Map2 getSpec() {
     return null;
   }
+
+  protected Map<String, JsonSchemaValidator> getValidators(){
+    return Collections.EMPTY_MAP;
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 3bd4cea..44cf5a9 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -105,6 +105,7 @@ import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
 import org.apache.solr.servlet.cache.Method;
 import org.apache.solr.update.processor.DistributingUpdateProcessorFactory;
 import org.apache.solr.util.CommandOperation;
+import org.apache.solr.util.JsonSchemaValidator;
 import org.apache.solr.util.RTimerTree;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -1075,7 +1076,7 @@ public class HttpSolrCall {
       if (contentStreams == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No content stream");
       for (ContentStream contentStream : contentStreams) {
         try {
-          parsedCommands = ApiBag.getCommandOperations(contentStream.getReader(), getSpec(), validateInput);
+          parsedCommands = ApiBag.getCommandOperations(contentStream.getReader(), getValidators(), validateInput);
         } catch (IOException e) {
           throw new SolrException(ErrorCode.BAD_REQUEST, "Error reading commands");
         }
@@ -1087,4 +1088,10 @@ public class HttpSolrCall {
   protected Map2 getSpec() {
     return null;
   }
+
+  protected Map<String, JsonSchemaValidator> getValidators(){
+    return Collections.EMPTY_MAP;
+  }
+
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
index a74fa8a..00733f5 100644
--- a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
+++ b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 package org.apache.solr.servlet;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
+import org.apache.solr.util.CommandOperation;
 import org.slf4j.Logger;
 
 import java.io.PrintWriter;
@@ -48,6 +50,10 @@ public class ResponseUtils {
       errorMetadata.add(SolrException.ERROR_CLASS, ex.getClass().getName());
       errorMetadata.add(SolrException.ROOT_ERROR_CLASS, SolrException.getRootCause(ex).getClass().getName());
       info.add("metadata", errorMetadata);
+      if (ex instanceof ApiBag.ExceptionWithErrObject) {
+        ApiBag.ExceptionWithErrObject exception = (ApiBag.ExceptionWithErrObject) ex;
+        info.add(CommandOperation.ERR_MSGS, exception.getErrs() );
+      }
     }
     
     for (Throwable th = ex; th != null; th = th.getCause()) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ba5dc750/solr/core/src/resources/apispec/node.Info.json
----------------------------------------------------------------------
diff --git a/solr/core/src/resources/apispec/node.Info.json b/solr/core/src/resources/apispec/node.Info.json
index 8ed7740..c6ba07d 100644
--- a/solr/core/src/resources/apispec/node.Info.json
+++ b/solr/core/src/resources/apispec/node.Info.json
@@ -2,7 +2,6 @@
   "documentation": "https://cwiki.apache.org/confluence/display/solr/Schema+API",
   "methods": ["GET"],
   "url": {
-    "path": "/node",
     "paths": [
       "/node/properties",
       "/node/threads",