You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2022/02/03 16:55:39 UTC

[commons-jexl] branch master updated: JEXL-357: added permissions parser; - added a (static) 'secure' permissions (used in tests); - quiesce spotbugs on builder patterns;

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 0b1cbdb  JEXL-357: added permissions parser; - added a (static) 'secure' permissions (used in tests); - quiesce spotbugs on builder patterns;
     new 6ad1244  Merge remote-tracking branch 'origin/master'
0b1cbdb is described below

commit 0b1cbdbe8aa7d98d71dbfe29279240adde8c136f
Author: henrib <he...@apache.org>
AuthorDate: Thu Feb 3 17:55:23 2022 +0100

    JEXL-357: added permissions parser;
    - added a (static) 'secure' permissions (used in tests);
    - quiesce spotbugs on builder patterns;
---
 src/main/config/findbugs-exclude-filter.xml        |  16 +
 .../jexl3/internal/introspection/ClassMap.java     |  53 ++-
 .../jexl3/internal/introspection/Introspector.java |  13 +-
 .../jexl3/internal/introspection/Permissions.java  | 413 ++++++++++++++-------
 ...ermissionParser.java => PermissionsParser.java} | 102 +++--
 .../jexl3/internal/introspection/Uberspect.java    |   5 +-
 .../jexl3/introspection/JexlPermissions.java       |  65 ++++
 .../org/apache/commons/jexl3/JexlTestCase.java     |   6 +-
 .../apache/commons/jexl3/PropertyAccessTest.java   |  10 +-
 .../introspection/MiscIntrospectionTest.java       | 127 +------
 ...{MiscIntrospectionTest.java => NoJexlTest.java} |  97 ++---
 ...IntrospectionTest.java => PermissionsTest.java} | 148 ++++----
 .../apache/commons/jexl3/jexl342/OptionalTest.java |   4 +
 13 files changed, 595 insertions(+), 464 deletions(-)

diff --git a/src/main/config/findbugs-exclude-filter.xml b/src/main/config/findbugs-exclude-filter.xml
index 2f70690..18f97c3 100644
--- a/src/main/config/findbugs-exclude-filter.xml
+++ b/src/main/config/findbugs-exclude-filter.xml
@@ -51,4 +51,20 @@
     <Match>
         <Class name="org.apache.commons.jexl3.parser.SimpleNode"/>
     </Match>
+    <Match>
+        <Class name="org.apache.commons.jexl3.JexlBuilder"/>
+        <Bug code="EI2,EI"></Bug>
+    </Match>
+    <Match>
+        <Package name="org.apache.commons.jexl3.internal"/>
+        <Bug code="EI2,EI"></Bug>
+    </Match>
+    <Match>
+        <Package name="org.apache.commons.jexl3.introspection.internal"/>
+        <Bug code="EI2,EI"></Bug>
+    </Match>
+    <Match>
+        <Package name="org.apache.commons.jexl3.parser"/>
+        <Bug code="EI2,EI"></Bug>
+    </Match>
 </FindBugsFilter>
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
index 5f435b8..770342b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -16,21 +16,21 @@
  */
 package org.apache.commons.jexl3.internal.introspection;
 
+import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.apache.commons.logging.Log;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 /**
  * A cache of introspection information for a specific class instance.
@@ -77,17 +77,50 @@ final class ClassMap {
      * </p>
      * Uses ConcurrentMap since 3.0, marginally faster than 2.1 under contention.
      */
-    private final ConcurrentMap<MethodKey, Method> byKey = new ConcurrentHashMap<>();
+    private final Map<MethodKey, Method> byKey ;
     /**
      * Keep track of all methods with the same name; this is not modified after creation.
      */
-    private final Map<String, Method[]> byName = new HashMap<>();
+    private final Map<String, Method[]> byName;
     /**
      * Cache of fields.
      */
     private final Map<String, Field> fieldCache;
 
     /**
+     * Singleton for permissions non-allowed classes.
+     */
+    private static final ClassMap EMPTY = new ClassMap();
+
+    /**
+     * @return the empty classmap instance
+     */
+    static ClassMap empty() {
+        return EMPTY;
+    }
+
+    /**
+     * Empty map.
+     */
+    private ClassMap() {
+        this.byKey = Collections.unmodifiableMap(new AbstractMap<MethodKey, Method>() {
+            @Override
+            public String toString() {
+                return "emptyClassMap{}";
+            }
+            @Override
+            public Set<Entry<MethodKey, Method>> entrySet() {
+                return null;
+            }
+            @Override public Method get(Object name) {
+                return CACHE_MISS;
+            }
+        });
+        this.byName = Collections.emptyMap();
+        this.fieldCache = Collections.emptyMap();
+    }
+
+    /**
      * Standard constructor.
      *
      * @param aClass      the class to deconstruct.
@@ -95,7 +128,9 @@ final class ClassMap {
      * @param log         the logger.
      */
     @SuppressWarnings("LeakingThisInConstructor")
-    ClassMap(final Class<?> aClass, final Permissions permissions, final Log log) {
+    ClassMap(final Class<?> aClass, final JexlPermissions permissions, final Log log) {
+        this.byKey = new ConcurrentHashMap<>();
+        this.byName = new HashMap<>();
         // eagerly cache methods
         create(this, permissions, aClass, log);
         // eagerly cache public fields
@@ -211,7 +246,7 @@ final class ClassMap {
      * @param clazz          the class to cache
      * @param log            the Log
      */
-    private static void create(final ClassMap cache, final Permissions permissions, Class<?> clazz, final Log log) {
+    private static void create(final ClassMap cache, final JexlPermissions permissions, Class<?> clazz, final Log log) {
         //
         // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
         // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
@@ -263,7 +298,7 @@ final class ClassMap {
      * @param log         the Log
      */
     private static void populateWithInterface(final ClassMap cache,
-                                              final Permissions permissions,
+                                              final JexlPermissions permissions,
                                               final Class<?> iface,
                                               final Log log) {
         if (Modifier.isPublic(iface.getModifiers())) {
@@ -284,7 +319,7 @@ final class ClassMap {
      * @param log         the Log
      */
     private static void populateWithClass(final ClassMap cache,
-                                          final Permissions permissions,
+                                          final JexlPermissions permissions,
                                           final Class<?> clazz,
                                           final Log log) {
         try {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index 8054ba5..ee534da 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl3.internal.introspection;
 
+import org.apache.commons.jexl3.introspection.JexlPermissions;
 import org.apache.commons.logging.Log;
 
 import java.lang.reflect.Constructor;
@@ -69,7 +70,7 @@ public final class Introspector {
     /**
      * The permissions.
      */
-    private final Permissions permissions;
+    private final JexlPermissions permissions;
     /**
      * The read/write lock.
      */
@@ -93,7 +94,7 @@ public final class Introspector {
      * @param cloader the class loader
      */
     public Introspector(final Log log, final ClassLoader cloader) {
-        this(log, cloader, null);
+        this(log, cloader, Permissions.DEFAULT);
     }
 
     /**
@@ -102,10 +103,10 @@ public final class Introspector {
      * @param cloader the class loader
      * @param perms the permissions
      */
-    public Introspector(final Log log, final ClassLoader cloader, final Permissions perms) {
+    public Introspector(final Log log, final ClassLoader cloader, final JexlPermissions perms) {
         this.logger = log;
         this.loader = cloader;
-        this.permissions = perms != null? perms : Permissions.DEFAULT;
+        this.permissions = perms != null? perms : Permissions.SECURE;
     }
 
     /**
@@ -312,7 +313,9 @@ public final class Introspector {
                 // try again
                 classMap = classMethodMaps.get(c);
                 if (classMap == null) {
-                    classMap = new ClassMap(c, permissions, logger);
+                    classMap = permissions.allow(c)
+                            ? new ClassMap(c, permissions, logger)
+                            : ClassMap.empty();
                     classMethodMaps.put(c, classMap);
                 }
             } finally {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
index 7f1ffae..3df5385 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Permissions.java
@@ -29,20 +29,25 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.commons.jexl3.annotations.NoJexl;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
 
 /**
  * Checks whether an element (ctor, field or method) is visible by JEXL introspection.
  * Default implementation does this by checking if element has been annotated with NoJexl.
+ *
+ * The NoJexl annotation allows a fine grain permissions on executable objects (methods, fields, constructors).
+ * NoJexl of a package implies all classes (including derived classes) and all interfaces
+ * of that package are invisible to Jexl;
+ * NoJexl on a class implies this class and all its derived classes are invisible to Jexl;
+ * NoJexl on a (public) field makes it not visible as a property to Jexl;
+ * NoJexl on a constructor prevents that constructor to be used to instantiate through 'new';
+ * NoJexl on a method prevents that method and any of its overrides to be visible to Jexl;
+ * NoJexl on an interface prevents all methods of that interface and their overrides to be visible to Jexl.
+ *
+ * It is possible to further refine permissions on classes used through libraries where source code form can
+ * not be altered. The @link(PermissionsParser).
  */
-public class Permissions {
-    /** Allow inheritance. */
-    protected Permissions() {
-        packages = Collections.emptyMap();
-    }
-    /**
-     * The default singleton.
-     */
-    public static final Permissions DEFAULT = new Permissions();
+public class Permissions implements JexlPermissions {
 
     /**
      * Equivalent of @NoJexl on a class in a package.
@@ -72,7 +77,46 @@ public class Permissions {
         }
 
         NoJexlClass getNoJexl(Class<?> clazz) {
-            return nojexl.get(clazz.getName());
+            return nojexl.get(classKey(clazz));
+        }
+
+        void addNoJexl(String key, NoJexlClass njc) {
+            nojexl.put(key, njc);
+        }
+    }
+
+    /**
+     * Creates a class key joining enclosing ascendants with '$'.
+     * <p>As in <code>outer$inner</code> for <code>class outer { class inner...</code>.</p>
+     * @param clazz the clazz
+     * @return the clazz key
+     */
+    private static String classKey(final Class<?> clazz) {
+        return classKey(clazz, null);
+    }
+
+    /**
+     * Creates a class key joining enclosing ascendants with '$'.
+     * <p>As in <code>outer$inner</code> for <code>class outer { class inner...</code>.</p>
+     * @param clazz the clazz
+     * @param strb the buffer to compose the key
+     * @return the clazz key
+     */
+    private static String classKey(final Class<?> clazz, final StringBuilder strb) {
+        StringBuilder keyb = strb;
+        Class<?> outer = clazz.getEnclosingClass();
+        if (outer != null) {
+            if (keyb == null) {
+                keyb = new StringBuilder();
+            }
+            classKey(outer, keyb);
+            keyb.append('$');
+        }
+        if (keyb != null) {
+            keyb.append(clazz.getSimpleName());
+            return keyb.toString();
+        } else {
+            return clazz.getSimpleName();
         }
     }
 
@@ -90,6 +134,8 @@ public class Permissions {
             fieldNames = fields;
         }
 
+        boolean isEmpty() { return methodNames.isEmpty() && fieldNames.isEmpty(); }
+
         NoJexlClass() {
             this(new HashSet<>(), new HashSet<>());
         }
@@ -102,8 +148,8 @@ public class Permissions {
             return methodNames.contains(method.getName());
         }
 
-        boolean deny(Constructor method) {
-            return methodNames.contains(method.getName());
+        boolean deny(Constructor<?> method) {
+            return methodNames.contains(method.getDeclaringClass().getSimpleName());
         }
     }
 
@@ -113,12 +159,14 @@ public class Permissions {
         boolean deny(Field field) {
             return true;
         }
+
         @Override
         boolean deny(Method method) {
             return true;
         }
+
         @Override
-        boolean deny(Constructor method) {
+        boolean deny(Constructor<?> method) {
             return true;
         }
     };
@@ -129,12 +177,14 @@ public class Permissions {
         boolean deny(Field field) {
             return false;
         }
+
         @Override
         boolean deny(Method method) {
             return false;
         }
+
         @Override
-        boolean deny(Constructor method) {
+        boolean deny(Constructor<?> method) {
             return false;
         }
     };
@@ -157,6 +207,69 @@ public class Permissions {
      * The @NoJexl execution-time map.
      */
     private final Map<String, NoJexlPackage> packages;
+    /**
+     * The closed world package patterns.
+     */
+    private final Set<String> allowed;
+
+    /** Allow inheritance. */
+    protected Permissions() {
+        this(Collections.emptySet(), Collections.emptyMap());
+    }
+
+    /**
+     * Default ctor.
+     * @param perimeter the allowed wildcard set of packages
+     * @param nojexl the NoJexl external map
+     */
+    protected Permissions(Set<String> perimeter, Map<String, NoJexlPackage> nojexl) {
+        this.allowed = perimeter;
+        this.packages = nojexl;
+    }
+
+    /**
+     * The default singleton.
+     */
+    public static final Permissions DEFAULT = new Permissions();
+
+    /**
+     * A very secure singleton.
+     */
+    public static final Permissions SECURE = new PermissionsParser().parse(
+            "# Secure Uberspect Permissions",
+            "java.nio.*",
+            "java.io.*",
+            "java.lang.*",
+            "java.math.*",
+            "java.text.*",
+            "java.util.*",
+            "org.w3c.dom.*",
+            "org.apache.commons.jexl3.*",
+            "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }",
+            "java.lang.annotation {}",
+            "java.lang.instrument {}",
+            "java.lang.invoke {}",
+            "java.lang.management {}",
+            "java.lang.ref {}",
+            "java.lang.reflect {}",
+            "java.net {}",
+            "java.io { File { } }",
+            "java.nio { Path { } Paths { } Files { } }"
+    );
+
+    /**
+     * @return the packages
+     */
+    Map<String, NoJexlPackage> getPackages() {
+        return packages == null? Collections.emptyMap() : Collections.unmodifiableMap(packages);
+    }
+
+    /**
+     * @return the wilcards
+     */
+    Set<String> getWildcards() {
+        return allowed == null? Collections.emptySet() : Collections.unmodifiableSet(allowed);
+    }
 
     /**
      * Gets the package constraints.
@@ -165,17 +278,55 @@ public class Permissions {
      */
     private NoJexlPackage getNoJexl(Package pack) {
         NoJexlPackage njp = packages.get(pack.getName());
-        return njp == null? JEXL_PACKAGE : njp;
+        if (njp != null) {
+            return njp;
+        }
+        return JEXL_PACKAGE;
     }
 
     /**
      * Gets the class constraints.
+     * <p>If nothing was explicitly forbidden, everything is allowed.</p>
      * @param clazz the class
      * @return the class constraints instance, not-null.
      */
     private NoJexlClass getNoJexl(Class<?> clazz) {
         NoJexlPackage njp = getNoJexl(clazz.getPackage());
-        return njp == null? JEXL_CLASS : njp.getNoJexl(clazz);
+        if (njp != null) {
+            NoJexlClass njc = njp.getNoJexl(clazz);
+            if (njc != null) {
+                return njc;
+            }
+        }
+        return JEXL_CLASS;
+    }
+
+    /**
+     * Whether the wilcard set of packages allows a given class to be introspected.
+     * @param clazz the package name (not null)
+     * @return true if allowed, false otherwise
+     */
+    private boolean wildcardAllow(Class<?> clazz) {
+        return wildcardAllow(allowed, clazz.getPackage().getName());
+    }
+
+    /**
+     * Whether the wilcard set of packages allows a given package to be introspected.
+     * @param allowed the allowed set (not null, may be empty)
+     * @param name the package name (not null)
+     * @return true if allowed, false otherwise
+     */
+    static boolean wildcardAllow(Set<String> allowed, String name) {
+        // allowed packages are explicit in this case
+        boolean found = allowed == null || allowed.isEmpty() || allowed.contains(name);
+        if (!found) {
+            String wildcard = name;
+            for (int i = name.length(); !found && i > 0; i = wildcard.lastIndexOf('.')) {
+                wildcard = wildcard.substring(0, i);
+                found = allowed.contains(wildcard + ".*");
+            }
+        }
+        return found;
     }
 
     /**
@@ -184,17 +335,33 @@ public class Permissions {
      * @return true if denied, false otherwise
      */
     private boolean deny(Package pack) {
+        // is package annotated with nojexl ?
+        final NoJexl nojexl = pack.getAnnotation(NoJexl.class);
+        if (nojexl != null) {
+            return true;
+        }
         return Objects.equals(NOJEXL_PACKAGE, packages.get(pack.getName()));
     }
 
     /**
      * Whether a whole class is denied Jexl visibility.
+     * <p>Also checks package visibility.</p>
      * @param clazz the class
      * @return true if denied, false otherwise
      */
     private boolean deny(Class<?> clazz) {
-        NoJexlPackage njp = packages.get(clazz.getPackage().getName());
-        return njp != null && NOJEXL_CLASS.equals(njp.getNoJexl(clazz));
+        // dont deny arrays
+        if (clazz.isArray()) {
+            return false;
+        }
+        // is clazz annotated with nojexl ?
+        final NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
+        if (nojexl != null) {
+            return true;
+        }
+        Package pkg = clazz.getPackage();
+        NoJexlPackage njp = packages.get(pkg.getName());
+        return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz));
     }
 
     /**
@@ -203,6 +370,15 @@ public class Permissions {
      * @return true if denied, false otherwise
      */
     private boolean deny(Constructor<?> ctor) {
+        // only public
+        if (!Modifier.isPublic(ctor.getModifiers())) {
+            return true;
+        }
+        // is ctor annotated with nojexl ?
+        final NoJexl nojexl = ctor.getAnnotation(NoJexl.class);
+        if (nojexl != null) {
+            return true;
+        }
         return getNoJexl(ctor.getDeclaringClass()).deny(ctor);
     }
 
@@ -212,6 +388,15 @@ public class Permissions {
      * @return true if denied, false otherwise
      */
     private boolean deny(Field field) {
+        // only public
+        if (!Modifier.isPublic(field.getModifiers())) {
+            return true;
+        }
+        // is field annotated with nojexl ?
+        final NoJexl nojexl = field.getAnnotation(NoJexl.class);
+        if (nojexl != null) {
+            return true;
+        }
         return getNoJexl(field.getDeclaringClass()).deny(field);
     }
 
@@ -221,6 +406,15 @@ public class Permissions {
      * @return true if denied, false otherwise
      */
     private boolean deny(Method method) {
+        // only public
+        if (!Modifier.isPublic(method.getModifiers())) {
+            return true;
+        }
+        // is method annotated with nojexl ?
+        final NoJexl nojexl = method.getAnnotation(NoJexl.class);
+        if (nojexl != null) {
+            return true;
+        }
         return getNoJexl(method.getDeclaringClass()).deny(method);
     }
 
@@ -229,11 +423,9 @@ public class Permissions {
      * @param pack the package
      * @return true if JEXL is allowed to introspect, false otherwise
      */
+    @Override
     public boolean allow(final Package pack) {
-        if (pack == null || pack.getAnnotation(NoJexl.class) != null || deny(pack)) {
-            return false;
-        }
-        return true;
+       return pack != null && !deny(pack);
     }
 
     /**
@@ -242,8 +434,24 @@ public class Permissions {
      * @param clazz the class to check
      * @return true if JEXL is allowed to introspect, false otherwise
      */
+    @Override
     public boolean allow(final Class<?> clazz) {
-        return clazz != null && allow(clazz.getPackage()) && allow(clazz, true);
+        if (clazz == null) {
+            return false;
+        }
+        // class must be allowed
+        if (deny(clazz)) {
+            return false;
+        }
+        // all super classes must be allowed
+        Class<?> walk = clazz.getSuperclass();
+        while (walk != null) {
+            if (deny(walk)) {
+                return false;
+            }
+            walk = walk.getSuperclass();
+        }
+        return true;
     }
 
     /**
@@ -251,27 +459,22 @@ public class Permissions {
      * @param ctor the constructor to check
      * @return true if JEXL is allowed to introspect, false otherwise
      */
+    @Override
     public boolean allow(final Constructor<?> ctor) {
         if (ctor == null) {
             return false;
         }
-        if (!Modifier.isPublic(ctor.getModifiers())) {
+        // check declared restrictions
+        if (deny(ctor)) {
             return false;
         }
+        // class must agree
         final Class<?> clazz = ctor.getDeclaringClass();
-        if (!allow(clazz, false)) {
-            return false;
-        }
-        // is ctor annotated with nojexl ?
-        final NoJexl nojexl = ctor.getAnnotation(NoJexl.class);
-        if (nojexl != null) {
+        if (deny(clazz)) {
             return false;
         }
-        // check added restrictions
-        if (deny(ctor)) {
-            return false;
-        }
-        return true;
+        // check wildcards
+        return wildcardAllow(clazz);
     }
 
     /**
@@ -279,27 +482,22 @@ public class Permissions {
      * @param field the field to check
      * @return true if JEXL is allowed to introspect, false otherwise
      */
+    @Override
     public boolean allow(final Field field) {
         if (field == null) {
             return false;
         }
-        if (!Modifier.isPublic(field.getModifiers())) {
+        // check declared restrictions
+        if (deny(field)) {
             return false;
         }
+        // class must agree
         final Class<?> clazz = field.getDeclaringClass();
-        if (!allow(clazz, false)) {
-            return false;
-        }
-        // is field annotated with nojexl ?
-        final NoJexl nojexl = field.getAnnotation(NoJexl.class);
-        if (nojexl != null) {
+        if (deny(clazz)) {
             return false;
         }
-        // check added restrictions
-        if (deny(field)) {
-            return false;
-        }
-        return true;
+        // check wildcards
+        return wildcardAllow(clazz);
     }
 
     /**
@@ -309,31 +507,21 @@ public class Permissions {
      * @param method the method to check
      * @return true if JEXL is allowed to introspect, false otherwise
      */
+    @Override
     public boolean allow(final Method method) {
         if (method == null) {
             return false;
         }
-        if (!Modifier.isPublic(method.getModifiers())) {
+        // method must be allowed
+        if (!allowMethod(method)) {
             return false;
         }
-        // is method annotated with nojexl ?
-        NoJexl nojexl = method.getAnnotation(NoJexl.class);
-        if (nojexl != null) {
-            return false;
-        }
-        // is the class annotated with nojexl ?
         Class<?> clazz = method.getDeclaringClass();
-        nojexl = clazz.getAnnotation(NoJexl.class);
-        if (nojexl != null) {
-            return false;
-        }
-        // check added restrictions
-        if (deny(method)) {
-            return false;
-        }
+        // gather if any implementation of the method is explicitly allowed by the packages
+        boolean[] explicit = new boolean[]{wildcardAllow(clazz)};
         // lets walk all interfaces
         for (final Class<?> inter : clazz.getInterfaces()) {
-            if (!allow(inter, method)) {
+            if (!allow(inter, method, explicit)) {
                 return false;
             }
         }
@@ -341,95 +529,58 @@ public class Permissions {
         clazz = clazz.getSuperclass();
         // walk all superclasses
         while (clazz != null) {
-            if (!allow(clazz, method)) {
+            if (!allow(clazz, method, explicit)) {
                 return false;
             }
             clazz = clazz.getSuperclass();
         }
-        return true;
+        return explicit[0];
     }
 
     /**
-     * Checks whether a class or one of its superclasses or implemented interfaces
-     * explicitly disallows JEXL introspection.
-     * @param clazz the class to check
-     * @param interf whether interfaces should be checked as well
-     * @return true if JEXL is allowed to introspect, false otherwise
+     * Checks whether a method is allowed.
+     * @param method the method
+     * @return true if it has not been disallowed through annotation or declaration
      */
-    protected boolean allow(final Class<?> clazz, final boolean interf) {
-        if (clazz == null) {
+    private boolean allowMethod(final Method method) {
+        // check declared restrictions
+        if (deny(method)) {
             return false;
         }
-        if (!Modifier.isPublic(clazz.getModifiers())) {
+        Class<?> clazz = method.getDeclaringClass();
+        // class must be allowed
+        if (deny(clazz)) {
             return false;
         }
-        // lets walk all interfaces
-        if (interf) {
-            for (final Class<?> inter : clazz.getInterfaces()) {
-                // is clazz annotated with nojexl ?
-                final NoJexl nojexl = inter.getAnnotation(NoJexl.class);
-                if (nojexl != null) {
-                    return false;
-                }
-                // check added restrictions
-                if (deny(inter)) {
-                    return false;
-                }
-            }
-        }
-        // lets walk all super classes
-        Class<?> walk = clazz.getSuperclass();
-        // walk all superclasses
-        while (walk != null) {
-            // is clazz annotated with nojexl ?
-            final NoJexl nojexl = walk.getAnnotation(NoJexl.class);
-            if (nojexl != null) {
-                return false;
-            }
-            // check added restrictions
-            if (deny(walk)) {
-                return false;
-            }
-            walk = walk.getSuperclass();
-        }
         return true;
     }
 
     /**
-     * Check whether a method is allowed to be JEXL introspected in all its
-     * superclasses and interfaces.
-     * @param clazz the class
+     * Check whether a method is allowed to be introspected in one superclass or interface.
+     * @param clazz the superclass or interface to check
      * @param method the method
+     * @param explicit carries whether the package holding the method is explicitly allowed
      * @return true if JEXL is allowed to introspect, false otherwise
      */
-    protected boolean allow(final Class<?> clazz, final Method method) {
-        if (clazz != null) {
-            try {
-                // check if method in that class is different from the method argument
-                final Method wmethod = clazz.getMethod(method.getName(), method.getParameterTypes());
-                if (wmethod != null) {
-                    NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
-                    if (nojexl != null) {
-                        return false;
-                    }
-                    // check if parent declaring class said nojexl on that method (transitivity)
-                    nojexl = wmethod.getAnnotation(NoJexl.class);
-                    if (nojexl != null) {
-                        return false;
-                    }
-                    // check added restrictions
-                    if (deny(wmethod)) {
-                        return false;
-                    }
-                }
-            } catch (final NoSuchMethodException ex) {
-                // unexpected, return no
-                return true;
-            } catch (final SecurityException ex) {
-                // unexpected, can't do much
+    private boolean allow(final Class<?> clazz, final Method method, boolean[] explicit) {
+        try {
+            // check if method in that class is declared ie overrides
+            final Method override = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
+            // should not be possible...
+            if (!allowMethod(override)) {
                 return false;
             }
+            // explicit |= ...
+            if (!explicit[0]) {
+                explicit[0] = wildcardAllow(clazz);
+            }
+            return true;
+        } catch (final NoSuchMethodException ex) {
+            // will happen if not overriding method in clazz
+            return true;
+        } catch (final SecurityException ex) {
+            // unexpected, can't do much
+            return false;
         }
-        return true;
     }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionParser.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
similarity index 75%
rename from src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionParser.java
rename to src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
index 25e6f29..156c7bf 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionParser.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/PermissionsParser.java
@@ -17,13 +17,20 @@
 
 package org.apache.commons.jexl3.internal.introspection;
 
+import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * Parses permissions akin to NoJexl annotations.
- * The syntax only recognizes packages, classes (and inner classes), methods and fields.
- *
+ * A crude parser to configure permissions akin to NoJexl annotations.
+ * The syntax recognizes 2 types of permissions:
+ * - restricting access to packages, classes (and inner classes), methods and fields
+ * - allowing access to a wildcard restricted set of packages
+ *  Example:
+ *  my.allowed.packages.*
+ *  another.allowed.package.*
+ *  # nojexl like restrictions
  *  my.package {
  *   class0 {...
  *     class1 {...}
@@ -32,44 +39,52 @@ import java.util.concurrent.ConcurrentHashMap;
  *         class3 {}
  *     }
  *     # and eol comment
- *     class0(); // constructors
- *     method(); // method
- *     field; // field
- *   } // end class0
- * } // end package my.package
+ *     class0(); # constructors
+ *     method(); # method
+ *     field; # field
+ *   } # end class0
+ * } # end package my.package
  *
  */
-public class PermissionParser {
+public class PermissionsParser {
     /** The source. */
     private String src;
     /** The source size. */
     private int size;
     /** The @NoJexl execution-time map. */
     private Map<String, Permissions.NoJexlPackage> packages;
+    /** The set of wildcard imports. */
+    private Set<String> wildcards;
 
     /**
      * Basic ctor.
      */
-    PermissionParser() {
+    public PermissionsParser() {
     }
 
-    void clear() {
-        src = null; size = 0; packages = null;
+    /**
+     * Clears this parser internals.
+     */
+    public void clear() {
+        src = null; size = 0; packages = null; wildcards = null;
     }
 
     /**
      * Parses permissions from a source.
-     * @param src the source
+     * @param srcs the sources
      * @return the permissions map
      */
-    Map<String, Permissions.NoJexlPackage> parse(String src) {
+    public Permissions parse(String... srcs) {
         packages = new ConcurrentHashMap<>();
-        this.src = src;
-        this.size = src.length();
-        readPackages();
-        Map<String, Permissions.NoJexlPackage> map = packages;
+        wildcards = new LinkedHashSet<>();
+        for(String src : srcs) {
+            this.src = src;
+            this.size = src.length();
+            readPackages();
+        }
+        Permissions permissions = new Permissions(wildcards, packages);
         clear();
-        return map;
+        return permissions;
     }
 
     /**
@@ -96,12 +111,11 @@ public class PermissionParser {
             }
             i += 1;
         }
-        return offset;
+        return i;
     }
 
     /**
      * Reads spaces.
-     * @param offset
      * @param offset initial position
      * @return position after spaces
      */
@@ -109,7 +123,7 @@ public class PermissionParser {
         int i = offset;
         while (i < size) {
             char c = src.charAt(i);
-            if (!Character.isSpaceChar(c)) {
+            if (!Character.isWhitespace(c)) {
                 break;
             }
             i += 1;
@@ -121,10 +135,21 @@ public class PermissionParser {
      * Reads an identifier (optionally dot-separated).
      * @param id the builder to fill the identifier character with
      * @param offset the initial reading position
+     * @return the position after the identifier
+     */
+    private int readIdentifier(StringBuilder id, int offset) {
+        return readIdentifier(id, offset, false, false);
+    }
+
+    /**
+     * Reads an identifier (optionally dot-separated).
+     * @param id the builder to fill the identifier character with
+     * @param offset the initial reading position
      * @param dot whether dots (.) are allowed
+     * @param star whether stars (*) are allowed
      * @return the position after the identifier
      */
-    private int readIdentifier(StringBuilder id, int offset, boolean dot) {
+    private int readIdentifier(StringBuilder id, int offset, boolean dot, boolean star) {
         int begin = -1;
         int i = offset;
         char c = 0;
@@ -140,8 +165,10 @@ public class PermissionParser {
                 if (src.charAt(i - 1) == '.') {
                     throw new IllegalStateException(unexpected(c, i));
                 }
-                id.append(c);
+                id.append('.');
                 begin = -1;
+            } else if (star && c == '*') {
+                id.append('*');
             } else {
                 break;
             }
@@ -163,7 +190,7 @@ public class PermissionParser {
         int i = 0;
         int j = -1;
         String pname = null;
-        for (; i < size; ) {
+        while (i < size) {
             char c = src.charAt(i);
             // if no parsing progress can be made, we are in error
             if (j < i) {
@@ -172,7 +199,7 @@ public class PermissionParser {
                 throw new IllegalStateException(unexpected(c, i));
             }
             // get rid of space
-            if (Character.isSpaceChar(c)) {
+            if (Character.isWhitespace(c)) {
                 i = readSpaces(i + 1);
                 continue;
             }
@@ -183,11 +210,16 @@ public class PermissionParser {
             }
             // read the package qualified name
             if (pname == null) {
-                int next = readIdentifier(temp, i, true);
+                int next = readIdentifier(temp, i, true, true);
                 if (i != next) {
                     pname = temp.toString();
                     temp.setLength(0);
                     i = next;
+                    // consume it if it is a wildcard decl
+                    if (pname.endsWith(".*")) {
+                        wildcards.add(pname);
+                        pname = null;
+                    }
                     continue;
                 }
             }
@@ -235,7 +267,7 @@ public class PermissionParser {
                 throw new IllegalStateException(unexpected(c, i));
             }
             // get rid of space
-            if (Character.isSpaceChar(c)) {
+            if (Character.isWhitespace(c)) {
                 i = readSpaces(i + 1);
                 continue;
             }
@@ -246,7 +278,7 @@ public class PermissionParser {
             }
             // read an identifier, the class name
             if (identifier == null) {
-                int next = readIdentifier(temp, i, false);
+                int next = readIdentifier(temp, i);
                 if (i != next) {
                     identifier = temp.toString();
                     temp.setLength(0);
@@ -254,6 +286,10 @@ public class PermissionParser {
                     continue;
                 } else if (c == '}') {
                     i += 1;
+                    // restrict the whole class
+                    if (njname != null && njclass.isEmpty()) {
+                        njpackage.addNoJexl(njname, Permissions.NOJEXL_CLASS);
+                    }
                     break;
                 }
             }
@@ -264,7 +300,7 @@ public class PermissionParser {
                     // if we have a class, it has a name
                     njclass = new Permissions.NoJexlClass();
                     njname = outer != null ? outer + "$" + identifier : identifier;
-                    njpackage.nojexl.put(njname, njclass);
+                    njpackage.addNoJexl(njname, njclass);
                     identifier = null;
                     i += 1;
                 } else {
@@ -275,6 +311,7 @@ public class PermissionParser {
                 if (c == '{') {
                     // inner class
                     i = readClass(njpackage, njname, identifier, i - 1);
+                    identifier = null;
                 } else if (c == ';') {
                     // field or method?
                     if (isMethod) {
@@ -283,7 +320,6 @@ public class PermissionParser {
                     } else {
                         njclass.fieldNames.add(identifier);
                     }
-                    isMethod = false;
                     identifier = null;
                     i += 1;
                 } else if (c == '(' && !isMethod) {
@@ -293,6 +329,10 @@ public class PermissionParser {
                 } else if (c == ')' && src.charAt(i - 1) == '(') {
                     i += 1;
                 } else if (c == '}') {
+                    // restrict the whole class
+                    if (njname != null && njclass.isEmpty()) {
+                        njpackage.addNoJexl(njname, Permissions.NOJEXL_CLASS);
+                    }
                     i += 1;
                     break;
                 } else {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 6125ebf..64bf0d6 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -25,6 +25,7 @@ import org.apache.commons.jexl3.introspection.JexlPropertySet;
 import org.apache.commons.jexl3.introspection.JexlUberspect;
 
 import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -88,9 +89,9 @@ public class Uberspect implements JexlUberspect {
      * @param perms the introspector permissions
      */
     public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final Permissions perms) {
-        logger = runtimeLogger;
+        logger = runtimeLogger == null? LogFactory.getLog(JexlEngine.class) : runtimeLogger;
         strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty;
-        permissions  = perms;
+        permissions = perms == null? Permissions.DEFAULT : perms;
         ref = new SoftReference<Introspector>(null);
         loader = new SoftReference<ClassLoader>(getClass().getClassLoader());
         operatorMap = new ConcurrentHashMap<Class<? extends JexlArithmetic>, Set<JexlOperator>>();
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
new file mode 100644
index 0000000..0b21639
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.introspection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * A JEXL dedicated 'security manager' that constraints which packages/classes/constructors/fields/methods
+ * are made visible to JEXL scripts.
+ */
+public interface JexlPermissions {
+    /**
+     * Checks whether a package explicitly disallows JEXL introspection.
+     * @param pack the package
+     * @return true if JEXL is allowed to introspect, false otherwise
+     */
+    boolean allow(final Package pack);
+
+    /**
+     * Checks whether a class or one of its super-classes or implemented interfaces
+     * explicitly disallows JEXL introspection.
+     * @param clazz the class to check
+     * @return true if JEXL is allowed to introspect, false otherwise
+     */
+    boolean allow(final Class<?> clazz);
+
+    /**
+     * Checks whether a constructor explicitly disallows JEXL introspection.
+     * @param ctor the constructor to check
+     * @return true if JEXL is allowed to introspect, false otherwise
+     */
+    boolean allow(final Constructor<?> ctor);
+
+    /**
+     * Checks whether a method explicitly disallows JEXL introspection.
+     * <p>Since methods can be overridden, this also checks that no superclass or interface
+     * explicitly disallows this methods.</p>
+     * @param method the method to check
+     * @return true if JEXL is allowed to introspect, false otherwise
+     */
+    boolean allow(final Method method);
+
+    /**
+     * Checks whether a field explicitly disallows JEXL introspection.
+     * @param field the field to check
+     * @return true if JEXL is allowed to introspect, false otherwise
+     */
+    boolean allow(final Field field);
+}
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
index 7683d6a..d9f515a 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java
@@ -22,6 +22,8 @@ import java.lang.reflect.Method;
 
 
 import org.apache.commons.jexl3.internal.Util;
+import org.apache.commons.jexl3.internal.introspection.Permissions;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import org.junit.After;
 import org.junit.Assert;
 
@@ -67,7 +69,9 @@ public class JexlTestCase {
     }
 
     public static JexlEngine createEngine(final boolean lenient) {
-        return new JexlBuilder().arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
+        return new JexlBuilder()
+                .uberspect(new Uberspect(null, null, Permissions.SECURE))
+                .arithmetic(new JexlArithmetic(!lenient)).cache(128).create();
     }
 
     /**
diff --git a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
index cd12059..7883c0a 100644
--- a/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
@@ -21,7 +21,11 @@ import java.util.Map;
 
 import org.apache.commons.jexl3.internal.Debugger;
 import org.apache.commons.jexl3.internal.introspection.IndexedType;
+import org.apache.commons.jexl3.internal.introspection.Permissions;
+import org.apache.commons.jexl3.internal.introspection.Uberspect;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
 import org.apache.commons.jexl3.junit.Asserter;
+import org.apache.commons.logging.LogFactory;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -379,7 +383,9 @@ public class PropertyAccessTest extends JexlTestCase {
         final HashMap<Object, Object> x = new HashMap<Object, Object>();
         x.put(2, "123456789");
         ctx.set("x", x);
-        final JexlEngine engine = new JexlBuilder().strict(true).silent(false).create();
+        final JexlEngine engine = new JexlBuilder()
+                .uberspect(new Uberspect(null, null, null))
+                .strict(true).silent(false).create();
         String stmt = "x.2.class.name";
         JexlScript script = engine.createScript(stmt);
         Object result = script.execute(ctx);
@@ -551,4 +557,4 @@ public class PropertyAccessTest extends JexlTestCase {
         Assert.assertEquals(42, result);
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
index ab3aeb3..69549db 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
@@ -26,8 +26,10 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
 /**
  * Checks the CacheMap.MethodKey implementation
@@ -95,129 +97,4 @@ public class MiscIntrospectionTest {
         Assert.assertEquals(-1, alw.indexOf(null));
     }
 
-    public static class A {
-        public int i;
-        public A() {}
-        public int method() { return 0; }
-    }
-
-    @NoJexl
-    public interface InterNoJexl0 {
-        int method();
-    }
-
-    public interface InterNoJexl1 {
-        @NoJexl
-        int method();
-    }
-
-
-    public static class A0 extends A implements InterNoJexl0 {
-        @NoJexl public int i0;
-        @NoJexl public A0() {}
-        @Override public int method() { return 1; }
-    }
-
-    public static class A1 extends A implements InterNoJexl1 {
-        private int i1;
-        @NoJexl public A1() {}
-        @Override public int method() { return 2; }
-    }
-
-    @NoJexl
-    public static class A2 extends A  {
-        public A2() {}
-        @Override public int method() { return 3; }
-    }
-
-    protected static class A3 {
-        protected int i3;
-        protected A3() {}
-        int method() { return 4; }
-    }
-
-    public static class A5 implements InterNoJexl5 {
-        public A5() {}
-        @Override public int method() { return 0; }
-    }
-
-    @NoJexl
-    public interface InterNoJexl5 {
-        int method();
-    }
-
-    @Test
-    public void testPermissions() throws Exception {
-        Permissions p = Permissions.DEFAULT;
-        Assert.assertFalse(p.allow((Field) null));
-        Assert.assertFalse(p.allow((Package) null));
-        Assert.assertFalse(p.allow((Method) null));
-        Assert.assertFalse(p.allow((Constructor<?>) null));
-        Assert.assertFalse(p.allow((Class<?>) null));
-
-        Assert.assertTrue(p.allow(A2.class));
-        Assert.assertFalse(p.allow(A3.class));
-        Assert.assertFalse(p.allow(A5.class));
-
-        Method mA = A.class.getMethod("method");
-        Assert.assertNotNull(mA);
-        Method mA0 = A0.class.getMethod("method");
-        Assert.assertNotNull(mA0);
-        Method mA1 = A1.class.getMethod("method");
-        Assert.assertNotNull(mA1);
-        Method mA2 = A2.class.getMethod("method");
-        Assert.assertNotNull(mA1);
-        Method mA3 = A2.class.getDeclaredMethod("method");
-        Assert.assertNotNull(mA1);
-
-        Assert.assertTrue(p.allow(mA));
-        Assert.assertFalse(p.allow(mA0));
-        Assert.assertFalse(p.allow(mA1));
-        Assert.assertFalse(p.allow(mA2));
-        Assert.assertFalse(p.allow(mA3));
-
-        Field fA = A.class.getField("i");
-        Assert.assertNotNull(fA);
-        Assert.assertTrue(p.allow(fA));
-
-        Field fA0 = A0.class.getField("i0");
-        Assert.assertNotNull(fA0);
-        Assert.assertFalse(p.allow(fA0));
-        Field fA1 = A1.class.getDeclaredField("i1");
-        Assert.assertNotNull(fA1);
-        Assert.assertFalse(p.allow(fA0));
-
-        Constructor<?> cA = A.class.getConstructor();
-        Assert.assertNotNull(cA);
-        Assert.assertTrue(p.allow(cA));
-
-        Constructor<?> cA0 = A0.class.getConstructor();
-        Assert.assertNotNull(cA0);
-        Assert.assertFalse(p.allow(cA0));
-
-        Constructor<?> cA3 = A3.class.getDeclaredConstructor();
-        Assert.assertNotNull(cA3);
-        Assert.assertFalse(p.allow(cA3));
-    }
-
-
-    @Test
-    public void testParsePermissions0() throws Exception {
-        String src = "java.lang { Runtime { exit(); } }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
-        Assert.assertNotNull(nojexlmap);
-    }
-
-
-    @Test
-    public void testParsePermissions1() throws Exception {
-        String src = "java.lang { Runtime { exit(); } }" +
-                "java.rmi {}" +
-                "java.io { File {} }" +
-                "java.nio { Path {} }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
-        Assert.assertNotNull(nojexlmap);
-    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/NoJexlTest.java
similarity index 64%
copy from src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
copy to src/test/java/org/apache/commons/jexl3/internal/introspection/NoJexlTest.java
index ab3aeb3..02d12a3 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/NoJexlTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3.internal.introspection;
 
-import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.annotations.NoJexl;
 import org.junit.Assert;
 import org.junit.Test;
@@ -24,76 +23,16 @@ import org.junit.Test;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.NoSuchElementException;
+import java.util.Set;
 
 /**
  * Checks the CacheMap.MethodKey implementation
  */
 
-public class MiscIntrospectionTest {
-    @Test
-    public void testEmptyContext() {
-        try {
-            JexlEngine.EMPTY_CONTEXT.set("nope", 42);
-            Assert.fail("empty context should be readonly");
-        } catch(UnsupportedOperationException xun) {
-            Assert.assertNotNull(xun);
-        }
-    }
-    @Test
-    public void testArrayIterator() {
-        // not on lists
-        try {
-            new ArrayIterator(new ArrayList<>());
-        } catch(IllegalArgumentException xill) {
-            Assert.assertNotNull(xill);
-        }
-        // wih null?
-        ArrayIterator ai0 = new ArrayIterator(null);
-        Assert.assertFalse(ai0.hasNext());
-        try {
-            ai0.next();
-            Assert.fail("should have failed");
-        } catch(NoSuchElementException no) {
-            Assert.assertNotNull(no);
-        }
-        // an array
-        ai0 = new ArrayIterator(new int[]{42});
-        Assert.assertTrue(ai0.hasNext());
-        Assert.assertEquals(42, ai0.next());
-        Assert.assertFalse(ai0.hasNext());
-        try {
-            ai0.next();
-            Assert.fail("iterator on null?");
-        } catch(NoSuchElementException no) {
-            Assert.assertNotNull(no);
-        }
-        // no remove
-        try {
-            ai0.remove();
-            Assert.fail("should have failed");
-        } catch(UnsupportedOperationException no) {
-            Assert.assertNotNull(no);
-        }
-    }
-
-    @Test
-    public void testArrayListWrapper() {
-        ArrayListWrapper alw ;
-        try {
-            new ArrayListWrapper(1);
-            Assert.fail("non-array wrap?");
-        } catch(IllegalArgumentException xil) {
-            Assert.assertNotNull(xil);
-        }
-        Integer[] ai = new Integer[]{1, 2};
-        alw = new ArrayListWrapper(ai);
-        Assert.assertEquals(1, alw.indexOf(2));
-        Assert.assertEquals(-1, alw.indexOf(null));
-    }
+public class NoJexlTest {
 
     public static class A {
         public int i;
@@ -155,9 +94,9 @@ public class MiscIntrospectionTest {
         Assert.assertFalse(p.allow((Constructor<?>) null));
         Assert.assertFalse(p.allow((Class<?>) null));
 
-        Assert.assertTrue(p.allow(A2.class));
-        Assert.assertFalse(p.allow(A3.class));
-        Assert.assertFalse(p.allow(A5.class));
+        Assert.assertFalse(p.allow(A2.class));
+        Assert.assertTrue(p.allow(A3.class));
+        Assert.assertTrue(p.allow(A5.class));
 
         Method mA = A.class.getMethod("method");
         Assert.assertNotNull(mA);
@@ -166,9 +105,9 @@ public class MiscIntrospectionTest {
         Method mA1 = A1.class.getMethod("method");
         Assert.assertNotNull(mA1);
         Method mA2 = A2.class.getMethod("method");
-        Assert.assertNotNull(mA1);
+        Assert.assertNotNull(mA2);
         Method mA3 = A2.class.getDeclaredMethod("method");
-        Assert.assertNotNull(mA1);
+        Assert.assertNotNull(mA3);
 
         Assert.assertTrue(p.allow(mA));
         Assert.assertFalse(p.allow(mA0));
@@ -204,8 +143,8 @@ public class MiscIntrospectionTest {
     @Test
     public void testParsePermissions0() throws Exception {
         String src = "java.lang { Runtime { exit(); } }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
+        Permissions p = new PermissionsParser().parse(src);
+        Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
         Assert.assertNotNull(nojexlmap);
     }
 
@@ -216,8 +155,20 @@ public class MiscIntrospectionTest {
                 "java.rmi {}" +
                 "java.io { File {} }" +
                 "java.nio { Path {} }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
+        Permissions p = new PermissionsParser().parse(src);
+        Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
         Assert.assertNotNull(nojexlmap);
     }
+
+    @Test
+    public void testWildCardPackages() {
+        Set<String> wildcards;
+        boolean found;
+        wildcards = new HashSet<>(Arrays.asList("com.apache.*"));
+        found = Permissions.wildcardAllow(wildcards, "com.apache.commons.jexl3");
+        Assert.assertTrue(found);
+        found = Permissions.wildcardAllow(wildcards, "com.google.spexl");
+        Assert.assertFalse(found);
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
similarity index 59%
copy from src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
copy to src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
index ab3aeb3..f53006f 100644
--- a/src/test/java/org/apache/commons/jexl3/internal/introspection/MiscIntrospectionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/internal/introspection/PermissionsTest.java
@@ -16,84 +16,22 @@
  */
 package org.apache.commons.jexl3.internal.introspection;
 
-import org.apache.commons.jexl3.JexlEngine;
-import org.apache.commons.jexl3.annotations.NoJexl;
 import org.junit.Assert;
 import org.junit.Test;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
-import java.util.NoSuchElementException;
+import java.util.Set;
 
 /**
  * Checks the CacheMap.MethodKey implementation
  */
 
-public class MiscIntrospectionTest {
-    @Test
-    public void testEmptyContext() {
-        try {
-            JexlEngine.EMPTY_CONTEXT.set("nope", 42);
-            Assert.fail("empty context should be readonly");
-        } catch(UnsupportedOperationException xun) {
-            Assert.assertNotNull(xun);
-        }
-    }
-    @Test
-    public void testArrayIterator() {
-        // not on lists
-        try {
-            new ArrayIterator(new ArrayList<>());
-        } catch(IllegalArgumentException xill) {
-            Assert.assertNotNull(xill);
-        }
-        // wih null?
-        ArrayIterator ai0 = new ArrayIterator(null);
-        Assert.assertFalse(ai0.hasNext());
-        try {
-            ai0.next();
-            Assert.fail("should have failed");
-        } catch(NoSuchElementException no) {
-            Assert.assertNotNull(no);
-        }
-        // an array
-        ai0 = new ArrayIterator(new int[]{42});
-        Assert.assertTrue(ai0.hasNext());
-        Assert.assertEquals(42, ai0.next());
-        Assert.assertFalse(ai0.hasNext());
-        try {
-            ai0.next();
-            Assert.fail("iterator on null?");
-        } catch(NoSuchElementException no) {
-            Assert.assertNotNull(no);
-        }
-        // no remove
-        try {
-            ai0.remove();
-            Assert.fail("should have failed");
-        } catch(UnsupportedOperationException no) {
-            Assert.assertNotNull(no);
-        }
-    }
-
-    @Test
-    public void testArrayListWrapper() {
-        ArrayListWrapper alw ;
-        try {
-            new ArrayListWrapper(1);
-            Assert.fail("non-array wrap?");
-        } catch(IllegalArgumentException xil) {
-            Assert.assertNotNull(xil);
-        }
-        Integer[] ai = new Integer[]{1, 2};
-        alw = new ArrayListWrapper(ai);
-        Assert.assertEquals(1, alw.indexOf(2));
-        Assert.assertEquals(-1, alw.indexOf(null));
-    }
+public class PermissionsTest {
 
     public static class A {
         public int i;
@@ -101,30 +39,29 @@ public class MiscIntrospectionTest {
         public int method() { return 0; }
     }
 
-    @NoJexl
+    //@NoJexl
     public interface InterNoJexl0 {
         int method();
     }
 
     public interface InterNoJexl1 {
-        @NoJexl
+        //@NoJexl
         int method();
     }
 
-
     public static class A0 extends A implements InterNoJexl0 {
-        @NoJexl public int i0;
-        @NoJexl public A0() {}
+        /*@NoJexl*/ public int i0;
+        /*@NoJexl*/ public A0() {}
         @Override public int method() { return 1; }
     }
 
     public static class A1 extends A implements InterNoJexl1 {
         private int i1;
-        @NoJexl public A1() {}
+        /*@NoJexl*/ public A1() {}
         @Override public int method() { return 2; }
     }
 
-    @NoJexl
+    //@NoJexl
     public static class A2 extends A  {
         public A2() {}
         @Override public int method() { return 3; }
@@ -141,23 +78,33 @@ public class MiscIntrospectionTest {
         @Override public int method() { return 0; }
     }
 
-    @NoJexl
+    //@NoJexl
     public interface InterNoJexl5 {
         int method();
     }
 
     @Test
     public void testPermissions() throws Exception {
-        Permissions p = Permissions.DEFAULT;
+
+        String src = " org.apache.commons.jexl3.internal.introspection { PermissionsTest { "+
+                "InterNoJexl0 { } "+
+                "InterNoJexl1 { method(); } "+
+                "A0 { A0(); i0; } "+
+                "A1 { A1(); } "+
+                "A2 { } "+
+                "InterNoJexl5 { } "+
+                "} }";
+
+        Permissions p = new PermissionsParser().parse(src);
         Assert.assertFalse(p.allow((Field) null));
         Assert.assertFalse(p.allow((Package) null));
         Assert.assertFalse(p.allow((Method) null));
         Assert.assertFalse(p.allow((Constructor<?>) null));
         Assert.assertFalse(p.allow((Class<?>) null));
 
-        Assert.assertTrue(p.allow(A2.class));
-        Assert.assertFalse(p.allow(A3.class));
-        Assert.assertFalse(p.allow(A5.class));
+        Assert.assertFalse(p.allow(A2.class));
+        Assert.assertTrue(p.allow(A3.class));
+        Assert.assertTrue(p.allow(A5.class));
 
         Method mA = A.class.getMethod("method");
         Assert.assertNotNull(mA);
@@ -166,9 +113,9 @@ public class MiscIntrospectionTest {
         Method mA1 = A1.class.getMethod("method");
         Assert.assertNotNull(mA1);
         Method mA2 = A2.class.getMethod("method");
-        Assert.assertNotNull(mA1);
+        Assert.assertNotNull(mA2);
         Method mA3 = A2.class.getDeclaredMethod("method");
-        Assert.assertNotNull(mA1);
+        Assert.assertNotNull(mA3);
 
         Assert.assertTrue(p.allow(mA));
         Assert.assertFalse(p.allow(mA0));
@@ -202,22 +149,53 @@ public class MiscIntrospectionTest {
 
 
     @Test
-    public void testParsePermissions0() throws Exception {
+    public void testParsePermissions0() {
         String src = "java.lang { Runtime { exit(); } }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
+        PermissionsParser pp = new PermissionsParser();
+        Permissions p = pp.parse(src);
+        Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
         Assert.assertNotNull(nojexlmap);
     }
 
 
     @Test
-    public void testParsePermissions1() throws Exception {
+    public void testParsePermissions1() {
         String src = "java.lang { Runtime { exit(); } }" +
                 "java.rmi {}" +
                 "java.io { File {} }" +
                 "java.nio { Path {} }";
-        PermissionParser pp = new PermissionParser();
-        Map<String, Permissions.NoJexlPackage> nojexlmap = pp.parse(src);
+        Permissions p = new PermissionsParser().parse(src);
+        Map<String, Permissions.NoJexlPackage> nojexlmap = p.getPackages();
         Assert.assertNotNull(nojexlmap);
     }
+
+    @Test
+    public void testWildCardPackages() {
+        Set<String> wildcards;
+        boolean found;
+        wildcards = new HashSet<>(Arrays.asList("com.apache.*"));
+        found = Permissions.wildcardAllow(wildcards, "com.apache.commons.jexl3");
+        Assert.assertTrue(found);
+        found = Permissions.wildcardAllow(wildcards, "com.google.spexl");
+        Assert.assertFalse(found);
+    }
+
+    @Test
+    public void testSecurePermissions() {
+        Permissions SECURE = new PermissionsParser().parse(
+                        "java.lang.*\n"+
+                        "java.math.*\n"+
+                        "java.text.*\n"+
+                        "java.util.*\n"+
+                        "java.lang { Runtime {} System {} ProcessBuilder {} Class {} }\n" +
+                                "java.lang.annotation {}\n" +
+                                "java.lang.instrument {}\n" +
+                                "java.lang.invoke {}\n" +
+                                "java.lang.management {}\n" +
+                                "java.lang.ref {}\n" +
+                                "java.lang.reflect {}\n");
+        Assert.assertNotNull(SECURE);
+        Set<String> wilcards = SECURE.getWildcards();
+        Assert.assertEquals(4, wilcards.size());
+    }
 }
diff --git a/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java b/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
index 0aecde7..ea28e11 100644
--- a/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
+++ b/src/test/java/org/apache/commons/jexl3/jexl342/OptionalTest.java
@@ -83,6 +83,10 @@ public class OptionalTest {
         result = script.execute(null, thing);
         Assert.assertNull(result);
         thing.name = "froboz";
+        script = jexl.createScript(info,"thing.names", "thing");
+        result = script.execute(null, thing);
+        Assert.assertNotNull(result);
+        script = jexl.createScript(info,"thing.names.size()", "thing");
         result = script.execute(null, thing);
         Assert.assertEquals(1, result);
     }