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.internal.introspection;
18  
19  import static org.apache.commons.jexl3.introspection.JexlPermissions.RESTRICTED;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.io.FileWriter;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.Field;
31  import java.lang.reflect.Method;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.HashMap;
35  import java.util.LinkedList;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  import org.apache.commons.jexl3.JexlArithmetic;
41  import org.apache.commons.jexl3.JexlBuilder;
42  import org.apache.commons.jexl3.JexlContext;
43  import org.apache.commons.jexl3.JexlEngine;
44  import org.apache.commons.jexl3.JexlException;
45  import org.apache.commons.jexl3.JexlScript;
46  import org.apache.commons.jexl3.JexlTestCase;
47  import org.apache.commons.jexl3.MapContext;
48  import org.apache.commons.jexl3.internal.introspection.nojexlpackage.Invisible;
49  import org.apache.commons.jexl3.introspection.JexlPermissions;
50  import org.apache.commons.jexl3.introspection.JexlUberspect;
51  import org.example.Pair;
52  import org.junit.jupiter.api.Test;
53  
54  /**
55   * Checks the CacheMap.MethodKey implementation
56   */
57  @SuppressWarnings({"unused","empty"})
58  class PermissionsTest {
59  
60      public static class A {
61          public int i;
62          public A() {}
63          public int method() { return 0; }
64      }
65  
66      public static class A0 extends A implements InterNoJexl0 {
67          /*@NoJexl*/ public int i0;
68          /*@NoJexl*/ public A0() {}
69          @Override public int method() { return 1; }
70      }
71  
72      public static class A1 extends A implements InterNoJexl1 {
73          private int i1;
74          /*@NoJexl*/ public A1() {}
75          @Override public int method() { return 2; }
76      }
77  
78      //@NoJexl
79      public static class A2 extends A  {
80          public A2() {}
81          @Override public int method() { return 3; }
82      }
83  
84      protected static class A3 {
85          protected int i3;
86          protected A3() {}
87          int method() { return 4; }
88      }
89  
90      public static class A5 implements InterNoJexl5 {
91          public A5() {}
92          @Override public int method() { return 0; }
93      }
94  
95      protected static class Foo2 {
96          protected String protectedMethod() {
97              return "foo2";
98          }
99          public String publicMethod() {
100             return "foo2";
101         }
102     }
103 
104     public static class Foo3 extends Foo2 {
105         @Override public String protectedMethod() {
106             return "foo3";
107         }
108         @Override public String publicMethod() {
109             return "foo3";
110         }
111     }
112 
113     public static class I33Arithmetic extends JexlArithmetic {
114         public I33Arithmetic(final boolean astrict) {
115             super(astrict);
116         }
117 
118         /**
119          * Same name signature as default private method.
120          * @param s The string
121          * @return A double, NaN if fail
122          */
123         public double parseDouble(final String s) {
124             try {
125                 return Double.parseDouble(s);
126             } catch (final NumberFormatException nfe) {
127                 return Double.NaN;
128             }
129         }
130     }
131 
132     //@NoJexl
133     public interface InterNoJexl0 {
134         int method();
135     }
136 
137     public interface InterNoJexl1 {
138         //@NoJexl
139         int method();
140     }
141 
142     //@NoJexl
143     public interface InterNoJexl5 {
144         int method();
145     }
146 
147     public static class Outer {
148         public static class Inner {
149             public void callMeNot() {}
150         }
151     }
152 
153     static Method getMethod(final Class<?> clazz, final String method) {
154         return Arrays.stream(clazz.getMethods()).filter(mth->mth.getName().equals(method)).findFirst().get();
155     }
156 
157     JexlPermissions permissions0() {
158         final String src = " org.apache.commons.jexl3.internal.introspection { PermissionsTest { " +
159                 "InterNoJexl0 { } " +
160                 "InterNoJexl1 { method(); } " +
161                 "A0 { A0(); i0; } " +
162                 "A1 { A1(); } " +
163                 "A2 { } " +
164                 "InterNoJexl5 { } " +
165                 "} }";
166         return JexlPermissions.parse(src);
167     }
168 
169     private void runTestPermissions(final JexlPermissions p) throws Exception {
170         assertFalse(p.allow((Field) null));
171         assertFalse(p.allow((Package) null));
172         assertFalse(p.allow((Method) null));
173         assertFalse(p.allow((Constructor<?>) null));
174         assertFalse(p.allow((Class<?>) null));
175 
176         assertFalse(p.allow(A2.class));
177         assertTrue(p.allow(A3.class));
178         assertTrue(p.allow(A5.class));
179 
180         final Method mA = A.class.getMethod("method");
181         assertNotNull(mA);
182         final Method mA0 = A0.class.getMethod("method");
183         assertNotNull(mA0);
184         final Method mA1 = A1.class.getMethod("method");
185         assertNotNull(mA1);
186         final Method mA2 = A2.class.getMethod("method");
187         assertNotNull(mA2);
188         final Method mA3 = A2.class.getDeclaredMethod("method");
189         assertNotNull(mA3);
190 
191         assertTrue(p.allow(mA));
192         assertFalse(p.allow(mA0));
193         assertFalse(p.allow(mA1));
194         assertFalse(p.allow(mA2));
195         assertFalse(p.allow(mA3));
196 
197         final Field fA = A.class.getField("i");
198         assertNotNull(fA);
199         assertTrue(p.allow(fA));
200 
201         final Field fA0 = A0.class.getField("i0");
202         assertNotNull(fA0);
203         assertFalse(p.allow(fA0));
204         assertFalse(p.allow(A0.class, fA0));
205         final Field fA1 = A1.class.getDeclaredField("i1");
206         assertNotNull(fA1);
207         assertFalse(p.allow(fA0));
208 
209         final Constructor<?> cA = A.class.getConstructor();
210         assertNotNull(cA);
211         assertTrue(p.allow(cA));
212 
213         final Constructor<?> cA0 = A0.class.getConstructor();
214         assertNotNull(cA0);
215         assertFalse(p.allow(cA0));
216 
217         final Constructor<?> cA3 = A3.class.getDeclaredConstructor();
218         assertNotNull(cA3);
219         assertFalse(p.allow(cA3));
220     }
221 
222     @Test
223     void testGetPackageName() {
224         final String PKG = "org.apache.commons.jexl3.internal.introspection";
225         String pkg = ClassTool.getPackageName(Outer.class);
226         assertEquals(PKG, pkg);
227         pkg = ClassTool.getPackageName(Outer.Inner.class);
228         assertEquals(PKG, pkg);
229         final Outer[] oo = {};
230         pkg = ClassTool.getPackageName(oo.getClass());
231         assertEquals(PKG, pkg);
232         final Outer.Inner[] ii = {};
233         pkg = ClassTool.getPackageName(ii.getClass());
234         assertEquals(PKG, pkg);
235         pkg = ClassTool.getPackageName(Process.class);
236         assertEquals("java.lang", pkg);
237         pkg = ClassTool.getPackageName(Integer.TYPE);
238         assertEquals("java.lang", pkg);
239     }
240 
241     @Test
242     void testParsePermissions0a() {
243         final String src = "java.lang { Runtime { exit(); exec(); } }\njava.net { URL {} }";
244         final Permissions p = (Permissions) JexlPermissions.parse(src);
245         final Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
246         assertNotNull(nojexlmap);
247         final Permissions.NoJexlPackage njp = nojexlmap.get("java.lang");
248         assertNotNull(njp);
249         final Method exit = getMethod(java.lang.Runtime.class,"exit");
250         assertNotNull(exit);
251         assertFalse(p.allow(exit));
252         final Method exec = getMethod(java.lang.Runtime.class,"exec");
253         assertNotNull(exec);
254         assertFalse(p.allow(exec));
255         final Method avp = getMethod(java.lang.Runtime.class,"availableProcessors");
256         assertNotNull(avp);
257         assertTrue(p.allow(avp));
258         final JexlUberspect uber = new Uberspect(null, null, p);
259         assertNull(uber.getClassByName("java.net.URL"));
260     }
261 
262     @Test
263     void testParsePermissions0b() {
264         final String src = "java.lang { -Runtime { exit(); } }";
265         final Permissions p = (Permissions) JexlPermissions.parse(src);
266         final Method exit = getMethod(java.lang.Runtime.class,"exit");
267         assertNotNull(exit);
268         assertFalse(p.allow(exit));
269     }
270 
271     @Test
272     void testParsePermissions0a2() {
273         // Deny-list package with partial class denial: Runtime is accessible, only exit()/exec() denied
274         final String src = "java.lang.*\njava.lang { Runtime { exit(); exec(); } }";
275         final Permissions p = (Permissions) JexlPermissions.parse(src);
276         assertTrue(p.allow(Runtime.class));
277         assertFalse(p.allow(getMethod(Runtime.class, "exit")));
278         assertTrue(p.allow(getMethod(Runtime.class, "availableProcessors")));
279     }
280 
281     @Test
282     void testParsePermissions0b2() {
283         // Positive package: all classes allowed, but deny specific methods on Runtime
284         final String src = "java.lang +{ -Runtime { exit(); } }";
285         final Permissions p = (Permissions) JexlPermissions.parse(src);
286         assertTrue(p.allow(Runtime.class));
287         final Method exit = getMethod(Runtime.class, "exit");
288         assertNotNull(exit);
289         assertFalse(p.allow(exit));
290         final Method avp = getMethod(Runtime.class, "availableProcessors");
291         assertNotNull(avp);
292         assertTrue(p.allow(avp));
293         assertTrue(p.allow(String.class));
294     }
295 
296     @Test
297     void testFormatterCtorDenied() throws Exception {
298         // positive package java.util, Formatter class accessible, only the constructor denied
299         final Constructor<?> ctor = java.util.Formatter.class.getConstructor();
300         assertNotNull(ctor);
301         assertFalse(RESTRICTED.allow(ctor));
302         final Method format = getMethod(java.util.Formatter.class, "format");
303         assertNotNull(format);
304         assertTrue(RESTRICTED.allow(format));
305         assertTrue(RESTRICTED.allow(java.util.Formatter.class));
306     }
307 
308     @Test
309     void testPrintWriterCtorDenied() {
310         // negative package java.io, PrintWriter added as a positive class, only the constructor denied
311         for (final Constructor<?> ctor : java.io.PrintWriter.class.getDeclaredConstructors()) {
312             assertFalse(RESTRICTED.allow(ctor), () -> "should deny " + ctor);
313         }
314         final Method println = getMethod(java.io.PrintWriter.class, "println");
315         assertNotNull(println);
316         assertTrue(RESTRICTED.allow(println));
317         assertTrue(RESTRICTED.allow(java.io.PrintWriter.class));
318         // unlisted java.io class stays denied
319         assertFalse(RESTRICTED.allow(java.io.File.class));
320     }
321 
322     @Test
323     void testForeignTypeRequiresExplicitAllow() {
324         // a foreign class must NOT be reachable just because a super-type is allowed (no reach-through):
325         // com.google.gson.internal.LinkedTreeMap extends java.util.AbstractMap but its own package is undeclared
326         assertFalse(RESTRICTED.allow(com.google.gson.internal.LinkedTreeMap.class));
327         // it becomes accessible only once its own package is explicitly allowed
328         final JexlPermissions allowed = RESTRICTED.compose("com.google.gson.internal +{}");
329         assertTrue(allowed.allow(com.google.gson.internal.LinkedTreeMap.class));
330         // a class in an undeclared sub-package of a declared package stays denied
331         assertFalse(RESTRICTED.allow(java.lang.annotation.ElementType.class));
332     }
333 
334     @Test
335     void testPositivePackageNoSubPackageWildcard() {
336         // a positive package (java.util +{}) must not implicitly allow its sub-packages
337         assertFalse(RESTRICTED.allow(java.util.zip.ZipEntry.class));
338     }
339 
340     @Test
341     void testSecureMinimum() throws Exception {
342         final JexlPermissions secure = JexlPermissions.SECURE;
343         // allowed: safe java.lang value types, java.math, java.util collections + their methods
344         assertTrue(secure.allow(String.class));
345         assertTrue(secure.allow(Integer.class));
346         assertTrue(secure.allow(java.util.ArrayList.class));
347         assertTrue(secure.allow(java.util.HashMap.class));
348         assertTrue(secure.allow(java.math.BigInteger.class));
349         assertTrue(secure.allow(getMethod(Integer.class, "intValue")));
350         assertTrue(secure.allow(getMethod(java.math.BigInteger.class, "bitLength")));
351         // Object: equals/hashCode/toString allowed, getClass denied
352         assertTrue(secure.allow(getMethod(Object.class, "toString")));
353         assertFalse(secure.allow(getMethod(Object.class, "getClass")));
354         // denied: dangerous java.lang, java.io, sub-packages, other base packages
355         assertFalse(secure.allow(System.class));
356         assertFalse(secure.allow(Runtime.class));
357         assertFalse(secure.allow(java.lang.Class.class));
358         assertFalse(secure.allow(java.io.File.class));
359         assertFalse(secure.allow(java.util.zip.ZipEntry.class));
360         assertFalse(secure.allow(java.time.LocalDate.class));
361     }
362 
363     @Test
364     void testNoneDeniesEverything() throws Exception {
365         final JexlPermissions none = JexlPermissions.NONE;
366         assertFalse(none.allow(String.class));
367         assertFalse(none.allow(Integer.class));
368         assertFalse(none.allow(Object.class));
369         assertFalse(none.allow(java.util.ArrayList.class));
370         assertFalse(none.allow(getMethod(String.class, "length")));
371         for (final java.lang.reflect.Constructor<?> ctor : String.class.getConstructors()) {
372             assertFalse(none.allow(ctor));
373         }
374     }
375 
376     @Test
377     void testCreateFromScratch() throws Exception {
378         // create() with no rules denies everything
379         assertFalse(JexlPermissions.create().allow(String.class));
380         // a positive rule grants exactly that class, nothing else
381         final JexlPermissions onlyString = JexlPermissions.create("java.lang { +String{} }");
382         assertTrue(onlyString.allow(String.class));
383         assertTrue(onlyString.allow(getMethod(String.class, "length")));
384         assertFalse(onlyString.allow(Integer.class));
385         assertFalse(onlyString.allow(java.util.ArrayList.class));
386         // composing onto NONE behaves the same as create
387         final JexlPermissions onlyUtil = JexlPermissions.NONE.compose("java.util.*");
388         assertTrue(onlyUtil.allow(java.util.ArrayList.class));
389         assertFalse(onlyUtil.allow(String.class));
390     }
391 
392     @Test
393     void testRestrictedNoFileMethods() throws Exception {
394         final JexlPermissions r = RESTRICTED;
395         // file / loader members are denied while the classes stay visible
396         assertFalse(r.allow(getMethod(java.util.Properties.class, "load")));
397         assertFalse(r.allow(getMethod(java.util.Properties.class, "store")));
398         assertFalse(r.allow(getMethod(java.util.Properties.class, "loadFromXML")));
399         assertFalse(r.allow(getMethod(java.util.ResourceBundle.class, "getBundle")));
400         assertFalse(r.allow(getMethod(java.util.ServiceLoader.class, "load")));
401         for (final java.lang.reflect.Constructor<?> ctor : java.util.Scanner.class.getConstructors()) {
402             assertFalse(r.allow(ctor), () -> "Scanner ctor must be denied: " + ctor);
403         }
404         // classes remain visible and their benign members allowed (no over-restriction)
405         assertTrue(r.allow(java.util.Properties.class));
406         assertTrue(r.allow(java.util.Scanner.class));
407         assertTrue(r.allow(java.util.ResourceBundle.class));
408         assertTrue(r.allow(getMethod(java.util.Properties.class, "getProperty")));
409         assertTrue(r.allow(getMethod(java.util.ResourceBundle.class, "getString")));
410         // system-property readers denied; the classes and their other methods stay usable
411         assertFalse(r.allow(getMethod(Integer.class, "getInteger")));
412         assertFalse(r.allow(getMethod(Long.class, "getLong")));
413         assertFalse(r.allow(getMethod(Boolean.class, "getBoolean")));
414         assertTrue(r.allow(Integer.class));
415         assertTrue(r.allow(getMethod(Integer.class, "intValue")));
416     }
417 
418     @Test
419     void testSecureNoFileNoEnv() throws Exception {
420         final JexlPermissions secure = JexlPermissions.SECURE;
421         // file / loader / thread bearing java.util classes are denied
422         assertFalse(secure.allow(java.util.Formatter.class));
423         assertFalse(secure.allow(java.util.Scanner.class));
424         assertFalse(secure.allow(java.util.ServiceLoader.class));
425         assertFalse(secure.allow(java.util.ResourceBundle.class));
426         assertFalse(secure.allow(java.util.Properties.class));
427         assertFalse(secure.allow(java.util.Timer.class));
428         // no java.util.Formatter constructor may be reached (no file can be opened for writing)
429         for (final java.lang.reflect.Constructor<?> ctor : java.util.Formatter.class.getConstructors()) {
430             assertFalse(secure.allow(ctor), () -> "Formatter ctor must be denied: " + ctor);
431         }
432         // getClass()/wait()/notify() are denied even on a positively-allowed concrete type (bypass fix)
433         assertFalse(secure.allow(String.class, getMethod(Object.class, "getClass")));
434         assertFalse(secure.allow(Integer.class, getMethod(Object.class, "getClass")));
435         assertFalse(secure.allow(String.class, getMethod(Object.class, "notify")));
436         // system-property readers stay denied
437         assertFalse(secure.allow(getMethod(Integer.class, "getInteger")));
438         assertFalse(secure.allow(getMethod(Long.class, "getLong")));
439         assertFalse(secure.allow(getMethod(Boolean.class, "getBoolean")));
440         // no regression: collection literals + their entry iteration still work under SECURE
441         assertTrue(secure.allow(java.util.LinkedHashMap.class));
442         assertTrue(secure.allow(java.util.HashSet.class));
443         assertTrue(secure.allow(java.util.LinkedHashSet.class));
444         final java.util.Map<String, String> m = new java.util.HashMap<>();
445         m.put("a", "b");
446         final java.util.Map.Entry<String, String> e = m.entrySet().iterator().next();
447         assertTrue(secure.allow(e.getClass()), () -> "map entry concrete class must be reachable: " + e.getClass());
448         assertTrue(secure.allow(e.getClass(), getMethod(e.getClass(), "getKey")));
449     }
450 
451     @Test
452     void testRangeIteratesUnderRestrictedAndSecure() {
453         // ranges are a language primitive and must iterate even under the tightest permissions
454         for (final JexlPermissions p : new JexlPermissions[] { RESTRICTED, JexlPermissions.SECURE }) {
455             final JexlEngine jexl = new JexlBuilder().permissions(p).create();
456             final Object r = jexl.createScript("var s = 0; for (var i : 1..3) { s += i; } s").execute(null);
457             assertEquals(6, ((Number) r).intValue(), () -> "range should iterate under " + p);
458         }
459     }
460 
461     @Test
462     void testParsePermissions0c() {
463         final String src = "java.lang { +Runtime { availableProcessorCount(); } }";
464         final Permissions p = (Permissions) JexlPermissions.parse(src);
465         final Method exit = getMethod(java.lang.Runtime.class,"exit");
466         assertNotNull(exit);
467         assertFalse(p.allow(exit));
468     }
469 
470     @Test
471     void testParsePermissions0d() throws Exception {
472         final String src = "java.lang { +System { currentTimeMillis(); } }";
473         final JexlPermissions p = RESTRICTED.compose(src);
474         final Field in = System.class.getField("in");
475         assertNotNull(in);
476         assertFalse(p.allow(in));
477         final Method ctm = getMethod(java.lang.System.class,"currentTimeMillis");
478         assertNotNull(ctm);
479         assertTrue(p.allow(ctm));
480     }
481 
482     @Test
483     void testParsePermissions0e() throws Exception {
484         final String src = "java.lang { +System { in; } }";
485         final JexlPermissions p = RESTRICTED.compose(src);
486         final Field in = System.class.getField("in");
487         assertNotNull(in);
488         assertTrue(p.allow(in));
489         final Method ctm = getMethod(java.lang.System.class,"currentTimeMillis");
490         assertNotNull(ctm);
491         assertFalse(p.allow(ctm));
492     }
493 
494     @Test
495     void testParsePermissions0f() {
496         final String src = "java.lang { +Class { getName(); getSimpleName(); } }";
497         final JexlPermissions p = RESTRICTED.compose(src);
498         final Method getName = getMethod(java.lang.Class.class,"getName");
499         assertNotNull(getName);
500         assertTrue(p.allow(getName));
501         assertFalse(RESTRICTED.allow(getName));
502         final Method getSimpleName = getMethod(java.lang.Class.class,"getSimpleName");
503         assertNotNull(getSimpleName);
504         assertTrue(p.allow(getSimpleName));
505         assertFalse(RESTRICTED.allow(getSimpleName));
506 
507         final Method getMethod = getMethod(java.lang.Class.class,"getMethod");
508         assertNotNull(getMethod);
509         assertFalse(p.allow(getMethod));
510 
511         final Method exit = getMethod(java.lang.Runtime.class,"exit");
512         assertNotNull(exit);
513         assertFalse(p.allow(exit));
514     }
515 
516     @Test
517     void testParsePermissions0g() {
518         final String src = "java.lang { +Class {  } }";
519         final JexlPermissions p = RESTRICTED.compose(src);
520         final Method getName = getMethod(java.lang.Class.class,"getName");
521         assertNotNull(getName);
522         assertTrue(p.allow(getName));
523         final Method getMethod = getMethod(java.lang.Class.class,"getMethod");
524         assertNotNull(getMethod);
525         assertTrue(p.allow(getMethod));
526 
527         final Method exit = getMethod(java.lang.Runtime.class,"exit");
528         assertNotNull(exit);
529         assertFalse(p.allow(exit));
530     }
531 
532     @Test
533     void testParsePermissions1() {
534         final String[] src = {
535             "java.lang.*",
536             "java.math.*",
537             "java.text.*",
538             "java.util.*",
539             "java.lang { Runtime {} }",
540             "java.rmi {}",
541             "java.io { File {} }",
542             "java.nio { Path {} }" ,
543             "org.apache.commons.jexl3.internal.introspection { " +
544                 "PermissionsTest { #level 0\n" +
545                     " Outer { #level 1\n" +
546                         " Inner { #level 2\n" +
547                             " callMeNot();" +
548                         " }" +
549                     " }" +
550                 " }" +
551             " }"};
552         final Permissions p = (Permissions) JexlPermissions.parse(src);
553         final Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
554         assertNotNull(nojexlmap);
555         final Set<String> wildcards = p.getWildcards();
556         assertEquals(4, wildcards.size());
557 
558         final JexlEngine jexl = new JexlBuilder().permissions(p).safe(false).lexical(true).create();
559         final Class<?> longz = Long.TYPE;
560         assertTrue(p.allow(longz));
561         final Method exit = getMethod(java.lang.Runtime.class,"exit");
562         assertNotNull(exit);
563         assertFalse(p.allow(exit));
564         final Method exec = getMethod(java.lang.Runtime.class,"getRuntime");
565         assertNotNull(exec);
566         assertFalse(p.allow(exec));
567         final Method callMeNot = getMethod(Outer.Inner.class, "callMeNot");
568         assertNotNull(callMeNot);
569         assertFalse(p.allow(callMeNot));
570         final JexlScript script = jexl.createScript("o.callMeNot()", "o");
571         assertEquals("callMeNot", assertThrows(JexlException.Method.class, () -> script.execute(null, new Outer.Inner())).getMethod());
572         final Method uncallable = getMethod(Invisible.class, "uncallable");
573         assertFalse(p.allow(uncallable));
574         final Package ip = Invisible.class.getPackage();
575         assertFalse(p.allow(ip));
576         final JexlScript script2 = jexl.createScript("o.uncallable()", "o");
577         assertEquals("uncallable", assertThrows(JexlException.Method.class, () -> script2.execute(null, new Invisible())).getMethod());
578     }
579 
580     @Test
581     void testParsePermissionsFailures() {
582         // @formatter:off
583         final String[] srcs = {
584                 "java.lang.*.*",
585                 "java.math.*.",
586                 "java.text.*;",
587                 "java.lang {{ Runtime {} }",
588                 "java.rmi {}}",
589                 "java.io { Text File {} }",
590                 "java.io { File { m.x } }",
591                 // f008: mixing +/- signs on a single element is ambiguous and rejected
592                 "java.io { File { + -m(); } }",
593                 "java.io { File { - +f; } }"
594         };
595         // @formatter:on
596         for (final String src : srcs) {
597             assertThrows(IllegalStateException.class, () -> JexlPermissions.parse(src));
598         }
599     }
600 
601     @Test
602     void testInnerClassDenySign() {
603         // f008: a '-' sign on an inner class must deny it, even inside an allowing (+) outer class;
604         // previously the inner-class sign was discarded and the outer polarity leaked in
605         final String pkg = "org.apache.commons.jexl3.internal.introspection";
606         final String src = pkg + " { PermissionsTest { +Outer { -Inner {} } } }";
607         final Permissions p = (Permissions) JexlPermissions.parse(src);
608         final Method callMeNot = getMethod(Outer.Inner.class, "callMeNot");
609         assertFalse(p.allow(callMeNot), "-Inner must be denied");
610     }
611 
612     @Test
613     void testPositiveMemberDoesNotLeakToInnerClass() {
614         // f008: a '+' before a member must not flip the enclosing class polarity and leak into a
615         // following inner class; Inner inherits Outer's deny polarity and stays denied
616         final String pkg = "org.apache.commons.jexl3.internal.introspection";
617         final String src = pkg + " { PermissionsTest { Outer { +hashCode(); Inner {} } } }";
618         final Permissions p = (Permissions) JexlPermissions.parse(src);
619         final Method callMeNot = getMethod(Outer.Inner.class, "callMeNot");
620         assertFalse(p.allow(callMeNot), "Inner must stay denied despite a preceding +member");
621     }
622 
623     @Test
624     void testEngineCompilerDeniedUnderRestricted() {
625         // f009/f036: a live engine reachable from a script must not act as a second-stage compiler
626         assertFalse(RESTRICTED.allow(getMethod(JexlEngine.class, "createScript")));
627         assertFalse(RESTRICTED.allow(getMethod(JexlEngine.class, "createExpression")));
628         assertFalse(RESTRICTED.allow(getMethod(JexlEngine.class, "createJxltEngine")));
629         assertFalse(RESTRICTED.allow(getMethod(JexlEngine.class, "getThreadEngine")));
630 
631         final JexlEngine jexl = new JexlBuilder().permissions(RESTRICTED).safe(false).strict(true).create();
632         final JexlContext ctxt = new MapContext();
633         ctxt.set("e", jexl);
634         final JexlScript script = jexl.createScript("e.createScript('1 + 1')");
635         assertThrows(JexlException.Method.class, () -> script.execute(ctxt));
636     }
637 
638     @Test
639     void testPermissions0() throws Exception {
640         runTestPermissions(permissions0());
641     }
642 
643     @Test
644     void testPermissions1() throws Exception {
645         runTestPermissions(new JexlPermissions.Delegate(permissions0()) {
646             @Override public String toString() {
647                 return "delegate:" + base.toString();
648             }
649         });
650     }
651 
652     @Test
653     void testPermissions2() throws Exception {
654         runTestPermissions(new JexlPermissions.ClassPermissions(permissions0(), Collections.emptySet()));
655     }
656 
657     @Test void testPrivateOverload1() {
658         final String src = "parseDouble(\"PHM1\".substring(3)).intValue()";
659         final JexlArithmetic jexla = new I33Arithmetic(true);
660         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(jexla).create();
661         final JexlScript script = jexl.createScript(src);
662         assertNotNull(script);
663         final Object result = script.execute(null);
664         assertEquals(1, result);
665     }
666 
667     @Test void testProtectedOverride0() {
668         JexlScript script;
669         Object r;
670         final Foo2 foo3 = new Foo3();
671         final JexlEngine jexl = new JexlBuilder().safe(false).create();
672         // call public override of protected, nok
673         final Foo2 foo2 = new Foo2();
674         script = jexl.createScript("x.protectedMethod()", "x");
675         assertThrows(JexlException.class, () -> script.execute(null, foo2), "protectedMethod() is not public through superclass Foo2");
676         // call public override, ok
677         r = script.execute(null, foo3);
678         assertEquals("foo3",r);
679     }
680 
681     @Test void testProtectedOverride1() {
682         final List<String> a = new LinkedList<>();
683         a.add("aaa");
684         a.add("bbb");
685 
686         final String src = "a.clone()";
687         final JexlEngine jexl = new JexlBuilder().safe(true).create();
688         final JexlScript script = jexl.createScript(src);
689         final JexlContext context = new MapContext();
690         context.set("a", a);
691         final Object result = script.execute(context, a);
692         assertNotNull(result);
693     }
694 
695     @Test
696     void testSecurePermissions() {
697         assertNotNull(JexlTestCase.TEST_PERMS);
698         final List<Class<?>> acs = Arrays.asList(
699             java.lang.Runtime.class,
700             java.math.BigDecimal.class,
701             java.text.SimpleDateFormat.class,
702             java.util.Map.class);
703         for(final Class<?> ac: acs) {
704             final Package p = ac.getPackage();
705             assertNotNull(p, ac::getName);
706             assertTrue(JexlTestCase.TEST_PERMS.allow(p), ac::getName);
707         }
708         final List<Class<?>> nacs = Arrays.asList(
709                 java.lang.annotation.ElementType.class,
710                 java.lang.instrument.ClassDefinition.class,
711                 java.lang.invoke.CallSite.class,
712                 java.lang.management.BufferPoolMXBean.class,
713                 java.lang.ref.SoftReference.class,
714                 java.lang.reflect.Method.class);
715         for(final Class<?> nac : nacs) {
716             assertFalse(JexlTestCase.TEST_PERMS.allow(nac), nac::getName);
717             final Package p = nac.getPackage();
718             assertNotNull(p, nac::getName);
719             assertFalse(JexlTestCase.TEST_PERMS.allow(p), nac::getName);
720         }
721     }
722 
723     @Test
724     void testWildCardPackages() {
725         Set<String> wildcards;
726         boolean found;
727         wildcards = Collections.singleton("com.apache.*");
728         found = Permissions.wildcardAllow(wildcards, "com.apache.commons.jexl3");
729         assertTrue(found);
730         found = Permissions.wildcardAllow(wildcards, "com.google.spexl");
731         assertFalse(found);
732     }
733 
734     public static class Scheme {
735         public Pair cons(final Object first, final Object second) {
736             return new Pair(first, second);
737         }
738     }
739 
740     @Test
741     void testPair0() throws Exception {
742         final Map<String, Object> funcs = new HashMap<>();
743         funcs.put("lisp", new Scheme());
744         final JexlPermissions permissions = JexlPermissions.RESTRICTED.compose(
745       "org.example.*",
746             "org.apache.commons.jexl3.internal.introspection { +PermissionsTest$Scheme {} }");
747         assertTrue(permissions.allow(Scheme.class.getMethod("cons", Object.class, Object.class)));
748         final JexlEngine jexl = new JexlBuilder().cache(8).permissions(permissions).namespaces(funcs).create();
749         String src = "let p = lisp:cons(17, 25); p.car + p.cdr;";
750         JexlScript script = jexl.createScript(src);
751         assertEquals(42, script.execute(null));
752         assertEquals(42, script.execute(null));
753         src = "(p, x, y) -> { p.car = x; p.cdr = y; }";
754         final Pair p = new Pair(-1, -41);
755         script = jexl.createScript(src);
756         assertNotNull(script.execute(null, p, 22, 20));
757         assertEquals(22, p.car);
758         assertEquals(20, p.cdr);
759         assertNotNull(script.execute(null, p, 18, 24));
760         assertEquals(18, p.car);
761         assertEquals(24, p.cdr);
762     }
763 
764     @Test
765     void testPair1() {
766         final Map<String, Object> funcs = new HashMap<>();
767         final JexlPermissions permissions = JexlPermissions.RESTRICTED.compose("org.example.*");
768         final JexlEngine jexl = new JexlBuilder().cache(8).permissions(permissions).namespaces(funcs).create();
769         final String src = "import org.example.Pair; new Pair(17, 25);";
770         final JexlScript script = jexl.createScript(src);
771         assertThrows(JexlException.class, ()-> script.execute(null));
772     }
773 
774     @Test
775     void testPermissions457a() {
776         assertTrue(RESTRICTED.allow(Long.TYPE));
777         for (final Constructor<?> ctor : FileWriter.class.getDeclaredConstructors()) {
778             assertFalse(RESTRICTED.allow(ctor));
779         }
780         for (final Method m : FileWriter.class.getMethods()) {
781             if (m.getName().equals("write")) {
782                 assertTrue(RESTRICTED.allow(m), m.toString());
783             }
784         }
785     }
786 
787     @Test
788     void testPermissions457b() {
789         final JexlEngine jexl = new JexlBuilder().silent(false).permissions(RESTRICTED).create();
790         final List<String> srcs = Arrays.asList(
791             "new('java.io.FileWriter', 'test.txt')",
792             "new('java.io.FileWriter', 'test.txt', true)",
793             "new('java.io.FileWriter', new java.io.File('test.txt'))",
794             "new('java.io.FileWriter', new java.io.File('test.txt'), true)",
795             "import java.io.FileWriter; new FileWriter('test.txt')"
796         );
797         for (final String src : srcs) {
798             final JexlScript script = jexl.createScript(src);
799             try {
800                 script.execute(null);
801                 fail("should have thrown a permission exception");
802             } catch (final JexlException.Method exception) {
803                 assertTrue(exception.getMethod().contains("File"), "FileWriter::new should not be allowed");
804             }
805         }
806     }
807 
808     @Test
809     void testPermissions457c() {
810         final JexlEngine jexl = new JexlBuilder().silent(false).permissions(RESTRICTED).create();
811         final List<String> srcs = Arrays.asList(
812             "new('java.io.FileReader','test.txt')",
813             "new('java.io.FileReader', new java.io.File('test.txt'))",
814             "import java.io.FileReader; new FileReader('test.txt')");
815         for (final String src : srcs) {
816             final JexlScript script = jexl.createScript(src);
817             try {
818                 script.execute(null);
819                 fail("should have thrown a permission exception");
820             } catch (final JexlException.Method exception) {
821                 assertTrue(exception.getMethod().contains("File"), "FileReader::new should not be allowed");
822             }
823         }
824     }
825 }