Skip to content

Commit 35bb255

Browse files
committed
update version doc
1 parent eb4857c commit 35bb255

File tree

2 files changed

+233
-13
lines changed

2 files changed

+233
-13
lines changed

README-source.adoc

Lines changed: 171 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,72 @@ include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=prec
185185
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=preciseSwitch]
186186
----
187187

188+
=== 安全策略
189+
190+
QLExpress4 默认采用隔离安全策略,不允许脚本访问 Java 对象的字段和方法,这确保了脚本执行的安全性。如果需要访问 Java 对象,可以通过不同的安全策略进行配置。
191+
192+
假设应用中有如下的 Java 类:
193+
194+
[source,java,indent=0]
195+
----
196+
include::./src/test/java/com/alibaba/qlexpress4/inport/MyDesk.java[]
197+
----
198+
199+
脚本执行的上下文设置如下:
200+
201+
[source,java,indent=0]
202+
----
203+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyContextSetup]
204+
----
205+
206+
QLExpress4 提供了四种安全策略:
207+
208+
==== 1. 隔离策略(默认)
209+
210+
默认情况下,QLExpress4 采用隔离策略,不允许访问任何字段和方法:
211+
212+
[source,java,indent=0]
213+
----
214+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyIsolation]
215+
----
216+
217+
==== 2. 黑名单策略
218+
219+
通过黑名单策略,可以禁止访问特定的字段或方法,其他字段和方法可以正常访问:
220+
221+
[source,java,indent=0]
222+
----
223+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyBlackList]
224+
----
225+
226+
==== 3. 白名单策略
227+
228+
通过白名单策略,只允许访问指定的字段或方法,其他字段和方法都会被禁止:
229+
230+
[source,java,indent=0]
231+
----
232+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyWhiteList]
233+
----
234+
235+
==== 4. 开放策略
236+
237+
开放策略允许访问所有字段和方法,类似于 QLExpress3 的行为,但需要注意安全风险:
238+
239+
[source,java,indent=0]
240+
----
241+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen]
242+
----
243+
244+
> 注意:开放策略虽然提供了最大的灵活性,但也带来了安全风险。建议只在受信任的环境中使用,不建议用于处理终端用户输入的脚本。
245+
246+
==== 策略建议
247+
248+
建议直接采用默认策略,在脚本中不要直接调用 Java 对象的字段和方法。而是通过自定义函数和操作符的方式(参考 link:#添加自定义函数与操作符[添加自定义函数与操作符]),对嵌入式脚本提供系统功能。这样能同时保证脚本的安全性和灵活性,用户体验还更好。
249+
250+
如果确实需要调用 Java 对象的字段和方法,至少应该使用白名单策略,只提供脚本有限的访问权限。
251+
252+
至于黑名单和开放策略,不建议在外部输入脚本的场景使用,除非确保每个脚本都会经过审核。
253+
188254
=== 调用应用中的 Java 类
189255

190256
> 需要放开安全策略,不建议用于终端用户输入
@@ -514,7 +580,7 @@ include::./src/test/java/com/alibaba/qlexpress4/inport/MyHome.java[]
514580

515581
可以通过下面的 QLExpress 脚本,便捷创建:
516582

517-
> 注意,该特性需要参考 link:#调用应用中的-java-类[调用应用中的Java类] 打开安全选项,才能正常执行。
583+
> 注意,该特性需要参考 link:#安全策略[安全策略] 打开安全选项,才能正常执行。
518584

519585
[source,java]
520586
----
@@ -609,6 +675,27 @@ b = 2
609675
return 1+1;
610676
----
611677

678+
因为分号可以省略,QLExpress4 对于换行的处理相比 3 或者 Java 语言更加严格。如果想要将多行表达式拆成多行,建议将操作符保留在当前行,而将右操作数换到下一行。
679+
680+
以下多行表达式会报语法错误(反例):
681+
682+
[source,java]
683+
----
684+
// syntax error
685+
a
686+
+ b
687+
----
688+
689+
以下是正确的换行示例(正例):
690+
691+
[source,java]
692+
----
693+
a +
694+
b
695+
----
696+
697+
其他的语法习惯保持和 Java 一致即可。
698+
612699
=== 表达式
613700

614701
QLExpress 采用表达式优先的设计,其中 除了 import, return 和循环等结构外,几乎都是表达式。
@@ -721,7 +808,7 @@ QLExpress 可以兼容 Java8 的常见语法。
721808

722809
可以直接使用 Java 集合中的 stream api 对集合进行操作。
723810

724-
因为此时的 stream api 都是来自 Java 中的方法,参考 link:#调用应用中的-java-类[调用应用中的Java类] 打开安全选项,以下脚本才能正常执行。
811+
因为此时的 stream api 都是来自 Java 中的方法,参考 link:#安全策略[安全策略] 打开安全选项,以下脚本才能正常执行。
725812

726813
[source,java]
727814
----
@@ -737,7 +824,86 @@ Java8 中引入了 Function, Consumer, Predicate 等函数式接口,QLExpress
737824
include::./src/test/resources/testsuite/java/lambda/java_functional_interface.ql[]
738825
----
739826

740-
== 附录一 如何贡献?
827+
== 附录一 升级指南
828+
829+
QLExpress 的上一版本因为多年的迭代停滞,在各项特性上和业界产生了较大差距。
830+
831+
QLExpress4 的目标之一就是一次性弥补这些差距,因此选择进行了大刀阔斧的升级,而有意放弃了部分兼容性。当然,基础的功能和体验还是和上一版本保持了对齐。
832+
833+
如果系统已经使用老版本的 QLExpress,升级之前务必要进行一次全面的回归测试,确保这些脚本都能在新版中正常执行,再进行升级。
834+
835+
如果没有时间或者方法对它们一一验证,那么不建议进行升级。
836+
837+
如果是新系统,建议直接采用 QLExpress4,未来 QLExpress4 的生态建设会越来越完善,而 3 会被逐渐抛弃。
838+
839+
下面将列表新版和旧版的主要不同,方便用户对已有脚本进行升级。如有遗漏,欢迎反馈:
840+
841+
=== 默认安全策略
842+
843+
如果完全使用默认选项,获取 Java 对象的字段(`o.field`),或者调用成员方法(`o.method()`),则会分别抛出 `FIELD_NOT_FOUND` 和 `METHOD_NOT_FOUND` 错误。
844+
845+
这是因为 3 可以没有限制地通过反射访问 Java 应用系统中的任意字段和方法,这在嵌入式脚本中被认为是不安全的。
846+
847+
如果想兼容 3 的行为,则在新建 `Express4Runner` 时, 要将安全策略设置为 “开放”,参考代码如下:
848+
849+
[source,java,indent=0]
850+
----
851+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=securityStrategyOpen]
852+
----
853+
854+
详细参考 link:#安全策略[安全策略] 章节。
855+
856+
=== 定义映射
857+
858+
QLExpress 老版本支持通过 `NewMap(key:value)` 的方式快速创建映射,虽然在文档中没有详细讨论,但是很多用户通过单元测试和询问的方式,知晓并使用了这个语法。
859+
860+
不过这种语法过于定制,也和业界的规范相差很大,因此在新版中将其移除。
861+
862+
新版原生支持 JSON 语法,直接采用 JSON 字典的格式(`\{key:value}`)即可快速创建映射,更加直观。
863+
864+
详细参考 link:#方便语法元素[方便语法元素]
865+
866+
=== 全局变量污染上下文
867+
868+
QLExpress 支持在执行脚本时传入一个全局的上下文,即 context 参数。
869+
870+
在老版本中,如果脚本中定义了全局变量,则这些变量也会写入到 context。在脚本执行结束后,可以通过 context 获取到脚本中定义的全局变量的值。
871+
872+
一个老版本的列子如下:
873+
874+
[source,java]
875+
----
876+
// only for QLExpress 3.x
877+
878+
String express = "a=3;a+1";
879+
ExpressRunner runner = new ExpressRunner(false, true);
880+
DefaultContext<String, Object> context = new DefaultContext<>();
881+
882+
Object res = runner.execute(express, context, null, true, true);
883+
// The result of the script execution should be 4 (a+1)
884+
Assert.assertEquals(4, res);
885+
// The variable 'a' defined in the script is also stored in the context
886+
Assert.assertEquals(3, context.get("a"));
887+
----
888+
889+
根据调研和反馈,我们认为这会导致全局上下文被脚本 “污染”,存在安全性问题。
890+
891+
因此在 QLExpress4 中,全局变量默认不会写入到 context 中。
892+
893+
如果想要兼容 3 的特性,需要将 `polluteUserContext` 选项设置为 `true`,参考代码如下:
894+
895+
[source,java,indent=0]
896+
----
897+
include::./src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java[tag=polluteUserContext]
898+
----
899+
900+
=== 分号可省略
901+
902+
“分号可省略” 已经是现代脚本语言的一个标配,QLExpress4 也跟进了这个特性,分号是可以省略的。
903+
904+
具体参考 link:#分号[分号] 章节。
905+
906+
== 附录二 如何贡献?
741907

742908
QLExpress 对社区的更改完全开放,任何建议和修改,都会受到欢迎,讨论后合理最后会被接纳到主干中。
743909

@@ -758,13 +924,13 @@ exit 0
758924

759925
这样在每次 git commit 之前,就会自动执行 maven 的 spotless 插件执行代码格式化,具体代码格式配置见 link:spotless_eclipse_formatter.xml[]
760926

761-
== 附录二 QLExpress4性能提升
927+
== 附录三 QLExpress4性能提升
762928

763929
link:https://www.yuque.com/xuanheng-ffjti/iunlps/pgfzw46zel2xfnie?singleDoc#%20%E3%80%8AQLExpress3%E4%B8%8E4%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94%E3%80%8B[QLExpress4与3性能对比]
764930

765931
总结:常见场景下,无编译缓存时,QLExpress4能比3有接近10倍性能提升;有编译缓存,也有一倍性能提升。
766932

767-
== 附录三 开发者联系方式
933+
== 附录四 开发者联系方式
768934

769935
* Email:
770936
** qinyuan.dqy@alibaba-inc.com

src/test/java/com/alibaba/qlexpress4/Express4RunnerTest.java

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.alibaba.qlexpress4.exception.QLRuntimeException;
99
import com.alibaba.qlexpress4.exception.QLSyntaxException;
1010
import com.alibaba.qlexpress4.exception.QLTimeoutException;
11+
import com.alibaba.qlexpress4.inport.MyDesk;
1112
import com.alibaba.qlexpress4.runtime.Value;
1213
import com.alibaba.qlexpress4.runtime.context.DynamicVariableContext;
1314
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
@@ -25,6 +26,7 @@
2526
import org.junit.Test;
2627

2728
import java.lang.reflect.InvocationTargetException;
29+
import java.lang.reflect.Member;
2830
import java.math.BigDecimal;
2931
import java.math.BigInteger;
3032
import java.util.ArrayList;
@@ -427,6 +429,56 @@ public void docPreciseTest() {
427429
// end::preciseSwitch[]
428430
}
429431

432+
@Test
433+
public void securityStrategyTest()
434+
throws NoSuchMethodException, SecurityException, NoSuchFieldException {
435+
// tag::securityStrategyContextSetup[]
436+
MyDesk desk = new MyDesk();
437+
desk.setBook1("Thinking in Java");
438+
desk.setBook2("Effective Java");
439+
Map<String, Object> context = Collections.singletonMap("desk", desk);
440+
// end::securityStrategyContextSetup[]
441+
442+
// tag::securityStrategyIsolation[]
443+
// default isolation strategy, no field or method can be found
444+
Express4Runner express4RunnerIsolation = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
445+
assertErrorCode(express4RunnerIsolation, context, "desk.book1", "FIELD_NOT_FOUND");
446+
assertErrorCode(express4RunnerIsolation, context, "desk.getBook2()", "METHOD_NOT_FOUND");
447+
// end::securityStrategyIsolation[]
448+
449+
// tag::securityStrategyBlackList[]
450+
// black list security strategy
451+
Set<Member> memberList = new HashSet<>();
452+
memberList.add(MyDesk.class.getMethod("getBook2"));
453+
Express4Runner express4RunnerBlackList = new Express4Runner(
454+
InitOptions.builder().securityStrategy(QLSecurityStrategy.blackList(memberList)).build());
455+
assertErrorCode(express4RunnerBlackList, context, "desk.book2", "FIELD_NOT_FOUND");
456+
Object resultBlack =
457+
express4RunnerBlackList.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult();
458+
Assert.assertEquals("Thinking in Java", resultBlack);
459+
// end::securityStrategyBlackList[]
460+
461+
// tag::securityStrategyWhiteList[]
462+
// white list security strategy
463+
Express4Runner express4RunnerWhiteList = new Express4Runner(
464+
InitOptions.builder().securityStrategy(QLSecurityStrategy.whiteList(memberList)).build());
465+
Object resultWhite =
466+
express4RunnerWhiteList.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult();
467+
Assert.assertEquals("Effective Java", resultWhite);
468+
assertErrorCode(express4RunnerWhiteList, context, "desk.getBook1()", "METHOD_NOT_FOUND");
469+
// end::securityStrategyWhiteList[]
470+
471+
// tag::securityStrategyOpen[]
472+
// open security strategy
473+
Express4Runner express4RunnerOpen =
474+
new Express4Runner(InitOptions.builder().securityStrategy(QLSecurityStrategy.open()).build());
475+
Assert.assertEquals("Thinking in Java",
476+
express4RunnerOpen.execute("desk.book1", context, QLOptions.DEFAULT_OPTIONS).getResult());
477+
Assert.assertEquals("Effective Java",
478+
express4RunnerOpen.execute("desk.getBook2()", context, QLOptions.DEFAULT_OPTIONS).getResult());
479+
// end::securityStrategyOpen[]
480+
}
481+
430482
@Test
431483
public void mapSetGetTest() {
432484
String script = "a = new HashMap<>();" + "a['aaa'] = 'bbb';" + "a";
@@ -526,6 +578,7 @@ public void debugExample() {
526578

527579
@Test
528580
public void populateTest() {
581+
// tag::polluteUserContext[]
529582
Express4Runner express4Runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS);
530583
QLOptions populateOption = QLOptions.builder().polluteUserContext(true).build();
531584
Map<String, Object> populatedMap = new HashMap<>();
@@ -535,14 +588,15 @@ public void populateTest() {
535588
assertEquals(11, populatedMap.get("b"));
536589

537590
// no population
538-
Map<String, Object> populatedMap2 = new HashMap<>();
539-
express4Runner.execute("a = 11", populatedMap2, QLOptions.DEFAULT_OPTIONS);
540-
assertFalse(populatedMap2.containsKey("a"));
541-
542-
Map<String, Object> populatedMap3 = new HashMap<>();
543-
populatedMap3.put("a", 10);
544-
assertEquals(19, express4Runner.execute("a = 19;a", populatedMap3, QLOptions.DEFAULT_OPTIONS).getResult());
545-
assertEquals(10, populatedMap3.get("a"));
591+
Map<String, Object> noPopulatedMap1 = new HashMap<>();
592+
express4Runner.execute("a = 11", noPopulatedMap1, QLOptions.DEFAULT_OPTIONS);
593+
assertFalse(noPopulatedMap1.containsKey("a"));
594+
595+
Map<String, Object> noPopulatedMap2 = new HashMap<>();
596+
noPopulatedMap2.put("a", 10);
597+
assertEquals(19, express4Runner.execute("a = 19;a", noPopulatedMap2, QLOptions.DEFAULT_OPTIONS).getResult());
598+
assertEquals(10, noPopulatedMap2.get("a"));
599+
// end::polluteUserContext[]
546600
}
547601

548602
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)