View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.introspection;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  import static org.junit.jupiter.api.Assertions.fail;
24  
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Iterator;
28  import java.util.LinkedHashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.commons.jexl3.JexlArithmetic;
33  import org.apache.commons.jexl3.JexlBuilder;
34  import org.apache.commons.jexl3.JexlContext;
35  import org.apache.commons.jexl3.JexlEngine;
36  import org.apache.commons.jexl3.JexlException;
37  import org.apache.commons.jexl3.JexlExpression;
38  import org.apache.commons.jexl3.JexlScript;
39  import org.apache.commons.jexl3.JexlTestCase;
40  import org.apache.commons.jexl3.MapContext;
41  import org.apache.commons.jexl3.annotations.NoJexl;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.junit.jupiter.api.Test;
45  
46  /**
47   * Tests sandbox features.
48   */
49  @SuppressWarnings({ "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes" })
50  class SandboxTest extends JexlTestCase {
51      public abstract static class AbstractCallMeNot {
52          public @NoJexl String NONO = "should not be accessible!";
53  
54          public String allowInherit() {
55              return "this is allowed";
56          }
57  
58          @NoJexl
59          public void callMeNot() {
60              fail("should not be callable!");
61          }
62      }
63  
64      public static class Arithmetic350 extends JexlArithmetic {
65          // cheat and keep the map builder around
66          MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
67  
68          public Arithmetic350(final boolean astrict) {
69              super(astrict);
70          }
71  
72          Map<?, ?> getLastMap() {
73              return (Map<Object, Object>) mb.create();
74          }
75  
76          @Override
77          public MapBuilder mapBuilder(final int size, final boolean extended) {
78              return mb;
79          }
80      }
81  
82      @NoJexl
83      public interface CantCallMe {
84          void tryMe();
85      }
86  
87      public static class CantSeeMe {
88          public boolean doIt() {
89              return false;
90          }
91      }
92  
93      public static class Foo extends AbstractCallMeNot implements CantCallMe, TryCallMe {
94          String name;
95          public String alias;
96  
97          public Foo(final String name) {
98              this.name = name;
99              this.alias = name + "-alias";
100         }
101 
102         public @NoJexl Foo(final String name, final String notcallable) {
103             throw new UnsupportedOperationException("should not be callable.");
104         }
105 
106         @NoJexl
107         public String cantCallMe() {
108             throw new UnsupportedOperationException("should not be callable.");
109         }
110 
111         public int doIt() {
112             return 42;
113         }
114 
115         public String getName() {
116             return name;
117         }
118 
119         public String Quux() {
120             return name + "-quux";
121         }
122 
123         public void setName(final String name) {
124             this.name = name;
125         }
126 
127         @Override
128         public void tryMe() {
129             throw new UnsupportedOperationException("should not be callable.");
130         }
131 
132         @Override
133         public void tryMeARiver() {
134             throw new UnsupportedOperationException("should not be callable.");
135         }
136     }
137 
138     public static class Foo386 implements SomeInterface {
139         @Override
140         public int bar() {
141             return 42;
142         }
143     }
144 
145     public static class Foo42 {
146         public int getFoo() {
147             return 42;
148         }
149     }
150 
151     public static class Foo43 extends Foo42 {
152         @Override
153         @NoJexl
154         public int getFoo() {
155             return 43;
156         }
157     }
158 
159     public static class Foo44 extends Foo43 {
160         @Override
161         public int getFoo() {
162             return 44;
163         }
164     }
165 
166     public abstract static class Operation {
167         protected final int base;
168 
169         public Operation(final int sz) {
170             base = sz;
171         }
172 
173         public abstract int nonCallable(int y);
174 
175         public abstract int someOp(int x);
176     }
177 
178     public static class Operation2 extends Operation {
179         public Operation2(final int sz) {
180             super(sz);
181         }
182 
183         @Override
184         public int nonCallable(final int y) {
185             throw new UnsupportedOperationException("do NOT call");
186         }
187 
188         @Override
189         public int someOp(final int x) {
190             return base + x;
191         }
192     }
193 
194     public static class Quux386 extends Foo386 {
195         @Override
196         public int bar() {
197             return -42;
198         }
199     }
200 
201     public interface SomeInterface {
202         int bar();
203     }
204 
205     public interface TryCallMe {
206         @NoJexl
207         void tryMeARiver();
208     }
209 
210     /** A simple iterable used to verify sandboxed iteration (f006). */
211     public static class Iterable386 implements Iterable<Integer> {
212         private final List<Integer> values = Arrays.asList(1, 2, 3);
213         @Override
214         public Iterator<Integer> iterator() {
215             return values.iterator();
216         }
217     }
218 
219     static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName());
220 
221     public SandboxTest() {
222         super("SandboxTest");
223     }
224 
225     @Test
226     void testIteratorBlock() {
227         // f006: the sandbox must gate iteration; blocking the "iterator" method denies foreach
228         final String src = "var sum = 0; for (var i : it) { sum += i; } sum";
229         final Iterable386 it = new Iterable386();
230 
231         // allow-by-default sandbox: iteration works and sums 1 + 2 + 3
232         final JexlSandbox open = new JexlSandbox();
233         final JexlEngine ojexl = new JexlBuilder().sandbox(open).strict(true).safe(false).create();
234         assertEquals(6, ojexl.createScript(src, "it").execute(null, it));
235 
236         // block the iterator method: getIterator returns null and the loop body never runs
237         final JexlSandbox sandbox = new JexlSandbox();
238         sandbox.block(Iterable386.class.getName()).execute("iterator");
239         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
240         assertEquals(0, sjexl.createScript(src, "it").execute(null, it));
241     }
242 
243     @Test
244     void testCantSeeMe() {
245         final JexlContext jc = new MapContext();
246         final String expr = "foo.doIt()";
247         JexlScript script;
248         Object result;
249 
250         final JexlSandbox sandbox = new JexlSandbox(false);
251         sandbox.allow(Foo.class.getName());
252         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
253 
254         jc.set("foo", new CantSeeMe());
255         script = sjexl.createScript(expr);
256         assertThrows(JexlException.class, () -> script.execute(jc));
257 
258         jc.set("foo", new Foo("42"));
259         result = script.execute(jc);
260         assertEquals(42, ((Integer) result).intValue());
261     }
262 
263     @Test
264     void testCtorAllow() {
265         final String expr = "new('" + Foo.class.getName() + "', '42')";
266         JexlScript script;
267         Object result;
268 
269         final JexlSandbox sandbox = new JexlSandbox();
270         sandbox.allow(Foo.class.getName()).execute("");
271         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
272 
273         script = sjexl.createScript(expr);
274         result = script.execute(null);
275         assertEquals("42", ((Foo) result).getName());
276     }
277 
278     @Test
279     void testCtorBlock() {
280         final String expr = "new('" + Foo.class.getName() + "', '42')";
281         final JexlScript script = JEXL.createScript(expr);
282         Object result;
283         result = script.execute(null);
284         assertEquals("42", ((Foo) result).getName());
285 
286         final JexlSandbox sandbox = new JexlSandbox();
287         sandbox.block(Foo.class.getName()).execute("");
288         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
289 
290         final JexlScript script2 = sjexl.createScript(expr);
291         assertThrows(JexlException.Method.class, () -> script2.execute(null), "ctor should not be accessible");
292 
293     }
294 
295     @Test
296     void testGetAllow() {
297         final Foo foo = new Foo("42");
298         final String expr = "foo.alias";
299         JexlScript script;
300         Object result;
301 
302         final JexlSandbox sandbox = new JexlSandbox();
303         sandbox.allow(Foo.class.getName()).read("alias");
304         sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS");
305         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
306 
307         script = sjexl.createScript(expr, "foo");
308         result = script.execute(null, foo);
309         assertEquals(foo.alias, result);
310 
311         script = sjexl.createScript("foo.ALIAS", "foo");
312         result = script.execute(null, foo);
313         assertEquals(foo.alias, result);
314     }
315 
316     @Test
317     void testGetBlock() {
318         final String expr = "foo.alias";
319         final JexlScript script = JEXL.createScript(expr, "foo");
320         final Foo foo = new Foo("42");
321         Object result;
322         result = script.execute(null, foo);
323         assertEquals(foo.alias, result);
324 
325         final JexlSandbox sandbox = new JexlSandbox();
326         sandbox.block(Foo.class.getName()).read("alias");
327         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
328 
329         final JexlScript script2 = sjexl.createScript(expr, "foo");
330         assertThrows(JexlException.Property.class, () -> script2.execute(null, foo), "alias should not be accessible");
331     }
332 
333     @Test
334     void testGetNullKeyAllowed0() {
335         final JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
336         final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
337         final Object o = expression.evaluate(null);
338         assertEquals("foo", o);
339     }
340 
341     @Test
342     void testGetNullKeyAllowed1() {
343         final JexlSandbox sandbox = new JexlSandbox(true, true);
344         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
345         p.read().add("quux");
346         final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
347         // cant read quux
348         final String q = "'quux'"; // quotes are important!
349         final JexlExpression expression = jexl.createExpression("{" + q + " : 'foo'}[" + q + "]");
350         assertTrue(assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
351 
352         // can read foo, null
353         for (final String k : Arrays.asList("'foo'", "null")) {
354             final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
355             final Object o = expression2.evaluate(null);
356             assertEquals("foo", o);
357         }
358     }
359 
360     @Test
361     void testGetNullKeyBlocked() {
362         final JexlSandbox sandbox = new JexlSandbox(true, true);
363         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
364         p.read().add(null);
365         p.read().add("quux");
366         // can read bar
367         final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
368         final JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
369         final Object r0 = e0.evaluate(null);
370         assertEquals("foo", r0);
371         // cannot read quux, null
372         for (final String k : Arrays.asList("'quux'", "null")) {
373             final JexlExpression expression = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
374             assertTrue(
375                     assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + k).getMessage().contains("undefined"));
376         }
377     }
378 
379     @Test
380     void testInheritedPermission0() {
381         final Foo386 foo = new Foo386();
382         final JexlSandbox sandbox = new JexlSandbox(false, true);
383         sandbox.permissions(SomeInterface.class.getName(), true, true, true, true);
384         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
385         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
386         assertEquals(42, (int) someOp.execute(null, foo));
387     }
388 
389     @Test
390     void testInheritedPermission1() {
391         final Quux386 foo = new Quux386();
392         final JexlSandbox sandbox = new JexlSandbox(false, true);
393         sandbox.permissions(Foo386.class.getName(), true, true, true, true);
394         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
395         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
396         assertEquals(-42, (int) someOp.execute(null, foo));
397     }
398 
399     @Test
400     void testMethodAllow() {
401         final Foo foo = new Foo("42");
402         final String expr = "foo.Quux()";
403         JexlScript script;
404         Object result;
405 
406         final JexlSandbox sandbox = new JexlSandbox();
407         sandbox.allow(Foo.class.getName()).execute("Quux");
408         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
409 
410         script = sjexl.createScript(expr, "foo");
411         result = script.execute(null, foo);
412         assertEquals(foo.Quux(), result);
413     }
414 
415     @Test
416     void testMethodBlock() {
417         final String expr = "foo.Quux()";
418         final JexlScript script = JEXL.createScript(expr, "foo");
419         final Foo foo = new Foo("42");
420         Object result;
421         result = script.execute(null, foo);
422         assertEquals(foo.Quux(), result);
423 
424         final JexlSandbox sandbox = new JexlSandbox();
425         sandbox.block(Foo.class.getName()).execute("Quux");
426         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
427 
428         final JexlScript script2 = sjexl.createScript(expr, "foo");
429         assertThrows(JexlException.Method.class, () -> script2.execute(null, foo), "Quux should not be accessible");
430     }
431 
432     @Test
433     void testMethodNoJexl() {
434         final Foo foo = new Foo("42");
435         // @formatter:off
436         final String[] exprs = {
437                 "foo.cantCallMe()",
438                 "foo.tryMe()",
439                 "foo.tryMeARiver()",
440                 "foo.callMeNot()",
441                 "foo.NONO",
442                 "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')" };
443         // @formatter:on
444         final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create();
445         for (final String expr : exprs) {
446             final JexlScript script = sjexl.createScript(expr, "foo");
447             assertThrows(JexlException.class, () -> script.execute(null, foo), "should have not been possible");
448         }
449     }
450 
451     @Test
452     void testNoJexl312() {
453         final JexlContext ctxt = new MapContext();
454         final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create();
455         final JexlScript foo = sjexl.createScript("x.getFoo()", "x");
456         assertThrows(JexlException.class, () -> foo.execute(ctxt, new Foo44()));
457     }
458 
459     @Test
460     void testNonInheritedPermission0() {
461         final Foo386 foo = new Foo386();
462         final JexlSandbox sandbox = new JexlSandbox(false, true);
463         sandbox.permissions(SomeInterface.class.getName(), false, true, true, true);
464         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
465         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
466         assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
467     }
468 
469     @Test
470     void testNonInheritedPermission1() {
471         final Quux386 foo = new Quux386();
472         final JexlSandbox sandbox = new JexlSandbox(false, true);
473         sandbox.permissions(Foo386.class.getName(), false, true, true, true);
474         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
475         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
476         assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
477     }
478 
479     @Test
480     void testRestrict() {
481         final JexlContext context = new MapContext();
482         context.set("System", System.class);
483         final JexlSandbox sandbox = new JexlSandbox();
484         // only allow call to currentTimeMillis (avoid exit, gc, loadLibrary, etc.)
485         sandbox.allow(System.class.getName()).execute("currentTimeMillis");
486         // cannot create a new file
487         sandbox.block(java.io.File.class.getName()).execute("");
488         final JexlEngine sjexl = new JexlBuilder().permissions(JexlPermissions.UNRESTRICTED).sandbox(sandbox).safe(false).strict(true).create();
489         Object result;
490         final JexlScript script1 = sjexl.createScript("System.exit()");
491         assertThrows(JexlException.class, () -> script1.execute(context), "should not allow calling exit.");
492         final JexlScript script2 = sjexl.createScript("System.exit(1)");
493         assertThrows(JexlException.class, () -> script2.execute(context), "should not allow calling exit.");
494         final JexlScript script3 = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
495         assertThrows(JexlException.class, () -> script3.execute(context), "should not allow creating a file");
496         final JexlScript script4 = sjexl.createScript("System.currentTimeMillis()");
497         result = script4.execute(context);
498         assertNotNull(result);
499     }
500 
501     @Test
502     void testSandboxInherit0() {
503         Object result;
504         final JexlContext ctxt = null;
505         final List<String> foo = new ArrayList<>();
506         final JexlSandbox sandbox = new JexlSandbox(false, true);
507         sandbox.allow(List.class.getName());
508 
509         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
510         final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y");
511         final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y");
512         final JexlScript get = sjexl.createScript("foo[x]", "foo", "x");
513 
514         result = method.execute(ctxt, foo, "nothing");
515         assertTrue((boolean) result);
516         result = get.execute(null, foo, Integer.valueOf(0));
517         assertEquals("nothing", result);
518         result = set.execute(null, foo, Integer.valueOf(0), "42");
519         assertEquals("42", result);
520         result = get.execute(null, foo, Integer.valueOf(0));
521         assertEquals("42", result);
522     }
523 
524     @Test
525     void testSandboxInherit1() {
526         Object result;
527         final JexlContext ctxt = null;
528         final Operation2 foo = new Operation2(12);
529         final JexlSandbox sandbox = new JexlSandbox(false, true);
530         sandbox.allow(Operation.class.getName());
531         sandbox.block(Operation.class.getName()).execute("nonCallable");
532         // sandbox.block(Foo.class.getName()).execute();
533         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
534         final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y");
535         result = someOp.execute(ctxt, foo, Integer.valueOf(30));
536         assertEquals(42, (int) result);
537         final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y");
538         assertThrows(JexlException.class, () -> nonCallable.execute(null, foo, Integer.valueOf(0)));
539     }
540 
541     @Test
542     void testSetAllow() {
543         final Foo foo = new Foo("42");
544         final String expr = "foo.alias = $0";
545         JexlScript script;
546         Object result;
547 
548         final JexlSandbox sandbox = new JexlSandbox();
549         sandbox.allow(Foo.class.getName()).write("alias");
550         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
551 
552         script = sjexl.createScript(expr, "foo", "$0");
553         result = script.execute(null, foo, "43");
554         assertEquals("43", result);
555         assertEquals("43", foo.alias);
556     }
557 
558     @Test
559     void testSetBlock() {
560         final String expr = "foo.alias = $0";
561         final JexlScript script1 = JEXL.createScript(expr, "foo", "$0");
562         final Foo foo = new Foo("42");
563         Object result;
564         result = script1.execute(null, foo, "43");
565         assertEquals("43", result);
566 
567         final JexlSandbox sandbox = new JexlSandbox();
568         sandbox.block(Foo.class.getName()).write("alias");
569         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
570 
571         final JexlScript script2 = sjexl.createScript(expr, "foo", "$0");
572         assertThrows(JexlException.class, () -> script2.execute(null, foo, "43"), "alias should not be accessible");
573 
574     }
575 
576     @Test
577     void testSetNullKeyAllowed0() {
578         final Arithmetic350 a350 = new Arithmetic350(true);
579         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
580         final JexlContext jc = new MapContext();
581         final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
582         expression.evaluate(jc);
583         final Map<?, ?> map = a350.getLastMap();
584         assertEquals("bar", map.get(null));
585     }
586 
587     @Test
588     void testSetNullKeyAllowed1() {
589         final Arithmetic350 a350 = new Arithmetic350(true);
590         final JexlSandbox sandbox = new JexlSandbox(true, true);
591         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
592         p.write().add("quux");
593         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
594         // cannot write quux
595         final String q = "'quux'"; // quotes are important!
596         final JexlExpression expression1 = jexl.createExpression("{" + q + " : 'foo'}[" + q + "] = '42'");
597         assertTrue(assertThrows(JexlException.Property.class, () -> expression1.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
598         // can write bar, null
599         JexlExpression expression2 = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
600         expression2.evaluate(null);
601         Map<?, ?> map = a350.getLastMap();
602         assertEquals("42", map.get("bar"));
603         map.clear();
604         expression2 = jexl.createExpression("{null : 'foo'}[null] = '42'");
605         expression2.evaluate(null);
606         map = a350.getLastMap();
607         assertEquals("42", map.get(null));
608     }
609 
610     @Test
611     void testSetNullKeyBlocked() {
612         final Arithmetic350 a350 = new Arithmetic350(true);
613         final JexlSandbox sandbox = new JexlSandbox(true, true);
614         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
615         p.write().add(null);
616         p.write().add("quux");
617         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
618         // can write bar
619         final JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
620         expression.evaluate(null);
621         final Map<?, ?> map = a350.getLastMap();
622         assertEquals("42", map.get("bar"));
623         // cannot write quux, null
624         for (final String k : Arrays.asList("'quux'", "null")) {
625             final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "] = '42'");
626             assertTrue(assertThrows(JexlException.Property.class, () -> expression2.evaluate(null), "should have blocked " + k).getMessage()
627                     .contains("undefined"));
628         }
629     }
630     interface I{}
631     static class A implements I{}
632     static class B extends A{}
633 
634     @Test
635     void testPermissionOrder() {
636         // permissions should not be dependent on order of evaluation
637         final JexlSandbox sandboxAB = new JexlSandbox(false, true);
638         sandboxAB.permissions(I.class.getName(), true, true, true, false);
639         assertEquals("allow{all}", sandboxAB.get(A.class.getName()).write().toString());
640         assertEquals("allow{all}", sandboxAB.get(B.class.getName()).write().toString());
641         final JexlSandbox sandboxBA = new JexlSandbox(false, true);
642         sandboxBA.permissions(I.class.getName(), true, true, true, false);
643         assertEquals("allow{all}", sandboxBA.get(B.class.getName()).write().toString());
644         assertEquals("allow{all}", sandboxBA.get(A.class.getName()).write().toString());
645     }
646 
647     @Test
648     void testIssue424() {
649         final JexlSandbox sandbox = new JexlSandbox(false, true);
650         sandbox.permissions(Map.class.getName(), true, true, true, true);
651         final String jexlCode = "x.foo = 'bar'";
652         final JexlEngine engine = new JexlBuilder()
653                     .sandbox(sandbox)
654                     .safe(false)
655                     .strict(true).create();
656         final JexlContext context = new MapContext();
657         final Map<String, Object> x = new LinkedHashMap<>();
658         context.set("x",  x);
659         final Object result = engine.createScript(jexlCode).execute(context);
660         assertEquals("bar", result);
661         assertEquals("bar", x.get("foo"));
662     }
663 
664 }