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;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  
23  import java.util.List;
24  
25  import org.apache.commons.jexl3.introspection.JexlPermissions;
26  import org.junit.jupiter.api.Test;
27  
28  /**
29   * Tests for {@link JexlPermissions#logging(org.apache.commons.logging.Log)} and the
30   * {@code LoggingPermissions} wrapper: it must log what it allows/denies, but each decision only once.
31   */
32  class LoggingPermissionsTest {
33  
34      @Test
35      void testLoggingPermissionsCaptureAndDedup() throws Exception {
36          final CaptureLog log = new CaptureLog();
37          final JexlPermissions perms = JexlPermissions.RESTRICTED.logging(log);
38          // call each element twice to exercise the once-only logging
39          for (int i = 0; i < 2; i++) {
40              assertTrue(perms.allow(String.class));                     // allowed class
41              assertFalse(perms.allow(java.lang.Runtime.class));         // denied class
42              assertTrue(perms.allow(String.class.getMethod("length"))); // allowed method
43          }
44          final List<String> msgs = log.getCapturedMessages();
45          // Each distinct line is logged once despite two rounds (otherwise there would be 8 records).
46          // allow(Method) delegates through allow(Class, Method), so a single method check yields two
47          // distinct lines: the method-in-class form and the plain method form.
48          assertEquals(4, msgs.size(), msgs::toString);
49          assertEquals(4, log.count("info"));
50          assertTrue(msgs.contains("Class java.lang.String is allowed"));
51          assertTrue(msgs.contains("Class java.lang.Runtime is denied"));
52          assertTrue(msgs.contains("Method java.lang.String.length() is allowed"));
53          assertTrue(msgs.contains("Method java.lang.String.length() is allowed for class java.lang.String"));
54      }
55  
56      @Test
57      void testLoggingConstructorAndField() throws Exception {
58          final CaptureLog log = new CaptureLog();
59          final JexlPermissions perms = JexlPermissions.RESTRICTED.logging(log);
60          // exercise the constructor and field overrides, twice each to check dedup
61          for (int i = 0; i < 2; i++) {
62              assertTrue(perms.allow(StringBuilder.class.getConstructor()));        // allowed ctor
63              assertFalse(perms.allow(java.io.File.class.getConstructor(String.class))); // denied ctor
64              assertTrue(perms.allow(Integer.class.getField("MAX_VALUE")));         // allowed field
65              assertFalse(perms.allow(System.class.getField("out")));              // denied field
66          }
67          final List<String> msgs = log.getCapturedMessages();
68          // constructor lines (ctor.getName() yields the declaring class binary name)
69          assertTrue(msgs.stream().anyMatch(m ->
70              m.startsWith("Constructor java.lang.StringBuilder") && m.endsWith("is allowed")), msgs::toString);
71          assertTrue(msgs.stream().anyMatch(m ->
72              m.startsWith("Constructor java.io.File") && m.endsWith("is denied")), msgs::toString);
73          // field lines (single-arg and the class-qualified form via delegation)
74          assertTrue(msgs.contains("Field java.lang.Integer.MAX_VALUE is allowed"), msgs::toString);
75          assertTrue(msgs.contains("Field java.lang.System.out is denied"), msgs::toString);
76          assertTrue(msgs.contains("Field java.lang.System.out is denied for class java.lang.System"), msgs::toString);
77          // dedup: a given line never repeats despite two rounds
78          assertEquals(msgs.size(), msgs.stream().distinct().count(), msgs::toString);
79      }
80  
81      @Test
82      void testLoggingComposePreservesWrapper() throws Exception {
83          final CaptureLog log = new CaptureLog();
84          final JexlPermissions composed =
85              JexlPermissions.RESTRICTED.logging(log).compose("java.io { +File {} }");
86          assertTrue(composed instanceof JexlPermissions.LoggingPermissions);
87          assertTrue(composed.allow(java.io.File.class));   // newly allowed by the compose rule
88          assertTrue(log.getCapturedMessages().contains("Class java.io.File is allowed"));
89      }
90  
91      @Test
92      void testLoggingFactories() {
93          // the (delegate) and (String, delegate) constructors via the instance factory methods
94          assertTrue(JexlPermissions.RESTRICTED.logging() instanceof JexlPermissions.LoggingPermissions);
95          final JexlPermissions named = JexlPermissions.RESTRICTED.logging("jexl.test.perms");
96          assertTrue(named instanceof JexlPermissions.LoggingPermissions);
97          // the wrapper still returns the delegate's verdict
98          assertTrue(named.allow(String.class));
99          assertFalse(named.allow(java.lang.Runtime.class));
100     }
101 }