Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package io.cdap.wrangler.api;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
Expand Down Expand Up @@ -48,33 +47,37 @@
* "json-parser" : "parse-as-json",
* "js-parser" : "parse-as-json"
* },
* "jexlAllowlist" : [
* {
* "className": "java.lang.Runtime",
* "methods": ["*"],
* "properties": ["*"]
* }
* ]
* "jexlConfiguration" : {
* "jexlAllowlistEnabled": true,
* "jexlAllowlist" : [
* {
* "className": "java.lang.Runtime",
* "methods": ["*"],
* "properties": ["*"]
* }
* ]
* }
* }
*/
@Deprecated
public final class DirectiveConfig {
public static final DirectiveConfig EMPTY = new DirectiveConfig(null, null, Collections.emptyList());
public static final DirectiveConfig EMPTY =
new DirectiveConfig(null, null, new JexlConfiguration(false, Collections.emptyList()));
public static final String EXCLUSIONS_KEY = "exclusions";
public static final String ALIASES_KEY = "aliases";
public static final String JEXL_ALLOWLIST_KEY = "jexlAllowlist";
public static final String JEXL_CONFIGURATION_KEY = "jexlConfiguration";

private final ImmutableSet<String> exclusions;
private final ImmutableMap<String, String> aliases;
@Nullable private final ImmutableList<JexlAllowlist> jexlAllowlist;
@Nullable private final JexlConfiguration jexlConfiguration;

public DirectiveConfig(
@Nullable Set<String> exclusions,
@Nullable Map<String, String> aliases,
@Nullable List<JexlAllowlist> jexlAllowlist) {
@Nullable JexlConfiguration jexlConfiguration) {
this.exclusions = exclusions != null ? ImmutableSet.copyOf(exclusions) : ImmutableSet.of();
this.aliases = aliases != null ? ImmutableMap.copyOf(aliases) : ImmutableMap.of();
this.jexlAllowlist = jexlAllowlist != null ? ImmutableList.copyOf(jexlAllowlist) : null;
this.jexlConfiguration = jexlConfiguration;
}

/**
Expand All @@ -96,13 +99,13 @@ public Map<String, String> getAliases() {
}

/**
* Gets the list of JEXL inclusions.
* Gets the JEXL configuration.
*
* @return the list of JEXL inclusions
* @return the JEXL configuration
*/
@Nullable
public List<JexlAllowlist> getJexlAllowlist() {
return jexlAllowlist;
public JexlConfiguration getJexlConfiguration() {
return jexlConfiguration;
}

/**
Expand Down Expand Up @@ -160,7 +163,7 @@ public JsonElement toJson() {
JsonObject object = new JsonObject();
object.add(EXCLUSIONS_KEY, gson.toJsonTree(exclusions));
object.add(ALIASES_KEY, gson.toJsonTree(aliases));
object.add(JEXL_ALLOWLIST_KEY, gson.toJsonTree(jexlAllowlist));
object.add(JEXL_CONFIGURATION_KEY, gson.toJsonTree(jexlConfiguration));
return object;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
Expand All @@ -35,7 +34,6 @@
public final class DirectiveConfigDeserializer implements JsonDeserializer<DirectiveConfig> {
private static final Type STRING_SET_TYPE = new TypeToken<Set<String>>() { }.getType();
private static final Type STRING_MAP_TYPE = new TypeToken<Map<String, String>>() { }.getType();
private static final Type JEXL_ALLOWLIST_LIST_TYPE = new TypeToken<List<JexlAllowlist>>() { }.getType();

@Override
public DirectiveConfig deserialize(JsonElement configJson, Type typeOfT, JsonDeserializationContext ctx)
Expand All @@ -45,7 +43,7 @@ public DirectiveConfig deserialize(JsonElement configJson, Type typeOfT, JsonDes
return new DirectiveConfig(
deserializeProperty(configJsonObj, DirectiveConfig.EXCLUSIONS_KEY, STRING_SET_TYPE, ctx),
deserializeProperty(configJsonObj, DirectiveConfig.ALIASES_KEY, STRING_MAP_TYPE, ctx),
deserializeProperty(configJsonObj, DirectiveConfig.JEXL_ALLOWLIST_KEY, JEXL_ALLOWLIST_LIST_TYPE, ctx));
deserializeProperty(configJsonObj, DirectiveConfig.JEXL_CONFIGURATION_KEY, JexlConfiguration.class, ctx));
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import java.lang.reflect.Type;
import java.util.List;
import javax.annotation.Nullable;

/**
* Custom GSON deserializer for {@link JexlAllowlist}.
Expand Down Expand Up @@ -55,13 +56,10 @@ public JexlAllowlist deserialize(JsonElement allowlistJson, Type typeOfT, JsonDe
}
}

@Nullable
private static <T> T deserializeProperty(
JsonObject obj, String key, Type type, T defaultValue, JsonDeserializationContext ctx) {
JsonObject obj, String key, Type type, @Nullable T defaultValue, JsonDeserializationContext ctx) {
JsonElement element = obj.get(key);
if (element == null || element.isJsonNull()) {
return defaultValue;
}
T result = ctx.deserialize(element, type);
return result != null ? result : defaultValue;
return (element != null && !element.isJsonNull()) ? ctx.deserialize(element, type) : defaultValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright © 2026 Cask Data, Inc.
*
* Licensed 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 io.cdap.wrangler.api;

import com.google.common.collect.ImmutableList;
import io.cdap.wrangler.api.annotations.PublicEvolving;

import java.util.List;
import javax.annotation.Nullable;

/**
* Defines configuration for JEXL allowlisting.
*/
@PublicEvolving
public final class JexlConfiguration {
public static final String JEXL_ALLOWLIST_ENABLED_KEY = "jexlAllowlistEnabled";
public static final String JEXL_ALLOWLIST_KEY = "jexlAllowlist";

private final boolean jexlAllowlistEnabled;
@Nullable
private final List<JexlAllowlist> jexlAllowlist;

public JexlConfiguration(boolean jexlAllowlistEnabled, @Nullable List<JexlAllowlist> jexlAllowlist) {
this.jexlAllowlistEnabled = jexlAllowlistEnabled;
this.jexlAllowlist = jexlAllowlist != null ? ImmutableList.copyOf(jexlAllowlist) : null;
}

public boolean isJexlAllowlistEnabled() {
return jexlAllowlistEnabled;
}

@Nullable
public List<JexlAllowlist> getJexlAllowlist() {
return jexlAllowlist;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright © 2026 Cask Data, Inc.
*
* Licensed 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 io.cdap.wrangler.api;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import io.cdap.wrangler.api.annotations.PublicEvolving;

import java.lang.reflect.Type;
import java.util.List;
import javax.annotation.Nullable;

/**
* Custom GSON deserializer for {@link JexlConfiguration}.
*/
@PublicEvolving
public final class JexlConfigurationDeserializer implements JsonDeserializer<JexlConfiguration> {
private static final Type JEXL_ALLOWLIST_LIST_TYPE = new TypeToken<List<JexlAllowlist>>() { }.getType();

@Override
public JexlConfiguration deserialize(JsonElement jexlConfigJson, Type typeOfT, JsonDeserializationContext ctx)
throws JsonParseException {
if (!jexlConfigJson.isJsonObject()) {
throw new JsonParseException("Expected jexlConfiguration to be a JSON object.");
}
JsonObject jexlConfigJsonObj = jexlConfigJson.getAsJsonObject();

return new JexlConfiguration(
deserializeProperty(jexlConfigJsonObj, JexlConfiguration.JEXL_ALLOWLIST_ENABLED_KEY, boolean.class, true, ctx),
deserializeProperty(
jexlConfigJsonObj, JexlConfiguration.JEXL_ALLOWLIST_KEY, JEXL_ALLOWLIST_LIST_TYPE, null, ctx));
}

@Nullable
private static <T> T deserializeProperty(
JsonObject obj, String key, Type type, @Nullable T defaultValue, JsonDeserializationContext ctx) {
JsonElement element = obj.get(key);
return (element != null && !element.isJsonNull()) ? ctx.deserialize(element, type) : defaultValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,24 @@ public class DirectiveConfigDeserializerTest {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(DirectiveConfig.class, new DirectiveConfigDeserializer())
.registerTypeAdapter(JexlAllowlist.class, new JexlAllowlistDeserializer())
.registerTypeAdapter(JexlConfiguration.class, new JexlConfigurationDeserializer())
.create();

@Test
public void testDirectiveConfigDeserialization() {
String json = "{\n"
+ " \"exclusions\": [\"drop\"],\n"
+ " \"aliases\": {\"p\": \"parse-as-json\"},\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"java.lang.String\",\n"
+ " \"methods\": [\"*\"],\n"
+ " \"properties\": [\"*\"]\n"
+ " }\n"
+ " ]\n"
+ " \"jexlConfiguration\": {\n"
+ " \"jexlAllowlistEnabled\": true,\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"java.lang.String\",\n"
+ " \"methods\": [\"*\"],\n"
+ " \"properties\": [\"*\"]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";

DirectiveConfig config = GSON.fromJson(json, DirectiveConfig.class);
Expand All @@ -55,10 +59,12 @@ public void testDirectiveConfigDeserialization() {
Assert.assertEquals("parse-as-json", config.getAliasName("p"));
Assert.assertEquals(Collections.singleton("drop"), config.getExclusions());
Assert.assertEquals(Collections.singletonMap("p", "parse-as-json"), config.getAliases());
Assert.assertNotNull(config.getJexlAllowlist());
Assert.assertEquals(1, config.getJexlAllowlist().size());
Assert.assertNotNull(config.getJexlConfiguration());
Assert.assertTrue(config.getJexlConfiguration().isJexlAllowlistEnabled());
Assert.assertNotNull(config.getJexlConfiguration().getJexlAllowlist());
Assert.assertEquals(1, config.getJexlConfiguration().getJexlAllowlist().size());

JexlAllowlist allowlist = config.getJexlAllowlist().get(0);
JexlAllowlist allowlist = config.getJexlConfiguration().getJexlAllowlist().get(0);
Assert.assertEquals("java.lang.String", allowlist.getClassName());
Assert.assertTrue(allowlist.allowAllMethods());
Assert.assertTrue(allowlist.allowAllProperties());
Expand All @@ -69,13 +75,16 @@ public void testDirectiveConfigDeserializationWithSpecificMethodsAndProperties()
String json = "{\n"
+ " \"exclusions\": [\"drop\"],\n"
+ " \"aliases\": {\"p\": \"parse-as-json\"},\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"java.lang.String\",\n"
+ " \"methods\": [\"trim\", \"substring\"],\n"
+ " \"properties\": [\"bytes\"]\n"
+ " }\n"
+ " ]\n"
+ " \"jexlConfiguration\": {\n"
+ " \"jexlAllowlistEnabled\": false,\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"java.lang.String\",\n"
+ " \"methods\": [\"trim\", \"substring\"],\n"
+ " \"properties\": [\"bytes\"]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";

DirectiveConfig config = GSON.fromJson(json, DirectiveConfig.class);
Expand All @@ -84,10 +93,12 @@ public void testDirectiveConfigDeserializationWithSpecificMethodsAndProperties()
Assert.assertEquals("parse-as-json", config.getAliasName("p"));
Assert.assertEquals(Collections.singleton("drop"), config.getExclusions());
Assert.assertEquals(Collections.singletonMap("p", "parse-as-json"), config.getAliases());
Assert.assertNotNull(config.getJexlAllowlist());
Assert.assertEquals(1, config.getJexlAllowlist().size());
Assert.assertNotNull(config.getJexlConfiguration());
Assert.assertFalse(config.getJexlConfiguration().isJexlAllowlistEnabled());
Assert.assertNotNull(config.getJexlConfiguration().getJexlAllowlist());
Assert.assertEquals(1, config.getJexlConfiguration().getJexlAllowlist().size());

JexlAllowlist allowlist = config.getJexlAllowlist().get(0);
JexlAllowlist allowlist = config.getJexlConfiguration().getJexlAllowlist().get(0);
Assert.assertEquals("java.lang.String", allowlist.getClassName());
Assert.assertEquals(Arrays.asList("trim", "substring"), allowlist.getMethods());
Assert.assertEquals(Arrays.asList("bytes"), allowlist.getProperties());
Expand All @@ -103,19 +114,21 @@ public void testDirectiveConfigDeserializationEmpty() {
Assert.assertNotNull(config);
Assert.assertTrue(config.getExclusions().isEmpty());
Assert.assertTrue(config.getAliases().isEmpty());
Assert.assertNull(config.getJexlAllowlist());
Assert.assertNull(config.getJexlConfiguration());
}

@Test(expected = JsonParseException.class)
public void testDeserializeInvalidJexlAllowlistEntry() {
String json = "{\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"123InvalidClass\",\n"
+ " \"methods\": [\"*\"],\n"
+ " \"properties\": [\"*\"]\n"
+ " }\n"
+ " ]\n"
+ " \"jexlConfiguration\": {\n"
+ " \"jexlAllowlist\": [\n"
+ " {\n"
+ " \"className\": \"123InvalidClass\",\n"
+ " \"methods\": [\"*\"],\n"
+ " \"properties\": [\"*\"]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
GSON.fromJson(json, DirectiveConfig.class);
}
Expand Down
Loading
Loading