001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.jexl3.introspection; 018 019import org.apache.commons.jexl3.internal.introspection.PermissionsParser; 020 021import java.lang.reflect.Constructor; 022import java.lang.reflect.Field; 023import java.lang.reflect.Method; 024import java.lang.reflect.Modifier; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.HashSet; 028import java.util.Objects; 029import java.util.Set; 030import java.util.stream.Collectors; 031 032/** 033 * This interface describes permissions used by JEXL introspection that constrain which 034 * packages/classes/constructors/fields/methods are made visible to JEXL scripts. 035 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated 036 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control 037 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to 038 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions 039 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}. 040 * </p> 041 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated - 042 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl} 043 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments; 044 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be 045 * processed.</p> 046 * <p>A simple textual configuration can be used to create user defined permissions using 047 * {@link JexlPermissions#parse(String...)}.</p> 048 * 049 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder} 050 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would 051 * be to instantiate a {@link JexlUberspect} with those permissions and call 052 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p> 053 * 054 * <p> 055 * To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior 056 * by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with 057 * {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance. 058 * </p> 059 * <p> 060 * For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behaviour to 061 * JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)} 062 * with {@link #UNRESTRICTED} as parameter. 063 * </p> 064 * @since 3.3 065 */ 066public interface JexlPermissions { 067 068 /** 069 * Checks whether a package allows JEXL introspection. 070 * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible 071 * to JEXL and can not be used in scripts or expression.</p> 072 * @param pack the package 073 * @return true if JEXL is allowed to introspect, false otherwise 074 * @since 3.3 075 */ 076 boolean allow(final Package pack); 077 078 /** 079 * Checks whether a class allows JEXL introspection. 080 * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields 081 * as well as derived classes are visible to JEXL and can not be used in scripts or expressions. 082 * If one of its super-classes is not allowed, tbe class is not allowed either.</p> 083 * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p> 084 * @param clazz the class to check 085 * @return true if JEXL is allowed to introspect, false otherwise 086 * @since 3.3 087 */ 088 boolean allow(final Class<?> clazz); 089 090 /** 091 * Checks whether a constructor allows JEXL introspection. 092 * <p>If a constructor is not allowed, the new operator can not be used to instantiate its declared class 093 * in scripts or expressions.</p> 094 * @param ctor the constructor to check 095 * @return true if JEXL is allowed to introspect, false otherwise 096 * @since 3.3 097 */ 098 boolean allow(final Constructor<?> ctor); 099 100 /** 101 * Checks whether a method allows JEXL introspection. 102 * <p>If a method is not allowed, it can not resolved and called in scripts or expressions.</p> 103 * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface 104 * explicitly disallows this methods.</p> 105 * @param method the method to check 106 * @return true if JEXL is allowed to introspect, false otherwise 107 * @since 3.3 108 */ 109 boolean allow(final Method method); 110 111 /** 112 * Checks whether a field explicitly disallows JEXL introspection. 113 * <p>If a field is not allowed, it can not resolved and accessed in scripts or expressions.</p> 114 * @param field the field to check 115 * @return true if JEXL is allowed to introspect, false otherwise 116 * @since 3.3 117 */ 118 boolean allow(final Field field); 119 120 /** 121 * Parses a set of permissions. 122 * <p> 123 * In JEXL 3.3, the syntax recognizes 2 types of permissions: 124 * </p> 125 * <ul> 126 * <li>Allowing access to a wildcard restricted set of packages. </li> 127 * <li>Denying access to packages, classes (and inner classes), methods and fields</li> 128 * </ul> 129 * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be 130 * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards. 131 * These allow composing the volume of what is allowed by addition.</p> 132 * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation; 133 * they can restrict access to package, class, inner-class, methods and fields. 134 * These allow refining the volume of what is allowed by extrusion.</p> 135 * An example of a tight environment that would not allow scripts to wander could be: 136 * <pre> 137 * # allow a very restricted set of base classes 138 * java.math.* 139 * java.text.* 140 * java.util.* 141 * # deny classes that could pose a security risk 142 * java.lang { Runtime {} System {} ProcessBuilder {} Class {} } 143 * org.apache.commons.jexl3 { JexlBuilder {} } 144 * </pre> 145 * <ul> 146 * <li>Syntax for wildcards is the name of the package suffixed by <code>.*</code>.</li> 147 * <li>Syntax for restrictions is a list of package restrictions.</li> 148 * <li>A package restriction is a package name followed by a block (as in curly-bracket block {}) 149 * that contains a list of class restrictions.</li> 150 * <li>A class restriction is a class name followed by a block of member restrictions.</li> 151 * <li>A member restriction can be a class restriction - to restrict 152 * nested classes -, a field which is the Java field name suffixed with <code>;</code>, a method composed of 153 * its Java name suffixed with <code>();</code>. Constructor restrictions are specified like methods using the 154 * class name as method name.</li> 155 * </ul> 156 * <p> 157 * All overrides and overloads of a constructors or method are allowed or restricted at the same time, 158 * the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation. 159 * </p> 160 * <pre> 161 * # some wildcards 162 * java.lang.*; # java.lang is pretty much a must have 163 * my.allowed.package0.* 164 * another.allowed.package1.* 165 * # nojexl like restrictions 166 * my.package.internal {} # the whole package is hidden 167 * my.package { 168 * class0 { 169 * class1 {} # the whole class1 is hidden 170 * class2 { 171 * class2(); # class2 constructors can not be invoked 172 * class3 { 173 * aMethod(); # aMethod can not be called 174 * aField; # aField can not be accessed 175 * } 176 * } # end of class2 177 * class0(); # class0 constructors can not be invoked 178 * method(); # method can not be called 179 * field; # field can not be accessed 180 * } # end class0 181 * } # end package my.package 182 * </pre> 183 * 184 * @param src the permissions source, the default (NoJexl aware) permissions if null 185 * @return the permissions instance 186 * @since 3.3 187 */ 188 static JexlPermissions parse(final String... src) { 189 return new PermissionsParser().parse(src); 190 } 191 192 /** 193 * Compose these permissions with a new set. 194 * <p>This is a convenience method meant to easily give access to the packages JEXL is 195 * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code> 196 * would extend the restricted set of permissions by allowing the com.my.app package.</p> 197 * @param src the new constraints 198 * @return the new permissions 199 */ 200 JexlPermissions compose(final String... src); 201 202 /** 203 * The unrestricted permissions. 204 * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p> 205 * @since 3.3 206 */ 207 JexlPermissions UNRESTRICTED = JexlPermissions.parse(null); 208 /** 209 * A restricted singleton. 210 * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p> 211 * <p>Of particular importance are the restrictions on the {@link System}, 212 * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net}, {@link java.net}, 213 * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts 214 * and its host. 215 * </p> 216 * <p> 217 * As a simple guide, any line that ends with ".*" is allowing a package, any other is 218 * denying a package, class or method. 219 * </p> 220 * <ul> 221 * <li>java.nio.*</li> 222 * <li>java.io.*</li> 223 * <li>java.lang.*</li> 224 * <li>java.math.*</li> 225 * <li>java.text.*</li> 226 * <li>java.util.*</li> 227 * <li>org.w3c.dom.*</li> 228 * <li>org.apache.commons.jexl3.*</li> 229 * 230 * <li>org.apache.commons.jexl3 { JexlBuilder {} }</li> 231 * <li>org.apache.commons.jexl3.internal { Engine {} }</li> 232 * <li>java.lang { Runtime {} System {} ProcessBuilder {} Class {} }</li> 233 * <li>java.lang.annotation {}</li> 234 * <li>java.lang.instrument {}</li> 235 * <li>java.lang.invoke {}</li> 236 * <li>java.lang.management {}</li> 237 * <li>java.lang.ref {}</li> 238 * <li>java.lang.reflect {}</li> 239 * <li>java.net {}</li> 240 * <li>java.io { File { } }</li> 241 * <li>java.nio { Path { } Paths { } Files { } }</li> 242 * <li>java.rmi {}</li> 243 * </ul> 244 */ 245 JexlPermissions RESTRICTED = JexlPermissions.parse( 246 "# Restricted Uberspect Permissions", 247 "java.nio.*", 248 "java.io.*", 249 "java.lang.*", 250 "java.math.*", 251 "java.text.*", 252 "java.util.*", 253 "org.w3c.dom.*", 254 "org.apache.commons.jexl3.*", 255 "org.apache.commons.jexl3 { JexlBuilder {} }", 256 "org.apache.commons.jexl3.internal { Engine {} }", 257 "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" + 258 " RuntimePermission{} SecurityManager{}" + 259 " Thread{} ThreadGroup{} Class{} }", 260 "java.lang.annotation {}", 261 "java.lang.instrument {}", 262 "java.lang.invoke {}", 263 "java.lang.management {}", 264 "java.lang.ref {}", 265 "java.lang.reflect {}", 266 "java.net {}", 267 "java.io { File{} FileDescriptor{} }", 268 "java.nio { Path { } Paths { } Files { } }", 269 "java.rmi" 270 ); 271 272 /** 273 * Checks that a package is valid for permission check. 274 * @param pack the package 275 * @return true if the class is not null, false otherwise 276 */ 277 default boolean validate(final Package pack) { 278 return pack != null; 279 } 280 281 /** 282 * Checks that a class is valid for permission check. 283 * @param clazz the class 284 * @return true if the class is not null, false otherwise 285 */ 286 default boolean validate(final Class<?> clazz) { 287 return clazz != null; 288 } 289 290 /** 291 * Checks that a constructor is valid for permission check. 292 * @param constructor the constructor 293 * @return true if constructor is not null and public, false otherwise 294 */ 295 default boolean validate(final Constructor<?> constructor) { 296 if (constructor == null) { 297 return false; 298 } 299 // field must be public 300 if (!Modifier.isPublic(constructor.getModifiers())) { 301 return false; 302 } 303 return true; 304 } 305 306 /** 307 * Checks that a method is valid for permission check. 308 * @param method the method 309 * @return true if method is not null, public, ,ot-synthetic, not-bridge, false otherwise 310 */ 311 default boolean validate(final Method method) { 312 if (method == null) { 313 return false; 314 } 315 // method must be public 316 if (!Modifier.isPublic(method.getModifiers())) { 317 return false; 318 } 319 return true; 320 } 321 322 /** 323 * Checks that a field is valid for permission check. 324 * @param field the constructor 325 * @return true if field is not null and public, false otherwise 326 */ 327 default boolean validate(final Field field) { 328 if (field == null) { 329 return false; 330 } 331 // field must be public 332 if (!Modifier.isPublic(field.getModifiers())) { 333 return false; 334 } 335 return true; 336 } 337 338 /** 339 * A base for permission delegation allowing functional refinement. 340 * Overloads should call the appropriate validate() method early in their body. 341 */ 342 class Delegate implements JexlPermissions { 343 /** The permissions we delegate to. */ 344 protected final JexlPermissions base; 345 346 protected Delegate(JexlPermissions delegate) { 347 base = delegate; 348 } 349 350 @Override 351 public boolean allow(Package pack) { 352 return base.allow(pack); 353 } 354 355 @Override 356 public boolean allow(Class<?> clazz) { 357 return base.allow(clazz); 358 } 359 360 @Override 361 public boolean allow(Constructor<?> ctor) { 362 return base.allow(ctor); 363 } 364 365 @Override 366 public boolean allow(Method method) { 367 return base.allow(method); 368 } 369 370 @Override 371 public boolean allow(Field field) { 372 return base.allow(field); 373 } 374 375 @Override 376 public JexlPermissions compose(String... src) { 377 return new Delegate(base.compose(src)); 378 } 379 } 380 381 /** 382 * A permission delegation that augments the RESTRICTED permission with an explicit 383 * set of classes. 384 * <p>Typical use case is to deny access to a package - and thus all its classes - but allow 385 * a few specific classes.</p> 386 */ 387 class ClassPermissions extends JexlPermissions.Delegate { 388 /** The set of explicitly allowed classes, overriding the delegate permissions. */ 389 private final Set<String> allowedClasses; 390 391 /** 392 * Creates permissions based on the RESTRICTED set but allowing an explicit set. 393 * @param allow the set of allowed classes 394 */ 395 public ClassPermissions(Class... allow) { 396 this(JexlPermissions.RESTRICTED, 397 Arrays.asList(Objects.requireNonNull(allow)) 398 .stream().map(Class::getCanonicalName).collect(Collectors.toList())); 399 } 400 401 /** 402 * Required for compose(). 403 * @param delegate the base to delegate to 404 * @param allow the list of class canonical names 405 */ 406 public ClassPermissions(JexlPermissions delegate, Collection<String> allow) { 407 super(Objects.requireNonNull(delegate)); 408 allowedClasses = new HashSet<>(Objects.requireNonNull(allow)); 409 } 410 411 private boolean isClassAllowed(Class<?> clazz) { 412 return allowedClasses.contains(clazz.getCanonicalName()); 413 } 414 415 @Override 416 public boolean allow(Class<?> clazz) { 417 return (validate(clazz) && isClassAllowed(clazz)) || super.allow(clazz); 418 } 419 420 @Override 421 public boolean allow(Method method) { 422 return (validate(method) && isClassAllowed(method.getDeclaringClass())) || super.allow(method); 423 } 424 425 @Override 426 public boolean allow(Constructor constructor) { 427 return (validate(constructor) && isClassAllowed(constructor.getDeclaringClass())) || super.allow(constructor); 428 } 429 430 @Override 431 public JexlPermissions compose(String... src) { 432 return new ClassPermissions(base.compose(src), allowedClasses); 433 } 434 } 435}