Groovy脚本沙盒:使用Java中的Groovy TimeCategory语法作为String

时间:2014-06-23 16:06:04

标签: java string grails groovy dsl

我有API的Java应用程序。 API允许使用灵活的日期语法:

PUT /monthly-jobs/job1 
{
    "startExpression": "now + 1.week + 4.days + 2.hours"
}

对于此任务,我想使用Groovy的TimeCategory类(http://groovycookbook.org/basic_types/dates_times/)。

在我看来,根据界面,应该有一个帮助 Java 类:

interface DateExpressionEvaluator {
    Date evaluateDateExpression(String expr);
}

对此有什么正确的解决方法?如何在这些要求中使用Groovy?

请注意我无法明确地将输入评估为groovy代码,因为API用户可能会插入groovy代码并破解服务器。

1 个答案:

答案 0 :(得分:1)

我必须使用groovy脚本沙盒(https://github.com/kohsuke/groovy-sandbox)创建一个解决方案:

测试用例:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.Date;

import org.apache.commons.lang3.time.FastDateFormat;
import org.junit.Test;

public class DateExpressionUtilsTest {

    @Test
    public void test_parse() {
        assertEquals(
                FastDateFormat.getInstance("yyyy-MM-dd hh:mm").format(
                        new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)),
                FastDateFormat.getInstance("yyyy-MM-dd hh:mm").format(DateExpressionUtils.eval("now + 1.week")));

        try {
            DateExpressionUtils.eval("now + 1.week; Thread.sleep(1000);");
            fail();
        } catch (SecurityException e) {
            // ok
        }
    }
}

DateExpressionUtils:

import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.util.Date;

import org.codehaus.groovy.control.CompilerConfiguration;
import org.kohsuke.groovy.sandbox.SandboxTransformer;

/**
 * Sandboxing: https://github.com/kohsuke/groovy-sandbox
 * http://groovy-sandbox.kohsuke.org/
 *
 */

public class DateExpressionUtils {
    public static Date eval(String expr) {
        try {
            CompilerConfiguration cc = new CompilerConfiguration();
            cc.addCompilationCustomizers(new SandboxTransformer());
            Binding binding = new Binding();
            binding.setProperty("now", new Date());
            GroovyShell sh = new GroovyShell(binding, cc);
            DateExpressionSandbox sandbox = new DateExpressionSandbox();
            sandbox.register();
            try {
                Object resObj = sh.evaluate("use(groovy.time.TimeCategory){" + expr + "}");
                Date res = (Date) resObj;
                return res;
            } finally {
                sandbox.unregister();
            }
        } catch (SecurityException e) {
            throw new SecurityException(String.format("Possible date expression sandbox jailbreak with '%s': '%s'.",
                    expr, e.getMessage()));
        } catch (Exception e) {
            throw new RuntimeException(String.format("Unable to evaluate date expression '%s': '%s'.", expr,
                    e.getMessage()));
        }
    }
}

DateExpressionSandbox:

import groovy.lang.Closure;
import groovy.lang.Script;
import groovy.time.Duration;
import groovy.time.TimeCategory;

import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.kohsuke.groovy.sandbox.GroovyValueFilter;

/**
 * Example sandbox: https://github.com/kohsuke/groovy-sandbox/blob/master/src/test/groovy/org/
 * kohsuke/groovy/sandbox/robot/RobotSandbox.groovy
 *
 */

public class DateExpressionSandbox extends GroovyValueFilter {
    @Override
    public Object filter(Object o) {
        if (o == null || ALLOWED_TYPES.contains(o.getClass()))
            return o;

        if (Class.class.equals(o.getClass()) && ALLOWED_STATIC_CLASSES.contains(o.toString())) {
            return o;
        }

        if (o instanceof Script || o instanceof Closure)
            return o; // access to properties of compiled groovy script

        throw new SecurityException(String.format("Unexpected type: '%s', '%s'.", o.getClass(), o));
    }

    private static final Set<Class<?>> ALLOWED_TYPES = new HashSet<Class<?>>(Arrays.asList(String.class, Integer.class,
            Long.class, Double.class, Boolean.class, Date.class, TimeCategory.class, Duration.class));

    private static final Set<String> ALLOWED_STATIC_CLASSES = new HashSet<>();
    static {
        for (Class<?> cl : ALLOWED_TYPES) {
            ALLOWED_STATIC_CLASSES.add("class " + cl.getCanonicalName());
        }
    }
}