diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index ad5e80ba16edd..8102016828c30 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -22,6 +22,7 @@ import org.elasticsearch.bootstrap.BootstrapInfo; import org.elasticsearch.painless.antlr.Walker; import org.elasticsearch.painless.node.SSource; +import org.elasticsearch.painless.spi.Whitelist; import org.objectweb.asm.util.Printer; import java.lang.reflect.Constructor; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index 7d8b4ff4e614e..7729c5319ea81 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -20,6 +20,7 @@ package org.elasticsearch.painless; import org.apache.lucene.util.SetOnce; +import org.elasticsearch.painless.spi.Whitelist; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -46,29 +47,6 @@ public final class Definition { private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$"); - public static final String[] DEFINITION_FILES = new String[] { - "org.elasticsearch.txt", - "java.lang.txt", - "java.math.txt", - "java.text.txt", - "java.time.txt", - "java.time.chrono.txt", - "java.time.format.txt", - "java.time.temporal.txt", - "java.time.zone.txt", - "java.util.txt", - "java.util.function.txt", - "java.util.regex.txt", - "java.util.stream.txt", - "joda.time.txt" - }; - - /** - * Whitelist that is "built in" to Painless and required by all scripts. - */ - public static final Definition DEFINITION = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, DEFINITION_FILES))); - /** Some native types as constants: */ public final Type voidType; public final Type booleanType; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java index 842af8717a34b..795d81bb6e058 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java @@ -22,28 +22,56 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.painless.spi.PainlessExtension; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.plugins.ExtensiblePlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.ScriptPlugin; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptEngine; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; /** * Registers Painless as a plugin. */ public final class PainlessPlugin extends Plugin implements ScriptPlugin, ExtensiblePlugin { + private final Map, List> extendedWhitelists = new HashMap<>(); + @Override public ScriptEngine getScriptEngine(Settings settings, Collection> contexts) { - return new PainlessScriptEngine(settings, contexts); + Map, List> contextsWithWhitelists = new HashMap<>(); + for (ScriptContext context : contexts) { + // we might have a context that only uses the base whitelists, so would not have been filled in by reloadSPI + List whitelists = extendedWhitelists.get(context); + if (whitelists == null) { + whitelists = new ArrayList<>(Whitelist.BASE_WHITELISTS); + } + contextsWithWhitelists.put(context, whitelists); + } + return new PainlessScriptEngine(settings, contextsWithWhitelists); } @Override public List> getSettings() { return Arrays.asList(CompilerSettings.REGEX_ENABLED); } + + @Override + public void reloadSPI(ClassLoader loader) { + for (PainlessExtension extension : ServiceLoader.load(PainlessExtension.class, loader)) { + for (Map.Entry, List> entry : extension.getContextWhitelists().entrySet()) { + List existing = extendedWhitelists.computeIfAbsent(entry.getKey(), + c -> new ArrayList<>(Whitelist.BASE_WHITELISTS)); + existing.addAll(entry.getValue()); + } + } + } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java index ac01f45a7fdd6..95a38bf22c653 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java @@ -19,12 +19,12 @@ package org.elasticsearch.painless; -import org.apache.logging.log4j.core.tools.Generate; import org.apache.lucene.index.LeafReaderContext; import org.elasticsearch.SpecialPermission; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.painless.Compiler.Loader; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptEngine; @@ -45,7 +45,6 @@ import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -82,7 +81,7 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr /** * Default compiler settings to be used. Note that {@link CompilerSettings} is mutable but this instance shouldn't be mutated outside - * of {@link PainlessScriptEngine#PainlessScriptEngine(Settings, Collection)}. + * of {@link PainlessScriptEngine#PainlessScriptEngine(Settings, Map)}. */ private final CompilerSettings defaultCompilerSettings = new CompilerSettings(); @@ -92,23 +91,19 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr * Constructor. * @param settings The settings to initialize the engine with. */ - public PainlessScriptEngine(Settings settings, Collection> contexts) { + public PainlessScriptEngine(Settings settings, Map, List> contexts) { super(settings); defaultCompilerSettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(settings)); Map, Compiler> contextsToCompilers = new HashMap<>(); - // Placeholder definition used for all contexts until SPI is fully integrated. Reduces memory foot print - // by re-using the same definition since caching isn't implemented at this time. - Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); - - for (ScriptContext context : contexts) { + for (Map.Entry, List> entry : contexts.entrySet()) { + ScriptContext context = entry.getKey(); if (context.instanceClazz.equals(SearchScript.class) || context.instanceClazz.equals(ExecutableScript.class)) { - contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class, definition)); + contextsToCompilers.put(context, new Compiler(GenericElasticsearchScript.class, new Definition(entry.getValue()))); } else { - contextsToCompilers.put(context, new Compiler(context.instanceClazz, definition)); + contextsToCompilers.put(context, new Compiler(context.instanceClazz, new Definition(entry.getValue()))); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/PainlessExtension.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/PainlessExtension.java new file mode 100644 index 0000000000000..9434e6986c0a3 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/PainlessExtension.java @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.painless.spi; + +import java.util.List; +import java.util.Map; + +import org.elasticsearch.script.ScriptContext; + +public interface PainlessExtension { + + Map, List> getContextWhitelists(); +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Whitelist.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/Whitelist.java similarity index 93% rename from modules/lang-painless/src/main/java/org/elasticsearch/painless/Whitelist.java rename to modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/Whitelist.java index 678b8a4c1ae38..e715eb0090c7f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Whitelist.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/Whitelist.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.painless; +package org.elasticsearch.painless.spi; import java.util.Collections; import java.util.List; @@ -34,6 +34,26 @@ */ public final class Whitelist { + private static final String[] BASE_WHITELIST_FILES = new String[] { + "org.elasticsearch.txt", + "java.lang.txt", + "java.math.txt", + "java.text.txt", + "java.time.txt", + "java.time.chrono.txt", + "java.time.format.txt", + "java.time.temporal.txt", + "java.time.zone.txt", + "java.util.txt", + "java.util.function.txt", + "java.util.regex.txt", + "java.util.stream.txt", + "joda.time.txt" + }; + + public static final List BASE_WHITELISTS = + Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Whitelist.class, BASE_WHITELIST_FILES)); + /** * Struct represents the equivalent of a Java class in Painless complete with super classes, * constructors, methods, and fields. In Painless a class is known as a struct primarily to avoid diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WhitelistLoader.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java similarity index 98% rename from modules/lang-painless/src/main/java/org/elasticsearch/painless/WhitelistLoader.java rename to modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java index 93ea951f453aa..8817bfa274c60 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WhitelistLoader.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.painless; +package org.elasticsearch.painless.spi; import java.io.InputStreamReader; import java.io.LineNumberReader; @@ -25,6 +25,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -296,8 +298,9 @@ public static Whitelist loadFromResourceFiles(Class resource, String... filep throw new RuntimeException("error in [" + filepath + "] at line [" + number + "]", exception); } } + ClassLoader loader = AccessController.doPrivileged((PrivilegedAction)resource::getClassLoader); - return new Whitelist(resource.getClassLoader(), whitelistStructs); + return new Whitelist(loader, whitelistStructs); } private WhitelistLoader() {} diff --git a/modules/lang-painless/src/main/plugin-metadata/plugin-security.policy b/modules/lang-painless/src/main/plugin-metadata/plugin-security.policy index e45c1b86ceb2c..b383c6da3f12c 100644 --- a/modules/lang-painless/src/main/plugin-metadata/plugin-security.policy +++ b/modules/lang-painless/src/main/plugin-metadata/plugin-security.policy @@ -20,4 +20,7 @@ grant { // needed to generate runtime classes permission java.lang.RuntimePermission "createClassLoader"; + + // needed to find the classloader to load whitelisted classes from + permission java.lang.RuntimePermission "getClassLoader"; }; diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.lang.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.math.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.math.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.math.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.math.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.text.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.text.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.text.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.text.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.chrono.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.chrono.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.chrono.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.chrono.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.format.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.format.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.format.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.format.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.temporal.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.temporal.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.temporal.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.temporal.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.zone.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.zone.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.time.zone.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.time.zone.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.function.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.function.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.function.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.function.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.regex.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.regex.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.stream.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.stream.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.stream.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.stream.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/java.util.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/joda.time.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/joda.time.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/joda.time.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/joda.time.txt diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt similarity index 100% rename from modules/lang-painless/src/main/resources/org/elasticsearch/painless/org.elasticsearch.txt rename to modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.txt diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java index 58ae31a45c93a..919b0881c0794 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AnalyzerCasterTests.java @@ -21,16 +21,12 @@ import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Type; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.test.ESTestCase; -import java.util.Collections; - -import static org.elasticsearch.painless.Definition.DEFINITION_FILES; - public class AnalyzerCasterTests extends ESTestCase { - private static final Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, DEFINITION_FILES))); + private static final Definition definition = new Definition(Whitelist.BASE_WHITELISTS); private static void assertCast(Type actual, Type expected, boolean mustBeExplicit) { Location location = new Location("dummy", 0); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java index 2ba8692b8af59..59cafa96ddcb9 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java @@ -19,13 +19,12 @@ package org.elasticsearch.painless; -import org.elasticsearch.script.ScriptContext; - -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.elasticsearch.painless.spi.Whitelist; + import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.containsString; @@ -37,8 +36,7 @@ */ public class BaseClassTests extends ScriptTestCase { - private final Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); + private final Definition definition = new Definition(Whitelist.BASE_WHITELISTS); public abstract static class Gets { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java index a55b48f0189b3..279438e74a7c3 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java @@ -22,10 +22,10 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ScriptException; import java.io.IOException; -import java.util.Collections; import java.util.Map; import static java.util.Collections.singletonList; @@ -35,8 +35,7 @@ import static org.hamcrest.Matchers.not; public class DebugTests extends ScriptTestCase { - private final Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); + private final Definition definition = new Definition(Whitelist.BASE_WHITELISTS); public void testExplain() { // Debug.explain can explain an object diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java index 52ec783db4ef4..e29986a3c87de 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java @@ -20,11 +20,11 @@ package org.elasticsearch.painless; import org.apache.lucene.util.IOUtils; +import org.elasticsearch.painless.spi.Whitelist; import org.objectweb.asm.util.Textifier; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Collections; /** quick and dirty tools for debugging */ final class Debugger { @@ -40,8 +40,7 @@ static String toString(Class iface, String source, CompilerSettings settings) PrintWriter outputWriter = new PrintWriter(output); Textifier textifier = new Textifier(); try { - new Compiler(iface, new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES)))) + new Compiler(iface, new Definition(Whitelist.BASE_WHITELISTS)) .compile("", source, settings, textifier); } catch (Exception e) { textifier.print(outputWriter); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java index dccc9c0aeb505..8fd96d67d5b53 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java @@ -27,11 +27,11 @@ import java.util.Collections; import java.util.HashMap; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.test.ESTestCase; public class DefBootstrapTests extends ESTestCase { - private final Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); + private final Definition definition = new Definition(Whitelist.BASE_WHITELISTS); /** calls toString() on integers, twice */ public void testOneType() throws Throwable { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java index b15a2747bd088..556ef8dd3c6d3 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/FactoryTests.java @@ -19,21 +19,23 @@ package org.elasticsearch.painless; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.TemplateScript; -import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; public class FactoryTests extends ScriptTestCase { - protected Collection> scriptContexts() { - Collection> contexts = super.scriptContexts(); - contexts.add(StatefulFactoryTestScript.CONTEXT); - contexts.add(FactoryTestScript.CONTEXT); - contexts.add(EmptyTestScript.CONTEXT); - contexts.add(TemplateScript.CONTEXT); + @Override + protected Map, List> scriptContexts() { + Map, List> contexts = super.scriptContexts(); + contexts.put(StatefulFactoryTestScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(FactoryTestScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(EmptyTestScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(TemplateScript.CONTEXT, Whitelist.BASE_WHITELISTS); return contexts; } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java index db254b734a81a..50a377b881878 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/NeedsScoreTests.java @@ -22,14 +22,17 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.query.QueryShardContext; -import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.test.ESSingleNodeTestCase; -import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Test that needsScores() is reported correctly depending on whether _score is used @@ -40,8 +43,10 @@ public class NeedsScoreTests extends ESSingleNodeTestCase { public void testNeedsScores() { IndexService index = createIndex("test", Settings.EMPTY, "type", "d", "type=double"); - PainlessScriptEngine service = new PainlessScriptEngine(Settings.EMPTY, - Arrays.asList(SearchScript.CONTEXT, ExecutableScript.CONTEXT)); + Map, List> contexts = new HashMap<>(); + contexts.put(SearchScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(ExecutableScript.CONTEXT, Whitelist.BASE_WHITELISTS); + PainlessScriptEngine service = new PainlessScriptEngine(Settings.EMPTY, contexts); QueryShardContext shardContext = index.newQueryShardContext(0, null, () -> 0, null); SearchLookup lookup = new SearchLookup(index.mapperService(), shardContext::getForField, null); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java index edd600c5664f2..87b1677102635 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/PainlessDocGenerator.java @@ -27,7 +27,7 @@ import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Struct; import org.elasticsearch.painless.Definition.Type; -import org.elasticsearch.painless.api.Augmentation; +import org.elasticsearch.painless.spi.Whitelist; import java.io.IOException; import java.io.PrintStream; @@ -36,7 +36,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -68,8 +67,7 @@ public static void main(String[] args) throws IOException { Files.newOutputStream(indexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name())) { emitGeneratedWarning(indexStream); - List types = new Definition(Collections.singletonList( - WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))). + List types = new Definition(Whitelist.BASE_WHITELISTS). allSimpleTypes().stream().sorted(comparing(t -> t.name)).collect(toList()); for (Type type : types) { if (type.clazz.isPrimitive()) { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index 730dd298f8a54..ea1d2275b3e8d 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.lucene.ScorerAware; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.painless.antlr.Walker; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptException; @@ -31,10 +32,8 @@ import org.elasticsearch.test.ESTestCase; import org.junit.Before; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.elasticsearch.painless.node.SSource.MainMethodReserved; @@ -63,11 +62,10 @@ protected Settings scriptEngineSettings() { /** * Script contexts used to build the script engine. Override to customize which script contexts are available. */ - protected Collection> scriptContexts() { - Collection> contexts = new ArrayList<>(); - contexts.add(SearchScript.CONTEXT); - contexts.add(ExecutableScript.CONTEXT); - + protected Map, List> scriptContexts() { + Map, List> contexts = new HashMap<>(); + contexts.put(SearchScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(ExecutableScript.CONTEXT, Whitelist.BASE_WHITELISTS); return contexts; } @@ -92,8 +90,7 @@ public Object exec(String script, Map vars, boolean picky) { public Object exec(String script, Map vars, Map compileParams, Scorer scorer, boolean picky) { // test for ambiguity errors before running the actual script if picky is true if (picky) { - Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); + Definition definition = new Definition(Whitelist.BASE_WHITELISTS); ScriptClassInfo scriptClassInfo = new ScriptClassInfo(definition, GenericElasticsearchScript.class); CompilerSettings pickySettings = new CompilerSettings(); pickySettings.setPicky(true); diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/SimilarityScriptTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/SimilarityScriptTests.java index d8f43fb066867..0795ab7777526 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/SimilarityScriptTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/SimilarityScriptTests.java @@ -37,20 +37,25 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.elasticsearch.index.similarity.ScriptedSimilarity; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.SimilarityScript; import org.elasticsearch.script.SimilarityWeightScript; import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class SimilarityScriptTests extends ScriptTestCase { @Override - protected Collection> scriptContexts() { - return Arrays.asList(SimilarityScript.CONTEXT, SimilarityWeightScript.CONTEXT); + protected Map, List> scriptContexts() { + Map, List> contexts = new HashMap<>(); + contexts.put(SimilarityScript.CONTEXT, Whitelist.BASE_WHITELISTS); + contexts.put(SimilarityWeightScript.CONTEXT, Whitelist.BASE_WHITELISTS); + return contexts; } public void testBasics() throws IOException { diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java index 9e3477b1cfe02..424b0c286ecff 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/node/NodeToStringTests.java @@ -33,12 +33,11 @@ import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.ScriptClassInfo; -import org.elasticsearch.painless.WhitelistLoader; +import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.painless.antlr.Walker; import org.elasticsearch.test.ESTestCase; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -50,8 +49,7 @@ * Tests {@link Object#toString} implementations on all extensions of {@link ANode}. */ public class NodeToStringTests extends ESTestCase { - private final Definition definition = new Definition( - Collections.singletonList(WhitelistLoader.loadFromResourceFiles(Definition.class, Definition.DEFINITION_FILES))); + private final Definition definition = new Definition(Whitelist.BASE_WHITELISTS); public void testEAssignment() { assertToString( diff --git a/plugins/examples/painless-whitelist/build.gradle b/plugins/examples/painless-whitelist/build.gradle index 2213aea16f6cd..12bbff8b0419e 100644 --- a/plugins/examples/painless-whitelist/build.gradle +++ b/plugins/examples/painless-whitelist/build.gradle @@ -26,6 +26,10 @@ esplugin { extendedPlugins = ['lang-painless'] } +dependencies { + compileOnly project(':modules:lang-painless') +} + integTestCluster { distribution = 'zip' } diff --git a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java new file mode 100644 index 0000000000000..9e3bc66e7d58d --- /dev/null +++ b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.example.painlesswhitelist; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.elasticsearch.painless.spi.PainlessExtension; +import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.ScriptContext; +import org.elasticsearch.script.SearchScript; + +/** An extension of painless which adds a whitelist. */ +public class ExampleWhitelistExtension implements PainlessExtension { + + private static final Whitelist WHITELIST = + WhitelistLoader.loadFromResourceFiles(ExampleWhitelistExtension.class, "example_whitelist.txt"); + + @Override + public Map, List> getContextWhitelists() { + return Collections.singletonMap(SearchScript.CONTEXT, Collections.singletonList(WHITELIST)); + } +} diff --git a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistedClass.java b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistedClass.java new file mode 100644 index 0000000000000..14f15b383d0c8 --- /dev/null +++ b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistedClass.java @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.example.painlesswhitelist; + +/** + * An example of a class to be whitelisted for use by painless scripts + * + * Each of the members and methods below are whitelisted for use in search scripts. + * See example_whitelist.txt. + */ +public class ExampleWhitelistedClass { + + public static final int CONSTANT = 42; + + public int publicMember; + + private int privateMember; + + public ExampleWhitelistedClass(int publicMember, int privateMember) { + this.publicMember = publicMember; + this.privateMember = privateMember; + } + + public int getPrivateMemberAccessor() { + return this.privateMember; + } + + public void setPrivateMemberAccessor(int privateMember) { + this.privateMember = privateMember; + } + + public static void staticMethod() { + // electricity + } + + // example augmentation method + public static int toInt(String x) { + return Integer.parseInt(x); + } +} diff --git a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/MyWhitelistPlugin.java b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/MyWhitelistPlugin.java index 877a05391ac77..a4ef5f6f000e1 100644 --- a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/MyWhitelistPlugin.java +++ b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/MyWhitelistPlugin.java @@ -22,4 +22,5 @@ import org.elasticsearch.plugins.Plugin; public class MyWhitelistPlugin extends Plugin { + // we don't actually need anything here, since whitelists are extended through SPI } diff --git a/plugins/examples/painless-whitelist/src/main/resources/META-INF/services/org.elasticsearch.painless.spi.PainlessExtension b/plugins/examples/painless-whitelist/src/main/resources/META-INF/services/org.elasticsearch.painless.spi.PainlessExtension new file mode 100644 index 0000000000000..9babd702c8083 --- /dev/null +++ b/plugins/examples/painless-whitelist/src/main/resources/META-INF/services/org.elasticsearch.painless.spi.PainlessExtension @@ -0,0 +1 @@ +org.elasticsearch.example.painlesswhitelist.ExampleWhitelistExtension \ No newline at end of file diff --git a/plugins/examples/painless-whitelist/src/main/resources/org/elasticsearch/example/painlesswhitelist/example_whitelist.txt b/plugins/examples/painless-whitelist/src/main/resources/org/elasticsearch/example/painlesswhitelist/example_whitelist.txt new file mode 100644 index 0000000000000..7908d35417511 --- /dev/null +++ b/plugins/examples/painless-whitelist/src/main/resources/org/elasticsearch/example/painlesswhitelist/example_whitelist.txt @@ -0,0 +1,42 @@ +# +# Licensed to Elasticsearch under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This file contains a whitelist for an example class which may be access from painless + +class org.elasticsearch.example.painlesswhitelist.ExampleWhitelistedClass { + # constructor + (int, int) + + # static constants and methods look the same as instance members and methods + int CONSTANT + void staticMethod() + + # members lack parenthesis that methods have + int publicMember + + # getter and setter for private member + int getPrivateMemberAccessor() + void setPrivateMemberAccessor(int) +} + +class java.lang.String { + # existing classes can be "augmented" to have additional methods, which take the object + # to operate on as the first argument to a static method + int org.elasticsearch.example.painlesswhitelist.ExampleWhitelistedClass toInt() +} \ No newline at end of file diff --git a/plugins/examples/painless-whitelist/src/test/resources/rest-api-spec/test/painless_whitelist/20_whitelist.yml b/plugins/examples/painless-whitelist/src/test/resources/rest-api-spec/test/painless_whitelist/20_whitelist.yml new file mode 100644 index 0000000000000..bbb0b44ef1d45 --- /dev/null +++ b/plugins/examples/painless-whitelist/src/test/resources/rest-api-spec/test/painless_whitelist/20_whitelist.yml @@ -0,0 +1,26 @@ +# Example test using whitelisted members and methods + +"Whitelisted custom class": + - do: + index: + index: test + type: test + id: 1 + body: { "num1": 1.0 } + - do: + indices.refresh: {} + + - do: + index: test + search: + body: + query: + match_all: {} + script_fields: + sNum1: + script: + source: "def e = new ExampleWhitelistedClass(6, 42); ExampleWhitelistedClass.staticMethod(); return e.publicMember + e.privateMemberAccessor + ExampleWhitelistedClass.CONSTANT + '2'.toInt()" + lang: painless + + - match: { hits.total: 1 } + - match: { hits.hits.0.fields.sNum1.0: 92 }