From 3c85272561eb2d26b868be0a269eb71c2f59ef71 Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Thu, 16 Jul 2020 10:00:40 -0500 Subject: [PATCH] Scripting: Augment String with sha1 and sha256 (#59671) Only available in the ingest context for use in ingest pipelines. Digests are computed on the UTF-8 encoding of the String and are returned as hex strings. sha1() return hex strings of length 40, sha256() returns length 64 Fixes: #59647 --- .../painless/PainlessPlugin.java | 6 +++ .../painless/api/Augmentation.java | 14 ++++++ .../painless/spi/org.elasticsearch.ingest.txt | 25 ++++++++++ .../painless/AugmentationTests.java | 47 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.ingest.txt 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 1d09d21a1b310..358c4aa6376e6 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 @@ -48,6 +48,7 @@ import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.script.IngestScript; import org.elasticsearch.script.ScoreScript; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptEngine; @@ -90,6 +91,11 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens scoreFn.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.score.txt")); map.put(ScoreScript.CONTEXT, scoreFn); + // Functions available to ingest pipelines + List ingest = new ArrayList<>(Whitelist.BASE_WHITELISTS); + ingest.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.ingest.txt")); + map.put(IngestScript.CONTEXT, ingest); + whitelists = map; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java index d0745dc982c36..74e14571aa35f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/api/Augmentation.java @@ -19,6 +19,8 @@ package org.elasticsearch.painless.api; +import org.elasticsearch.common.hash.MessageDigests; + import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; @@ -665,4 +667,16 @@ private static Object handleMissing(Object obj, String[] elements, int i, Suppli throw new IllegalArgumentException( String.format(Locale.ROOT, format, obj.getClass().getName(), elements[i], i, String.join(".", elements))); } + + public static String sha1(String source) { + return MessageDigests.toHexString( + MessageDigests.sha1().digest(source.getBytes(StandardCharsets.UTF_8)) + ); + } + + public static String sha256(String source) { + return MessageDigests.toHexString( + MessageDigests.sha256().digest(source.getBytes(StandardCharsets.UTF_8)) + ); + } } diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.ingest.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.ingest.txt new file mode 100644 index 0000000000000..3d50b10f32ade --- /dev/null +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.ingest.txt @@ -0,0 +1,25 @@ +# +# 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 the ingest scripts + +class java.lang.String { + String org.elasticsearch.painless.api.Augmentation sha1() + String org.elasticsearch.painless.api.Augmentation sha256() +} diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java index e462997444165..8fe095beeb33a 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java @@ -19,14 +19,41 @@ package org.elasticsearch.painless; +import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.ScriptContext; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Pattern; public class AugmentationTests extends ScriptTestCase { + @Override + protected Map, List> scriptContexts() { + Map, List> contexts = super.scriptContexts(); + List digestWhitelist = new ArrayList<>(Whitelist.BASE_WHITELISTS); + digestWhitelist.add(WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.ingest.txt")); + contexts.put(DigestTestScript.CONTEXT, digestWhitelist); + + return contexts; + } + + public abstract static class DigestTestScript { + public static final String[] PARAMETERS = {}; + public abstract String execute(); + public interface Factory { + DigestTestScript newInstance(); + } + + public static final ScriptContext CONTEXT = + new ScriptContext<>("test", DigestTestScript.Factory.class); + } + public void testStatic() { assertEquals(1, exec("ArrayList l = new ArrayList(); l.add(1); return l.getLength();")); assertEquals(1, exec("ArrayList l = new ArrayList(); l.add(1); return l.length;")); @@ -238,4 +265,24 @@ public void testString_SplitOnToken() { ); } } + + public String execDigest(String script) { + return scriptEngine.compile( + "digest_test", + script, + DigestTestScript.CONTEXT, Collections.emptyMap() + ).newInstance().execute(); + } + + public void testSha1() { + assertEquals("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33", execDigest("'foo'.sha1()")); + assertEquals("62cdb7020ff920e5aa642c3d4066950dd1f01f4d", execDigest("'bar'.sha1()")); + assertEquals("5f5513f8822fdbe5145af33b64d8d970dcf95c6e", execDigest("'foobarbaz'.sha1()")); + } + + public void testSha256() { + assertEquals("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", execDigest("'foo'.sha256()")); + assertEquals("fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9", execDigest("'bar'.sha256()")); + assertEquals("97df3588b5a3f24babc3851b372f0ba71a9dcdded43b14b9d06961bfc1707d9d", execDigest("'foobarbaz'.sha256()")); + } }