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    *      http://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  
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.Field;
29  import java.lang.reflect.Method;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.apache.commons.jexl3.JexlArithmetic;
39  import org.apache.commons.jexl3.JexlBuilder;
40  import org.apache.commons.jexl3.JexlContext;
41  import org.apache.commons.jexl3.JexlEngine;
42  import org.apache.commons.jexl3.JexlException;
43  import org.apache.commons.jexl3.JexlScript;
44  import org.apache.commons.jexl3.JexlTestCase;
45  import org.apache.commons.jexl3.MapContext;
46  import org.apache.commons.jexl3.internal.introspection.nojexlpackage.Invisible;
47  import org.apache.commons.jexl3.introspection.JexlPermissions;
48  import org.apache.commons.jexl3.introspection.JexlUberspect;
49  import org.junit.jupiter.api.Test;
50  
51  /**
52   * Checks the CacheMap.MethodKey implementation
53   */
54  
55  public class PermissionsTest {
56  
57      public static class A {
58          public int i;
59          public A() {}
60          public int method() { return 0; }
61      }
62  
63      public static class A0 extends A implements InterNoJexl0 {
64          /*@NoJexl*/ public int i0;
65          /*@NoJexl*/ public A0() {}
66          @Override public int method() { return 1; }
67      }
68  
69      public static class A1 extends A implements InterNoJexl1 {
70          private int i1;
71          /*@NoJexl*/ public A1() {}
72          @Override public int method() { return 2; }
73      }
74  
75      //@NoJexl
76      public static class A2 extends A  {
77          public A2() {}
78          @Override public int method() { return 3; }
79      }
80  
81      protected static class A3 {
82          protected int i3;
83          protected A3() {}
84          int method() { return 4; }
85      }
86  
87      public static class A5 implements InterNoJexl5 {
88          public A5() {}
89          @Override public int method() { return 0; }
90      }
91  
92      protected static class Foo2 {
93          protected String protectedMethod() {
94              return "foo2";
95          }
96          public String publicMethod() {
97              return "foo2";
98          }
99      }
100 
101     public static class Foo3 extends Foo2 {
102         @Override public String protectedMethod() {
103             return "foo3";
104         }
105         @Override public String publicMethod() {
106             return "foo3";
107         }
108     }
109 
110     public class I33Arithmetic extends JexlArithmetic {
111         public I33Arithmetic(final boolean astrict) {
112             super(astrict);
113         }
114 
115         /**
116          * Same name signature as default private method.
117          * @param s the string
118          * @return a double, NaN if fail
119          */
120         public double parseDouble(final String s) {
121             try {
122                 return Double.parseDouble(s);
123             } catch (final NumberFormatException nfe) {
124                 return Double.NaN;
125             }
126         }
127     }
128 
129     //@NoJexl
130     public interface InterNoJexl0 {
131         int method();
132     }
133 
134     public interface InterNoJexl1 {
135         //@NoJexl
136         int method();
137     }
138 
139     //@NoJexl
140     public interface InterNoJexl5 {
141         int method();
142     }
143 
144     public static class Outer {
145         public static class Inner {
146             public void callMeNot() {}
147         }
148     }
149 
150     static Method getMethod(final Class<?> clazz, final String method) {
151         return Arrays.stream(clazz.getMethods()).filter(mth->mth.getName().equals(method)).findFirst().get();
152     }
153 
154     JexlPermissions permissions0() {
155         final String src = " org.apache.commons.jexl3.internal.introspection { PermissionsTest { " +
156                 "InterNoJexl0 { } " +
157                 "InterNoJexl1 { method(); } " +
158                 "A0 { A0(); i0; } " +
159                 "A1 { A1(); } " +
160                 "A2 { } " +
161                 "InterNoJexl5 { } " +
162                 "} }";
163         return JexlPermissions.parse(src);
164     }
165 
166     private void runTestPermissions(final JexlPermissions p) throws Exception {
167         assertFalse(p.allow((Field) null));
168         assertFalse(p.allow((Package) null));
169         assertFalse(p.allow((Method) null));
170         assertFalse(p.allow((Constructor<?>) null));
171         assertFalse(p.allow((Class<?>) null));
172 
173         assertFalse(p.allow(A2.class));
174         assertTrue(p.allow(A3.class));
175         assertTrue(p.allow(A5.class));
176 
177         final Method mA = A.class.getMethod("method");
178         assertNotNull(mA);
179         final Method mA0 = A0.class.getMethod("method");
180         assertNotNull(mA0);
181         final Method mA1 = A1.class.getMethod("method");
182         assertNotNull(mA1);
183         final Method mA2 = A2.class.getMethod("method");
184         assertNotNull(mA2);
185         final Method mA3 = A2.class.getDeclaredMethod("method");
186         assertNotNull(mA3);
187 
188         assertTrue(p.allow(mA));
189         assertFalse(p.allow(mA0));
190         assertFalse(p.allow(mA1));
191         assertFalse(p.allow(mA2));
192         assertFalse(p.allow(mA3));
193 
194         final Field fA = A.class.getField("i");
195         assertNotNull(fA);
196         assertTrue(p.allow(fA));
197 
198         final Field fA0 = A0.class.getField("i0");
199         assertNotNull(fA0);
200         assertFalse(p.allow(fA0));
201         final Field fA1 = A1.class.getDeclaredField("i1");
202         assertNotNull(fA1);
203         assertFalse(p.allow(fA0));
204 
205         final Constructor<?> cA = A.class.getConstructor();
206         assertNotNull(cA);
207         assertTrue(p.allow(cA));
208 
209         final Constructor<?> cA0 = A0.class.getConstructor();
210         assertNotNull(cA0);
211         assertFalse(p.allow(cA0));
212 
213         final Constructor<?> cA3 = A3.class.getDeclaredConstructor();
214         assertNotNull(cA3);
215         assertFalse(p.allow(cA3));
216     }
217 
218     @Test
219     public void testGetPackageName() {
220         final String PKG = "org.apache.commons.jexl3.internal.introspection";
221         String pkg = ClassTool.getPackageName(Outer.class);
222         assertEquals(PKG, pkg);
223         pkg = ClassTool.getPackageName(Outer.Inner.class);
224         assertEquals(PKG, pkg);
225         final Outer[] oo = {};
226         pkg = ClassTool.getPackageName(oo.getClass());
227         assertEquals(PKG, pkg);
228         final Outer.Inner[] ii = {};
229         pkg = ClassTool.getPackageName(ii.getClass());
230         assertEquals(PKG, pkg);
231         pkg = ClassTool.getPackageName(Process.class);
232         assertEquals("java.lang", pkg);
233         pkg = ClassTool.getPackageName(Integer.TYPE);
234         assertEquals("java.lang", pkg);
235     }
236 
237     @Test
238     public void testParsePermissions0a() throws Exception {
239         final String src = "java.lang { Runtime { exit(); exec(); } }\njava.net { URL {} }";
240         final Permissions p = (Permissions) JexlPermissions.parse(src);
241         final Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
242         assertNotNull(nojexlmap);
243         final Permissions.NoJexlPackage njp = nojexlmap.get("java.lang");
244         assertNotNull(njp);
245         final Method exit = getMethod(java.lang.Runtime.class,"exit");
246         assertNotNull(exit);
247         assertFalse(p.allow(exit));
248         final Method exec = getMethod(java.lang.Runtime.class,"exec");
249         assertNotNull(exec);
250         assertFalse(p.allow(exec));
251         final Method avp = getMethod(java.lang.Runtime.class,"availableProcessors");
252         assertNotNull(avp);
253         assertTrue(p.allow(avp));
254         final JexlUberspect uber = new Uberspect(null, null, p);
255         assertNull(uber.getClassByName("java.net.URL"));
256     }
257 
258     @Test
259     public void testParsePermissions0b() throws Exception {
260         final String src = "java.lang { -Runtime { exit(); } }";
261         final Permissions p = (Permissions) JexlPermissions.parse(src);
262         final Method exit = getMethod(java.lang.Runtime.class,"exit");
263         assertNotNull(exit);
264         assertFalse(p.allow(exit));
265     }
266 
267     @Test
268     public void testParsePermissions0c() throws Exception {
269         final String src = "java.lang { +Runtime { availableProcessorCount(); } }";
270         final Permissions p = (Permissions) JexlPermissions.parse(src);
271         final Method exit = getMethod(java.lang.Runtime.class,"exit");
272         assertNotNull(exit);
273         assertFalse(p.allow(exit));
274     }
275 
276     @Test
277     public void testParsePermissions0d() throws Exception {
278         final String src = "java.lang { +System { currentTimeMillis(); } }";
279         final JexlPermissions p = RESTRICTED.compose(src);
280         final Field in = System.class.getField("in");
281         assertNotNull(in);
282         assertFalse(p.allow(in));
283         final Method ctm = getMethod(java.lang.System.class,"currentTimeMillis");
284         assertNotNull(ctm);
285         assertTrue(p.allow(ctm));
286     }
287 
288     @Test
289     public void testParsePermissions0e() throws Exception {
290         final String src = "java.lang { +System { in; } }";
291         final JexlPermissions p = RESTRICTED.compose(src);
292         final Field in = System.class.getField("in");
293         assertNotNull(in);
294         assertTrue(p.allow(in));
295         final Method ctm = getMethod(java.lang.System.class,"currentTimeMillis");
296         assertNotNull(ctm);
297         assertFalse(p.allow(ctm));
298     }
299 
300     @Test
301     public void testParsePermissions0f() throws Exception {
302         final String src = "java.lang { +Class { getName(); getSimpleName(); } }";
303         final JexlPermissions p = RESTRICTED.compose(src);
304         final Method getName = getMethod(java.lang.Class.class,"getName");
305         assertNotNull(getName);
306         assertTrue(p.allow(getName));
307         assertFalse(RESTRICTED.allow(getName));
308         final Method getSimpleName = getMethod(java.lang.Class.class,"getSimpleName");
309         assertNotNull(getSimpleName);
310         assertTrue(p.allow(getSimpleName));
311         assertFalse(RESTRICTED.allow(getSimpleName));
312 
313         final Method getMethod = getMethod(java.lang.Class.class,"getMethod");
314         assertNotNull(getMethod);
315         assertFalse(p.allow(getMethod));
316 
317         final Method exit = getMethod(java.lang.Runtime.class,"exit");
318         assertNotNull(exit);
319         assertFalse(p.allow(exit));
320     }
321 
322     @Test
323     public void testParsePermissions0g() throws Exception {
324         final String src = "java.lang { +Class {  } }";
325         final JexlPermissions p = RESTRICTED.compose(src);
326         final Method getName = getMethod(java.lang.Class.class,"getName");
327         assertNotNull(getName);
328         assertTrue(p.allow(getName));
329         final Method getMethod = getMethod(java.lang.Class.class,"getMethod");
330         assertNotNull(getMethod);
331         assertTrue(p.allow(getMethod));
332 
333         final Method exit = getMethod(java.lang.Runtime.class,"exit");
334         assertNotNull(exit);
335         assertFalse(p.allow(exit));
336     }
337 
338     @Test
339     public void testParsePermissions1() {
340         final String[] src = {
341                 "java.lang.*",
342                 "java.math.*",
343                 "java.text.*",
344                 "java.util.*",
345                 "java.lang { Runtime {} }",
346                 "java.rmi {}",
347                 "java.io { File {} }",
348                 "java.nio { Path {} }" ,
349                 "org.apache.commons.jexl3.internal.introspection { " +
350                     "PermissionsTest { #level 0\n" +
351                         " Outer { #level 1\n" +
352                             " Inner { #level 2\n" +
353                                 " callMeNot();" +
354                             " }" +
355                         " }" +
356                     " }" +
357                 " }"};
358         final Permissions p = (Permissions) JexlPermissions.parse(src);
359         final Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
360         assertNotNull(nojexlmap);
361         final Set<String> wildcards = p.getWildcards();
362         assertEquals(4, wildcards.size());
363 
364         final JexlEngine jexl = new JexlBuilder().permissions(p).safe(false).lexical(true).create();
365 
366         final Method exit = getMethod(java.lang.Runtime.class,"exit");
367         assertNotNull(exit);
368         assertFalse(p.allow(exit));
369         final Method exec = getMethod(java.lang.Runtime.class,"getRuntime");
370         assertNotNull(exec);
371         assertFalse(p.allow(exec));
372         final Method callMeNot = getMethod(Outer.Inner.class, "callMeNot");
373         assertNotNull(callMeNot);
374         assertFalse(p.allow(callMeNot));
375         final JexlScript script = jexl.createScript("o.callMeNot()", "o");
376         assertEquals("callMeNot", assertThrows(JexlException.Method.class, () -> script.execute(null, new Outer.Inner())).getMethod());
377         final Method uncallable = getMethod(Invisible.class, "uncallable");
378         assertFalse(p.allow(uncallable));
379         final Package ip = Invisible.class.getPackage();
380         assertFalse(p.allow(ip));
381         final JexlScript script2 = jexl.createScript("o.uncallable()", "o");
382         assertEquals("uncallable", assertThrows(JexlException.Method.class, () -> script2.execute(null, new Invisible())).getMethod());
383     }
384 
385     @Test
386     public void testParsePermissionsFailures() {
387         // @formatter:off
388         final String[] srcs = {
389                 "java.lang.*.*",
390                 "java.math.*.",
391                 "java.text.*;",
392                 "java.lang {{ Runtime {} }",
393                 "java.rmi {}}",
394                 "java.io { Text File {} }",
395                 "java.io { File { m.x } }"
396         };
397         // @formatter:on
398         for (final String src : srcs) {
399             assertThrows(IllegalStateException.class, () -> JexlPermissions.parse(src));
400         }
401     }
402 
403     @Test
404     public void testPermissions0() throws Exception {
405         runTestPermissions(permissions0());
406     }
407 
408     @Test
409     public void testPermissions1() throws Exception {
410         runTestPermissions(new JexlPermissions.Delegate(permissions0()) {
411             @Override public String toString() {
412                 return "delegate:" + base.toString();
413             }
414         });
415     }
416 
417     @Test
418     public void testPermissions2() throws Exception {
419         runTestPermissions(new JexlPermissions.ClassPermissions(permissions0(), Collections.emptySet()));
420     }
421 
422     @Test public void testPrivateOverload1() throws Exception {
423         final String src = "parseDouble(\"PHM1\".substring(3)).intValue()";
424         final JexlArithmetic jexla = new I33Arithmetic(true);
425         final JexlEngine jexl = new JexlBuilder().safe(false).arithmetic(jexla).create();
426         final JexlScript script = jexl.createScript(src);
427         assertNotNull(script);
428         final Object result = script.execute(null);
429         assertEquals(1, result);
430     }
431 
432     @Test public void testProtectedOverride0() {
433         JexlScript script;
434         Object r;
435         final Foo2 foo3 = new Foo3();
436         final JexlEngine jexl = new JexlBuilder().safe(false).create();
437         // call public override of protected, nok
438         final Foo2 foo2 = new Foo2();
439         script = jexl.createScript("x.protectedMethod()", "x");
440         assertThrows(JexlException.class, () -> script.execute(null, foo2), "protectedMethod() is not public through superclass Foo2");
441         // call public override, ok
442         r = script.execute(null, foo3);
443         assertEquals("foo3",r);
444     }
445 
446     @Test public void testProtectedOverride1() {
447         final List<String> a = new LinkedList<>();
448         a.add("aaa");
449         a.add("bbb");
450 
451         final String src = "a.clone()";
452         final JexlEngine jexl = new JexlBuilder().safe(true).create();
453         final JexlScript script = jexl.createScript(src);
454         final JexlContext context = new MapContext();
455         context.set("a", a);
456         final Object result = script.execute(context, a);
457         assertNotNull(result);
458     }
459 
460     @Test
461     public void testSecurePermissions() {
462         assertNotNull(JexlTestCase.SECURE);
463         final List<Class<?>> acs = Arrays.asList(
464             java.lang.Runtime.class,
465             java.math.BigDecimal.class,
466             java.text.SimpleDateFormat.class,
467             java.util.Map.class);
468         for(final Class<?> ac: acs) {
469             final Package p = ac.getPackage();
470             assertNotNull(p, ac::getName);
471             assertTrue(JexlTestCase.SECURE.allow(p), ac::getName);
472         }
473         final List<Class<?>> nacs = Arrays.asList(
474                 java.lang.annotation.ElementType.class,
475                 java.lang.instrument.ClassDefinition.class,
476                 java.lang.invoke.CallSite.class,
477                 java.lang.management.BufferPoolMXBean.class,
478                 java.lang.ref.SoftReference.class,
479                 java.lang.reflect.Method.class);
480         for(final Class<?> nac : nacs) {
481             final Package p = nac.getPackage();
482             assertNotNull(p, nac::getName);
483             assertFalse(JexlTestCase.SECURE.allow(p), nac::getName);
484         }
485     }
486 
487     @Test
488     public void testWildCardPackages() {
489         Set<String> wildcards;
490         boolean found;
491         wildcards = new HashSet<>(Arrays.asList("com.apache.*"));
492         found = Permissions.wildcardAllow(wildcards, "com.apache.commons.jexl3");
493         assertTrue(found);
494         found = Permissions.wildcardAllow(wildcards, "com.google.spexl");
495         assertFalse(found);
496     }
497 }