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.introspection; 18 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.Method; 22 import java.lang.reflect.Modifier; 23 import java.util.Arrays; 24 import java.util.Collection; 25 import java.util.HashSet; 26 import java.util.Objects; 27 import java.util.Set; 28 import java.util.stream.Collectors; 29 30 import org.apache.commons.jexl3.internal.introspection.PermissionsParser; 31 32 /** 33 * This interface describes permissions used by JEXL introspection that constrain which 34 * packages/classes/constructors/fields/methods are made visible to JEXL scripts. 35 * <p>By specifying or implementing permissions, it is possible to constrain precisely which objects can be manipulated 36 * by JEXL, allowing users to enter their own expressions or scripts whilst maintaining tight control 37 * over what can be executed. JEXL introspection mechanism will check whether it is permitted to 38 * access a constructor, method or field before exposition to the {@link JexlUberspect}. The restrictions 39 * are applied in all cases, for any {@link org.apache.commons.jexl3.introspection.JexlUberspect.ResolverStrategy}. 40 * </p> 41 * <p>This complements using a dedicated {@link ClassLoader} and/or {@link SecurityManager} - being deprecated - 42 * and possibly {@link JexlSandbox} with a simpler mechanism. The {@link org.apache.commons.jexl3.annotations.NoJexl} 43 * annotation processing is actually performed using the result of calling {@link #parse(String...)} with no arguments; 44 * implementations shall delegate calls to its methods for {@link org.apache.commons.jexl3.annotations.NoJexl} to be 45 * processed.</p> 46 * <p>A simple textual configuration can be used to create user-defined permissions using 47 * {@link JexlPermissions#parse(String...)}.</p> 48 * 49 *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder} 50 * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would 51 * be to instantiate a {@link JexlUberspect} with those permissions and call 52 * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p> 53 * 54 * <p> 55 * To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior 56 * by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with 57 * {@link #UNRESTRICTED} as parameter before creating a JEXL engine instance. 58 * </p> 59 * <p> 60 * For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behavior to 61 * JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)} 62 * with {@link #UNRESTRICTED} as parameter. 63 * </p> 64 * @since 3.3 65 */ 66 public interface JexlPermissions { 67 68 /** 69 * A permission delegation that augments the RESTRICTED permission with an explicit 70 * set of classes. 71 * <p>Typical use case is to deny access to a package - and thus all its classes - but allow 72 * a few specific classes.</p> 73 * <p>Note that the newer positive restriction syntax is preferable as in: 74 * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p> 75 */ 76 final class ClassPermissions extends JexlPermissions.Delegate { 77 /** The set of explicitly allowed classes, overriding the delegate permissions. */ 78 private final Set<String> allowedClasses; 79 80 /** 81 * Creates permissions based on the RESTRICTED set but allowing an explicit set. 82 * @param allow the set of allowed classes 83 */ 84 public ClassPermissions(final Class<?>... allow) { 85 this(JexlPermissions.RESTRICTED, 86 Arrays.stream(Objects.requireNonNull(allow)) 87 .map(Class::getCanonicalName) 88 .collect(Collectors.toList())); 89 } 90 91 /** 92 * Required for compose(). 93 * @param delegate the base to delegate to 94 * @param allow the list of class canonical names 95 */ 96 public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) { 97 super(Objects.requireNonNull(delegate)); 98 allowedClasses = new HashSet<>(Objects.requireNonNull(allow)); 99 } 100 101 @Override 102 public boolean allow(final Class<?> clazz) { 103 return validate(clazz) && isClassAllowed(clazz) || super.allow(clazz); 104 } 105 106 @Override 107 public boolean allow(final Constructor<?> constructor) { 108 return validate(constructor) && isClassAllowed(constructor.getDeclaringClass()) || super.allow(constructor); 109 } 110 111 @Override 112 public boolean allow(final Method method) { 113 return validate(method) && isClassAllowed(method.getDeclaringClass()) || super.allow(method); 114 } 115 116 @Override 117 public JexlPermissions compose(final String... src) { 118 return new ClassPermissions(base.compose(src), allowedClasses); 119 } 120 121 private boolean isClassAllowed(final Class<?> clazz) { 122 return allowedClasses.contains(clazz.getCanonicalName()); 123 } 124 } 125 126 /** 127 * A base for permission delegation allowing functional refinement. 128 * Overloads should call the appropriate validate() method early in their body. 129 */ 130 class Delegate implements JexlPermissions { 131 /** The permissions we delegate to. */ 132 protected final JexlPermissions base; 133 134 /** 135 * Constructs a new instance. 136 * 137 * @param delegate the delegate. 138 */ 139 protected Delegate(final JexlPermissions delegate) { 140 base = delegate; 141 } 142 143 @Override 144 public boolean allow(final Class<?> clazz) { 145 return base.allow(clazz); 146 } 147 148 @Override 149 public boolean allow(final Constructor<?> ctor) { 150 return base.allow(ctor); 151 } 152 153 @Override 154 public boolean allow(final Field field) { 155 return base.allow(field); 156 } 157 158 @Override 159 public boolean allow(final Method method) { 160 return base.allow(method); 161 } 162 163 @Override 164 public boolean allow(final Package pack) { 165 return base.allow(pack); 166 } 167 168 @Override 169 public JexlPermissions compose(final String... src) { 170 return new Delegate(base.compose(src)); 171 } 172 } 173 174 /** 175 * The unrestricted permissions. 176 * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p> 177 * @since 3.3 178 */ 179 JexlPermissions UNRESTRICTED = JexlPermissions.parse(); 180 181 /** 182 * A restricted singleton. 183 * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p> 184 * <p>Of particular importance are the restrictions on the {@link System}, 185 * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net}, {@link java.net}, 186 * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts 187 * and its host. 188 * </p> 189 * <p> 190 * As a simple guide, any line that ends with ".*" is allowing a package, any other is 191 * denying a package, class or method. 192 * </p> 193 * <ul> 194 * <li>java.nio.*</li> 195 * <li>java.io.*</li> 196 * <li>java.lang.*</li> 197 * <li>java.math.*</li> 198 * <li>java.text.*</li> 199 * <li>java.util.*</li> 200 * <li>org.w3c.dom.*</li> 201 * <li>org.apache.commons.jexl3.*</li> 202 * 203 * <li>org.apache.commons.jexl3 { JexlBuilder {} }</li> 204 * <li>org.apache.commons.jexl3.internal { Engine {} }</li> 205 * <li>java.lang { Runtime {} System {} ProcessBuilder {} Class {} }</li> 206 * <li>java.lang.annotation {}</li> 207 * <li>java.lang.instrument {}</li> 208 * <li>java.lang.invoke {}</li> 209 * <li>java.lang.management {}</li> 210 * <li>java.lang.ref {}</li> 211 * <li>java.lang.reflect {}</li> 212 * <li>java.net {}</li> 213 * <li>java.io { File { } }</li> 214 * <li>java.nio { Path { } Paths { } Files { } }</li> 215 * <li>java.rmi {}</li> 216 * </ul> 217 */ 218 JexlPermissions RESTRICTED = JexlPermissions.parse( 219 "# Restricted Uberspect Permissions", 220 "java.nio.*", 221 "java.io.*", 222 "java.lang.*", 223 "java.math.*", 224 "java.text.*", 225 "java.util.*", 226 "org.w3c.dom.*", 227 "org.apache.commons.jexl3.*", 228 "org.apache.commons.jexl3 { JexlBuilder {} }", 229 "org.apache.commons.jexl3.internal { Engine {} }", 230 "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" + 231 " RuntimePermission{} SecurityManager{}" + 232 " Thread{} ThreadGroup{} Class{} }", 233 "java.lang.annotation {}", 234 "java.lang.instrument {}", 235 "java.lang.invoke {}", 236 "java.lang.management {}", 237 "java.lang.ref {}", 238 "java.lang.reflect {}", 239 "java.net {}", 240 "java.io { File{} FileDescriptor{} }", 241 "java.nio { Path { } Paths { } Files { } }", 242 "java.rmi" 243 ); 244 245 /** 246 * Parses a set of permissions. 247 * <p> 248 * In JEXL 3.3, the syntax recognizes 2 types of permissions: 249 * </p> 250 * <ul> 251 * <li>Allowing access to a wildcard restricted set of packages. </li> 252 * <li>Denying access to packages, classes (and inner classes), methods and fields</li> 253 * </ul> 254 * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be 255 * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards. 256 * These allow composing the volume of what is allowed by addition.</p> 257 * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation; 258 * they can restrict access to package, class, inner-class, methods and fields. 259 * These allow refining the volume of what is allowed by extrusion.</p> 260 * An example of a tight environment that would not allow scripts to wander could be: 261 * <pre> 262 * # allow a very restricted set of base classes 263 * java.math.* 264 * java.text.* 265 * java.util.* 266 * # deny classes that could pose a security risk 267 * java.lang { Runtime {} System {} ProcessBuilder {} Class {} } 268 * org.apache.commons.jexl3 { JexlBuilder {} } 269 * </pre> 270 * <ul> 271 * <li>Syntax for wildcards is the name of the package suffixed by <code>.*</code>.</li> 272 * <li>Syntax for restrictions is a list of package restrictions.</li> 273 * <li>A package restriction is a package name followed by a block (as in curly-bracket block {}) 274 * that contains a list of class restrictions.</li> 275 * <li>A class restriction is a class name prefixed by an optional <code>-</code> or <code>+</code> sign 276 * followed by a block of member restrictions.</li> 277 * <li>A member restriction can be a class restriction - to restrict 278 * nested classes -, a field which is the Java field name suffixed with <code>;</code>, a method composed of 279 * its Java name suffixed with <code>();</code>. Constructor restrictions are specified like methods using the 280 * class name as method name.</li> 281 * <li>By default or when prefixed with a <code>-</code>, a class restriction is explicitly denying the members 282 * declared in its block (or the whole class)</li> 283 * <li>When prefixed with a <code>+</code>, a class restriction is explicitly allowing the members 284 * declared in its block (or the whole class)</li> 285 * </ul> 286 * <p> 287 * All overrides and overloads of a constructors or method are allowed or restricted at the same time, 288 * the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation. 289 * </p> 290 * <pre> 291 * # some wildcards 292 * java.lang.*; # java.lang is pretty much a must have 293 * my.allowed.package0.* 294 * another.allowed.package1.* 295 * # nojexl like restrictions 296 * my.package.internal {} # the whole package is hidden 297 * my.package { 298 * +class4 { theMethod(); } # only theMethod can be called in class4 299 * class0 { 300 * class1 {} # the whole class1 is hidden 301 * class2 { 302 * class2(); # class2 constructors can not be invoked 303 * class3 { 304 * aMethod(); # aMethod can not be called 305 * aField; # aField can not be accessed 306 * } 307 * } # end of class2 308 * class0(); # class0 constructors can not be invoked 309 * method(); # method can not be called 310 * field; # field can not be accessed 311 * } # end class0 312 * } # end package my.package 313 * </pre> 314 * 315 * @param src the permissions source, the default (NoJexl aware) permissions if null 316 * @return the permissions instance 317 * @since 3.3 318 */ 319 static JexlPermissions parse(final String... src) { 320 return new PermissionsParser().parse(src); 321 } 322 323 /** 324 * Checks whether a class allows JEXL introspection. 325 * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields 326 * as well as derived classes are visible to JEXL and can not be used in scripts or expressions. 327 * If one of its super-classes is not allowed, tbe class is not allowed either.</p> 328 * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p> 329 * @param clazz the class to check 330 * @return true if JEXL is allowed to introspect, false otherwise 331 * @since 3.3 332 */ 333 boolean allow(final Class<?> clazz); 334 335 /** 336 * Checks whether a constructor allows JEXL introspection. 337 * <p>If a constructor is not allowed, the new operator can not be used to instantiate its declared class 338 * in scripts or expressions.</p> 339 * @param ctor the constructor to check 340 * @return true if JEXL is allowed to introspect, false otherwise 341 * @since 3.3 342 */ 343 boolean allow(final Constructor<?> ctor); 344 345 /** 346 * Checks whether a field explicitly disallows JEXL introspection. 347 * <p>If a field is not allowed, it can not resolved and accessed in scripts or expressions.</p> 348 * @param field the field to check 349 * @return true if JEXL is allowed to introspect, false otherwise 350 * @since 3.3 351 */ 352 boolean allow(final Field field); 353 /** 354 * Checks whether a method allows JEXL introspection. 355 * <p>If a method is not allowed, it can not resolved and called in scripts or expressions.</p> 356 * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface 357 * explicitly disallows this methods.</p> 358 * @param method the method to check 359 * @return true if JEXL is allowed to introspect, false otherwise 360 * @since 3.3 361 */ 362 boolean allow(final Method method); 363 364 /** 365 * Checks whether a package allows JEXL introspection. 366 * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible 367 * to JEXL and can not be used in scripts or expression.</p> 368 * @param pack the package 369 * @return true if JEXL is allowed to introspect, false otherwise 370 * @since 3.3 371 */ 372 boolean allow(final Package pack); 373 374 /** 375 * Compose these permissions with a new set. 376 * <p>This is a convenience method meant to easily give access to the packages JEXL is 377 * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code> 378 * would extend the restricted set of permissions by allowing the com.my.app package.</p> 379 * @param src the new constraints 380 * @return the new permissions 381 */ 382 JexlPermissions compose(final String... src); 383 384 /** 385 * Checks that a class is valid for permission check. 386 * @param clazz the class 387 * @return true if the class is not null, false otherwise 388 */ 389 default boolean validate(final Class<?> clazz) { 390 return clazz != null; 391 } 392 393 /** 394 * Checks that a constructor is valid for permission check. 395 * @param constructor the constructor 396 * @return true if constructor is not null and public, false otherwise 397 */ 398 default boolean validate(final Constructor<?> constructor) { 399 return constructor != null && Modifier.isPublic(constructor.getModifiers()); 400 } 401 402 /** 403 * Checks that a field is valid for permission check. 404 * @param field the constructor 405 * @return true if field is not null and public, false otherwise 406 */ 407 default boolean validate(final Field field) { 408 return field != null && Modifier.isPublic(field.getModifiers()); 409 } 410 411 /** 412 * Checks that a method is valid for permission check. 413 * @param method the method 414 * @return true if method is not null and public, false otherwise 415 */ 416 default boolean validate(final Method method) { 417 return method != null && Modifier.isPublic(method.getModifiers()); 418 } 419 420 /** 421 * Checks that a package is valid for permission check. 422 * @param pack the package 423 * @return true if the class is not null, false otherwise 424 */ 425 default boolean validate(final Package pack) { 426 return pack != null; 427 } 428 }