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 * https://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 java.lang.reflect.Constructor; 020import java.lang.reflect.Field; 021import java.lang.reflect.Method; 022import java.lang.reflect.Modifier; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.HashSet; 026import java.util.Objects; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import org.apache.commons.jexl3.internal.introspection.PermissionsParser; 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 behavior 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 * 065 * @since 3.3 066 */ 067public interface JexlPermissions { 068 069 /** 070 * A permission delegation that augments the RESTRICTED permission with an explicit 071 * set of classes. 072 * <p>Typical use case is to deny access to a package - and thus all its classes - but allow 073 * a few specific classes.</p> 074 * <p>Note that the newer positive restriction syntax is preferable as in: 075 * <code>RESTRICTED.compose("java.lang { +Class {} }")</code>.</p> 076 */ 077 final class ClassPermissions extends JexlPermissions.Delegate { 078 079 /** The set of explicitly allowed classes, overriding the delegate permissions. */ 080 private final Set<String> allowedClasses; 081 082 /** 083 * Creates permissions based on the RESTRICTED set but allowing an explicit set. 084 * 085 * @param allow the set of allowed classes 086 */ 087 public ClassPermissions(final Class<?>... allow) { 088 this(JexlPermissions.RESTRICTED, 089 Arrays.stream(Objects.requireNonNull(allow)) 090 .map(Class::getCanonicalName) 091 .collect(Collectors.toList())); 092 } 093 094 /** 095 * Required for compose(). 096 * 097 * @param delegate the base to delegate to 098 * @param allow the list of class canonical names 099 */ 100 public ClassPermissions(final JexlPermissions delegate, final Collection<String> allow) { 101 super(Objects.requireNonNull(delegate)); 102 allowedClasses = new HashSet<>(Objects.requireNonNull(allow)); 103 } 104 105 @Override 106 public boolean allow(final Class<?> clazz) { 107 return validate(clazz) && isClassAllowed(clazz) || super.allow(clazz); 108 } 109 110 @Override 111 public boolean allow(final Constructor<?> constructor) { 112 return validate(constructor) && isClassAllowed(constructor.getDeclaringClass()) || super.allow(constructor); 113 } 114 115 @Override 116 public boolean allow(final Method method) { 117 return validate(method) && isClassAllowed(method.getDeclaringClass()) || super.allow(method); 118 } 119 120 @Override 121 public JexlPermissions compose(final String... src) { 122 return new ClassPermissions(base.compose(src), allowedClasses); 123 } 124 125 private boolean isClassAllowed(final Class<?> clazz) { 126 return allowedClasses.contains(clazz.getCanonicalName()); 127 } 128 } 129 130 /** 131 * A base for permission delegation allowing functional refinement. 132 * Overloads should call the appropriate validate() method early in their body. 133 */ 134 class Delegate implements JexlPermissions { 135 136 /** The permissions we delegate to. */ 137 protected final JexlPermissions base; 138 139 /** 140 * Constructs a new instance. 141 * 142 * @param delegate the delegate. 143 */ 144 protected Delegate(final JexlPermissions delegate) { 145 base = delegate; 146 } 147 148 @Override 149 public boolean allow(final Class<?> clazz) { 150 return base.allow(clazz); 151 } 152 153 @Override 154 public boolean allow(final Constructor<?> ctor) { 155 return base.allow(ctor); 156 } 157 158 @Override 159 public boolean allow(final Field field) { 160 return base.allow(field); 161 } 162 163 @Override 164 public boolean allow(final Method method) { 165 return base.allow(method); 166 } 167 168 @Override 169 public boolean allow(final Package pack) { 170 return base.allow(pack); 171 } 172 173 @Override 174 public JexlPermissions compose(final String... src) { 175 return new Delegate(base.compose(src)); 176 } 177 } 178 179 /** 180 * The unrestricted permissions. 181 * <p>This enables any public class, method, constructor or field to be visible to JEXL and used in scripts.</p> 182 * 183 * @since 3.3 184 */ 185 JexlPermissions UNRESTRICTED = JexlPermissions.parse(); 186 187 /** 188 * A restricted singleton. 189 * <p>The RESTRICTED set is built using the following allowed packages and denied packages/classes.</p> 190 * <p>Of particular importance are the restrictions on the {@link System}, 191 * {@link Runtime}, {@link ProcessBuilder}, {@link Class} and those on {@link java.net}, {@link java.net}, 192 * {@link java.io} and {@link java.lang.reflect} that should provide a decent level of isolation between the scripts 193 * and its host. 194 * </p> 195 * <p> 196 * As a simple guide, any line that ends with ".*" is allowing a package, any other is 197 * denying a package, class or method. 198 * </p> 199 * <ul> 200 * <li>java.nio.*</li> 201 * <li>java.io.*</li> 202 * <li>java.lang.*</li> 203 * <li>java.math.*</li> 204 * <li>java.text.*</li> 205 * <li>java.util.*</li> 206 * <li>org.w3c.dom.*</li> 207 * <li>org.apache.commons.jexl3.*</li> 208 * 209 * <li>org.apache.commons.jexl3 { JexlBuilder {} }</li> 210 * <li>org.apache.commons.jexl3.internal { Engine {} }</li> 211 * <li>java.lang { Runtime {} System {} ProcessBuilder {} Class {} }</li> 212 * <li>java.lang.annotation {}</li> 213 * <li>java.lang.instrument {}</li> 214 * <li>java.lang.invoke {}</li> 215 * <li>java.lang.management {}</li> 216 * <li>java.lang.ref {}</li> 217 * <li>java.lang.reflect {}</li> 218 * <li>java.net {}</li> 219 * <li>java.io { File { } }</li> 220 * <li>java.nio { Path { } Paths { } Files { } }</li> 221 * <li>java.rmi {}</li> 222 * </ul> 223 */ 224 JexlPermissions RESTRICTED = JexlPermissions.parse( 225 "# Restricted Uberspect Permissions", 226 "java.nio.*", 227 "java.io.*", 228 "java.lang.*", 229 "java.math.*", 230 "java.text.*", 231 "java.util.*", 232 "org.w3c.dom.*", 233 "org.apache.commons.jexl3.*", 234 "org.apache.commons.jexl3 { JexlBuilder {} }", 235 "org.apache.commons.jexl3.introspection { JexlPermissions {} JexlPermissions$ClassPermissions {} }", 236 "org.apache.commons.jexl3.internal { Engine {} Engine32 {} TemplateEngine {} }", 237 "org.apache.commons.jexl3.internal.introspection { Uberspect {} Introspector {} }", 238 "java.lang { Runtime{} System{} ProcessBuilder{} Process{}" + 239 " RuntimePermission{} SecurityManager{}" + 240 " Thread{} ThreadGroup{} Class{} }", 241 "java.lang.annotation {}", 242 "java.lang.instrument {}", 243 "java.lang.invoke {}", 244 "java.lang.management {}", 245 "java.lang.ref {}", 246 "java.lang.reflect {}", 247 "java.net {}", 248 "java.io { File{} FileDescriptor{} }", 249 "java.nio { Path { } Paths { } Files { } }", 250 "java.rmi" 251 ); 252 253 /** 254 * Parses a set of permissions. 255 * <p> 256 * In JEXL 3.3, the syntax recognizes 2 types of permissions: 257 * </p> 258 * <ul> 259 * <li>Allowing access to a wildcard restricted set of packages. </li> 260 * <li>Denying access to packages, classes (and inner classes), methods and fields</li> 261 * </ul> 262 * <p>Wildcards specifications determine the set of allowed packages. When empty, all packages can be 263 * used. When using JEXL to expose functional elements, their packages should be exposed through wildcards. 264 * These allow composing the volume of what is allowed by addition.</p> 265 * <p>Restrictions behave exactly like the {@link org.apache.commons.jexl3.annotations.NoJexl} annotation; 266 * they can restrict access to package, class, inner-class, methods and fields. 267 * These allow refining the volume of what is allowed by extrusion.</p> 268 * An example of a tight environment that would not allow scripts to wander could be: 269 * <pre> 270 * # allow a very restricted set of base classes 271 * java.math.* 272 * java.text.* 273 * java.util.* 274 * # deny classes that could pose a security risk 275 * java.lang { Runtime {} System {} ProcessBuilder {} Class {} } 276 * org.apache.commons.jexl3 { JexlBuilder {} } 277 * </pre> 278 * <ul> 279 * <li>Syntax for wildcards is the name of the package suffixed by {@code .*}.</li> 280 * <li>Syntax for restrictions is a list of package restrictions.</li> 281 * <li>A package restriction is a package name followed by a block (as in curly-bracket block {}) 282 * that contains a list of class restrictions.</li> 283 * <li>A class restriction is a class name prefixed by an optional {@code -} or {@code +} sign 284 * followed by a block of member restrictions.</li> 285 * <li>A member restriction can be a class restriction - to restrict 286 * nested classes -, a field which is the Java field name suffixed with {@code ;}, a method composed of 287 * its Java name suffixed with {@code ();}. Constructor restrictions are specified like methods using the 288 * class name as method name.</li> 289 * <li>By default or when prefixed with a {@code -}, a class restriction is explicitly denying the members 290 * declared in its block (or the whole class)</li> 291 * <li>When prefixed with a {@code +}, a class restriction is explicitly allowing the members 292 * declared in its block (or the whole class)</li> 293 * </ul> 294 * <p> 295 * All overrides and overloads of a constructors or method are allowed or restricted at the same time, 296 * the restriction being based on their names, not their whole signature. This differs from the @NoJexl annotation. 297 * </p> 298 * <pre> 299 * # some wildcards 300 * java.lang.*; # java.lang is pretty much a must have 301 * my.allowed.package0.* 302 * another.allowed.package1.* 303 * # nojexl like restrictions 304 * my.package.internal {} # the whole package is hidden 305 * my.package { 306 * +class4 { theMethod(); } # only theMethod can be called in class4 307 * class0 { 308 * class1 {} # the whole class1 is hidden 309 * class2 { 310 * class2(); # class2 constructors cannot be invoked 311 * class3 { 312 * aMethod(); # aMethod cannot be called 313 * aField; # aField cannot be accessed 314 * } 315 * } # end of class2 316 * class0(); # class0 constructors cannot be invoked 317 * method(); # method cannot be called 318 * field; # field cannot be accessed 319 * } # end class0 320 * } # end package my.package 321 * </pre> 322 * 323 * @param src the permissions source, the default (NoJexl aware) permissions if null 324 * @return the permissions instance 325 * @since 3.3 326 */ 327 static JexlPermissions parse(final String... src) { 328 return new PermissionsParser().parse(src); 329 } 330 331 /** 332 * Checks whether a class allows JEXL introspection. 333 * <p>If the class disallows JEXL introspection, none of its constructors, methods or fields 334 * as well as derived classes are visible to JEXL and cannot be used in scripts or expressions. 335 * If one of its super-classes is not allowed, tbe class is not allowed either.</p> 336 * <p>For interfaces, only methods and fields are disallowed in derived interfaces or implementing classes.</p> 337 * 338 * @param clazz the class to check 339 * @return true if JEXL is allowed to introspect, false otherwise 340 * @since 3.3 341 */ 342 boolean allow(final Class<?> clazz); 343 344 /** 345 * Checks whether a constructor allows JEXL introspection. 346 * <p>If a constructor is not allowed, the new operator cannot be used to instantiate its declared class 347 * in scripts or expressions.</p> 348 * 349 * @param ctor the constructor to check 350 * @return true if JEXL is allowed to introspect, false otherwise 351 * @since 3.3 352 */ 353 boolean allow(final Constructor<?> ctor); 354 355 /** 356 * Checks whether a field explicitly disallows JEXL introspection. 357 * <p>If a field is not allowed, it cannot resolved and accessed in scripts or expressions.</p> 358 * 359 * @param field the field to check 360 * @return true if JEXL is allowed to introspect, false otherwise 361 * @since 3.3 362 */ 363 boolean allow(final Field field); 364 365 /** 366 * Checks whether a method allows JEXL introspection. 367 * <p>If a method is not allowed, it cannot resolved and called in scripts or expressions.</p> 368 * <p>Since methods can be overridden and overloaded, this also checks that no superclass or interface 369 * explicitly disallows this methods.</p> 370 * 371 * @param method the method to check 372 * @return true if JEXL is allowed to introspect, false otherwise 373 * @since 3.3 374 */ 375 boolean allow(final Method method); 376 377 /** 378 * Checks whether a package allows JEXL introspection. 379 * <p>If the package disallows JEXL introspection, none of its classes or interfaces are visible 380 * to JEXL and cannot be used in scripts or expression.</p> 381 * 382 * @param pack the package 383 * @return true if JEXL is allowed to introspect, false otherwise 384 * @since 3.3 385 */ 386 boolean allow(final Package pack); 387 388 /** 389 * Compose these permissions with a new set. 390 * <p>This is a convenience method meant to easily give access to the packages JEXL is 391 * used to integrate with. For instance, using <code>{@link #RESTRICTED}.compose("com.my.app.*")</code> 392 * would extend the restricted set of permissions by allowing the com.my.app package.</p> 393 * 394 * @param src the new constraints 395 * @return the new permissions 396 */ 397 JexlPermissions compose(final String... src); 398 399 /** 400 * Checks that a class is valid for permission check. 401 * 402 * @param clazz the class 403 * @return true if the class is not null, false otherwise 404 */ 405 default boolean validate(final Class<?> clazz) { 406 return clazz != null; 407 } 408 409 /** 410 * Checks that a constructor is valid for permission check. 411 * 412 * @param constructor the constructor 413 * @return true if constructor is not null and public, false otherwise 414 */ 415 default boolean validate(final Constructor<?> constructor) { 416 return constructor != null && Modifier.isPublic(constructor.getModifiers()); 417 } 418 419 /** 420 * Checks that a field is valid for permission check. 421 * 422 * @param field the constructor 423 * @return true if field is not null and public, false otherwise 424 */ 425 default boolean validate(final Field field) { 426 return field != null && Modifier.isPublic(field.getModifiers()); 427 } 428 429 /** 430 * Checks that a method is valid for permission check. 431 * 432 * @param method the method 433 * @return true if method is not null and public, false otherwise 434 */ 435 default boolean validate(final Method method) { 436 return method != null && Modifier.isPublic(method.getModifiers()); 437 } 438 439 /** 440 * Checks that a package is valid for permission check. 441 * 442 * @param pack the package 443 * @return true if the class is not null, false otherwise 444 */ 445 default boolean validate(final Package pack) { 446 return pack != null; 447 } 448}