1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.jexl3.internal.introspection;
19
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.lang.reflect.Proxy;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
31 import java.util.function.BiPredicate;
32
33 import org.apache.commons.jexl3.annotations.NoJexl;
34 import org.apache.commons.jexl3.introspection.JexlPermissions;
35
36 /**
37 * Checks whether an element (ctor, field, or method) is visible by JEXL introspection.
38 * <p>The default implementation does this by checking if an element has been annotated with NoJexl.</p>
39 *
40 * <p>The NoJexl annotation allows a fine grain permission on executable objects (methods, fields, constructors).
41 * </p>
42 * <ul>
43 * <li>NoJexl of a package implies all classes (including derived classes), and all interfaces
44 * of that package are invisible to JEXL.</li>
45 * <li>NoJexl on a class implies this class, and all its derived classes are invisible to JEXL.</li>
46 * <li>NoJexl on a (public) field makes it not visible as a property to JEXL.</li>
47 * <li>NoJexl on a constructor prevents that constructor to be used to instantiate through 'new'.</li>
48 * <li>NoJexl on a method prevents that method and any of its overrides to be visible to JEXL.</li>
49 * <li>NoJexl on an interface prevents all methods of that interface and their overrides to be visible to JEXL.</li>
50 * </ul>
51 * <p>It is possible to define permissions on external library classes used for which the source code
52 * cannot be altered using an instance of permissions using {@link JexlPermissions#parse(String...)}.</p>
53 */
54 public class Permissions implements JexlPermissions {
55 /**
56 * Represents the ability to create a copy of an object.
57 * Any class implementing this interface must provide a concrete
58 * implementation for the {@code copy()} method, which returns
59 * a new instance of the object that is a logical copy of the original.
60 *
61 * @param <T> the type of object that can be copied
62 */
63 interface Copyable<T> {
64 T copy() ;
65 }
66
67 /**
68 * Creates a copy of a map containing copyable values.
69 * @param map the map to copy
70 * @return the copy of the map
71 * @param <T> the type of Copyable values
72 */
73 static <T extends Copyable<T>> Map<String, T> copyMap(final Map<String, T> map) {
74 final Map<String, T> copy = new HashMap<>(map.size());
75 for (final Map.Entry<String, T> entry : map.entrySet()) {
76 copy.put(entry.getKey(), entry.getValue().copy());
77 }
78 return copy;
79 }
80
81 /**
82 * Equivalent of @NoJexl on a ctor, a method, or a field in a class.
83 * <p>Field or method that are named are denied access.</p>
84 */
85 static class NoJexlClass implements Copyable<NoJexlClass> {
86 // the NoJexl method names (including ctor, name of class)
87 final Set<String> methodNames;
88 // the NoJexl field names
89 final Set<String> fieldNames;
90
91 NoJexlClass() {
92 this(new HashSet<>(), new HashSet<>());
93 }
94
95 NoJexlClass(final Set<String> methods, final Set<String> fields) {
96 methodNames = methods;
97 fieldNames = fields;
98 }
99
100 @Override public NoJexlClass copy() {
101 return new NoJexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
102 }
103
104 boolean deny(final Constructor<?> method) {
105 return methodNames.contains(method.getDeclaringClass().getSimpleName());
106 }
107
108 boolean deny(final Field field) {
109 return isEmpty() || fieldNames.contains(field.getName());
110 }
111
112 boolean deny(final Method method) {
113 return isEmpty() || methodNames.contains(method.getName());
114 }
115
116 boolean isEmpty() { return methodNames.isEmpty() && fieldNames.isEmpty(); }
117
118 /**
119 * Whether this is a positive (allow-oriented) class declaration.
120 * <p>A positive class is explicitly allowed in its package (it contributes to
121 * {@link NoJexlPackage#hasAllowedClass()} and an empty one means "allow the whole class").
122 * A plain deny-list {@code NoJexlClass} is not positive.</p>
123 *
124 * @return true if the class is positively allowed, false if it is a deny-list
125 */
126 boolean isPositive() { return false; }
127 }
128
129 /**
130 * A positive NoJexl construct that defines what is denied by absence in the set.
131 * <p>Field or method that are named are the only one allowed access.</p>
132 */
133 static class JexlClass extends NoJexlClass {
134 JexlClass(final Set<String> methods, final Set<String> fields) {
135 super(methods, fields);
136 }
137 JexlClass() {
138 }
139 @Override public JexlClass copy() {
140 return new JexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
141 }
142 @Override boolean deny(final Constructor<?> method) { return !super.deny(method); }
143 @Override boolean deny(final Field field) { return !super.deny(field); }
144 @Override boolean deny(final Method method) { return !super.deny(method); }
145 @Override boolean isPositive() { return true; }
146 }
147
148 /**
149 * A class that is positively allowed in its package yet carries deny-list member semantics.
150 * <p>Used for {@code +ClassName{ -member(); }} inside a deny package: the class is added to the
151 * allowed set (so unlisted classes in the package stay denied) but the named members are denied
152 * while all others remain allowed. It is a {@link NoJexlClass} (deny-list) tagged as positive;
153 * the deny-list behavior is inherited unchanged.</p>
154 */
155 static class AllowedNoJexlClass extends NoJexlClass {
156 AllowedNoJexlClass() {
157 }
158 AllowedNoJexlClass(final Set<String> methods, final Set<String> fields) {
159 super(methods, fields);
160 }
161 @Override public AllowedNoJexlClass copy() {
162 return new AllowedNoJexlClass(new HashSet<>(methodNames), new HashSet<>(fieldNames));
163 }
164 @Override boolean isPositive() { return true; }
165 }
166
167 /**
168 * Equivalent of @NoJexl on a class in a package.
169 */
170 protected static class NoJexlPackage implements Copyable<NoJexlPackage> {
171 // the NoJexl class names
172 final Map<String, NoJexlClass> nojexl;
173
174 /**
175 * Default ctor.
176 */
177 NoJexlPackage() {
178 this(null);
179 }
180
181 /**
182 * Ctor.
183 *
184 * @param map the map of NoJexl classes
185 */
186 NoJexlPackage(final Map<String, NoJexlClass> map) {
187 this.nojexl = map == null || map.isEmpty() ? new HashMap<>() : map;
188 }
189
190 void addNoJexl(final String key, final NoJexlClass njc) {
191 if (njc == null) {
192 nojexl.remove(key);
193 } else {
194 nojexl.put(key, njc);
195 }
196 }
197
198 NoJexlClass getNoJexl(final Class<?> clazz) {
199 return nojexl.get(classKey(clazz));
200 }
201
202 boolean isEmpty() { return nojexl.isEmpty(); }
203
204 /**
205 * Whether this package has at least one explicitly-allowed class (a positive entry).
206 * <p>A package with allowed-class entries acts as an allow-list: unlisted classes are denied.
207 * A package with only denied-class entries acts as a deny-list: unlisted classes are allowed.</p>
208 *
209 * @return true if at least one class is explicitly allowed
210 */
211 boolean hasAllowedClass() {
212 for (final NoJexlClass njc : nojexl.values()) {
213 if (njc.isPositive()) {
214 return true;
215 }
216 }
217 return false;
218 }
219
220 /**
221 * Whether this is a positive (allow-oriented) package declaration.
222 * <p>A positive package allows its own classes by default; a deny-list package does not.</p>
223 *
224 * @return true if the package is positively allowed, false otherwise
225 */
226 boolean isPositive() { return false; }
227
228 @Override public NoJexlPackage copy() {
229 return new NoJexlPackage(copyMap(nojexl));
230 }
231 }
232
233 /**
234 * A package where classes are allowed by default.
235 */
236 static class JexlPackage extends NoJexlPackage {
237 JexlPackage(final Map<String, NoJexlClass> map) {
238 super(map);
239 }
240
241 @Override
242 NoJexlClass getNoJexl(final Class<?> clazz) {
243 final NoJexlClass njc = nojexl.get(classKey(clazz));
244 return njc != null ? njc : JEXL_CLASS;
245 }
246
247 @Override boolean isPositive() { return true; }
248
249 @Override public JexlPackage copy() {
250 return new JexlPackage(copyMap(nojexl));
251 }
252 }
253
254 /** Marker for whole NoJexl class. */
255 static final NoJexlClass NOJEXL_CLASS = new NoJexlClass(Collections.emptySet(), Collections.emptySet()) {
256 @Override boolean deny(final Constructor<?> method) {
257 return true;
258 }
259
260 @Override boolean deny(final Field field) {
261 return true;
262 }
263
264 @Override boolean deny(final Method method) {
265 return true;
266 }
267 };
268
269 /** Marker for allowed class. */
270 static final NoJexlClass JEXL_CLASS = new JexlClass(Collections.emptySet(), Collections.emptySet()) {
271 @Override boolean deny(final Constructor<?> method) {
272 return false;
273 }
274
275 @Override boolean deny(final Field field) {
276 return false;
277 }
278
279 @Override boolean deny(final Method method) {
280 return false;
281 }
282 };
283
284 /** Marker for @NoJexl package. */
285 static final NoJexlPackage NOJEXL_PACKAGE = new NoJexlPackage(Collections.emptyMap()) {
286 @Override NoJexlClass getNoJexl(final Class<?> clazz) {
287 return NOJEXL_CLASS;
288 }
289 };
290
291 /** Marker for fully allowed package. */
292 static final NoJexlPackage JEXL_PACKAGE = new NoJexlPackage(Collections.emptyMap()) {
293 @Override NoJexlClass getNoJexl(final Class<?> clazz) {
294 return JEXL_CLASS;
295 }
296 @Override boolean isPositive() {
297 return true;
298 }
299 // a constant singleton survives copy as itself, preserving its positive nature through compose()
300 @Override public NoJexlPackage copy() {
301 return this;
302 }
303 };
304
305 /**
306 * The no-restriction introspection permission singleton.
307 */
308 static final Permissions UNRESTRICTED = new Permissions();
309
310 /**
311 * The @NoJexl execution-time map.
312 */
313 private final Map<String, NoJexlPackage> packages;
314 /**
315 * The allowed package patterns (wildcards or exact package names).
316 * <p>Empty together with an empty {@link #packages} map means open-world: every package is accessible
317 * and only explicitly denied elements are carved out — the behavior of {@link #UNRESTRICTED}.
318 * Empty with a non-empty {@link #packages} map, or non-empty, means closed-world: only declared
319 * packages are accessible.</p>
320 */
321 private final Set<String> allowed;
322
323 /** Allow inheritance. */
324 protected Permissions() {
325 this.allowed = Collections.emptySet();
326 this.packages = Collections.emptyMap();
327 }
328
329 /**
330 * Default ctor.
331 *
332 * @param perimeter the allowed wildcard set of packages
333 * @param nojexl the NoJexl external map
334 */
335 protected Permissions(final Set<String> perimeter, final Map<String, NoJexlPackage> nojexl) {
336 this.allowed = perimeter;
337 this.packages = nojexl;
338 }
339
340 /**
341 * Creates a new set of permissions by composing these permissions with a new set of rules.
342 *
343 * @param src the rules
344 * @return the new permissions
345 */
346 @Override
347 public Permissions compose(final String... src) {
348 return new PermissionsParser().parse(new HashSet<>(allowed), copyMap(packages), src);
349 }
350
351 /**
352 * Creates a class key joining enclosing ascendants with '$'.
353 * <p>As in {@code outer$inner} for <code>class outer { class inner...</code>.</p>
354 *
355 * @param clazz the clazz
356 * @return the clazz key
357 */
358 static String classKey(final Class<?> clazz) {
359 return classKey(clazz, null);
360 }
361
362 /**
363 * Creates a class key joining enclosing ascendants with '$'.
364 * <p>As in {@code outer$inner} for <code>class outer { class inner...</code>.</p>
365 *
366 * @param clazz the clazz
367 * @param strb the buffer to compose the key
368 * @return the clazz key
369 */
370 static String classKey(final Class<?> clazz, final StringBuilder strb) {
371 StringBuilder keyb = strb;
372 final Class<?> outer = clazz.getEnclosingClass();
373 if (outer != null) {
374 if (keyb == null) {
375 keyb = new StringBuilder();
376 }
377 classKey(outer, keyb);
378 keyb.append('$');
379 }
380 if (keyb != null) {
381 keyb.append(clazz.getSimpleName());
382 return keyb.toString();
383 }
384 return clazz.getSimpleName();
385 }
386
387 /**
388 * Whether the wildcard set of packages allows a given package to be introspected.
389 *
390 * @param allowed the allowed set (not null, may be empty)
391 * @param name the package name (not null)
392 * @return true if allowed, false otherwise
393 */
394 static boolean wildcardAllow(final Set<String> allowed, final String name) {
395 // allowed packages are explicit in this case
396 boolean found = allowed == null || allowed.isEmpty() || allowed.contains(name);
397 if (!found) {
398 String wildcard = name;
399 for (int i = name.length(); !found && i > 0; i = wildcard.lastIndexOf('.')) {
400 wildcard = wildcard.substring(0, i);
401 found = allowed.contains(wildcard + ".*");
402 }
403 }
404 return found;
405 }
406
407 /**
408 * Gets the package constraints.
409 *
410 * @param packageName the package name
411 * @return the package constraints instance, not-null.
412 */
413 private NoJexlPackage getNoJexlPackage(final String packageName) {
414 return packages.getOrDefault(packageName, JEXL_PACKAGE);
415 }
416
417 /**
418 * @return the packages
419 */
420 Map<String, NoJexlPackage> getPackages() {
421 return packages == null ? Collections.emptyMap() : Collections.unmodifiableMap(packages);
422 }
423
424 /**
425 * @return the wildcards
426 */
427 Set<String> getWildcards() {
428 return allowed == null ? Collections.emptySet() : Collections.unmodifiableSet(allowed);
429 }
430
431 /**
432 * Whether a package belongs to the allowed perimeter.
433 * <p>Open-world ({@link #UNRESTRICTED}: no rules at all) allows every package. Closed-world requires the
434 * package to match an entry in {@link #allowed}; an empty perimeter in closed-world matches nothing.</p>
435 *
436 * @param packageName the package name (not null)
437 * @return true if allowed, false otherwise
438 */
439 private boolean allowedPackage(final String packageName) {
440 if ((allowed.isEmpty() && packages.isEmpty()) || (!allowed.isEmpty() && wildcardAllow(allowed, packageName))) {
441 return true;
442 }
443 // a package explicitly declared positive allows itself (exact match only, no sub-package inference)
444 final NoJexlPackage njp = packages.get(packageName);
445 return njp != null && njp.isPositive();
446 }
447
448 /**
449 * Determines whether a specified permission check is allowed for a given class.
450 * The check involves verifying if a class or its corresponding package explicitly permits
451 * a name (e.g., method) based on a given condition.
452 *
453 * @param <T> the type of the name to check (e.g., method, constructor)
454 * @param clazz the class to evaluate (not null)
455 * @param name the name to verify (not null)
456 * @param check the condition to test whether the specified name is allowed (not null)
457 * @return true if the specified name is allowed based on the condition, false otherwise
458 */
459 private <T> boolean specifiedAllow(final Class<?> clazz, final T name, final BiPredicate<NoJexlClass, T> check) {
460 final String packageName = ClassTool.getPackageName(clazz);
461 if (allowedPackage(packageName)) {
462 return true;
463 }
464 final NoJexlPackage njp = packages.get(packageName);
465 if (njp != null && check != null) {
466 // there is a package permission, check if there is a class permission
467 final NoJexlClass njc = njp.getNoJexl(clazz);
468 if (njc != null) {
469 return check.test(njc, name);
470 }
471 // class not listed: allowed if the package is a deny-list (no explicit class allows);
472 // denied if the package is an allow-list (e.g. java.io -{ +PrintWriter{} ... })
473 return !njp.hasAllowedClass();
474 }
475 // package not declared at all
476 return false;
477 }
478
479 /**
480 * Checks whether a class or one of its super-classes or implemented interfaces
481 * explicitly allows JEXL introspection.
482 *
483 * @param clazz the class to check
484 * @return true if JEXL is allowed to introspect, false otherwise
485 */
486 @Override
487 public boolean allow(final Class<?> clazz) {
488 // clazz must be not null
489 if (!validate(clazz)) {
490 return false;
491 }
492 // proxy goes through
493 if (Proxy.isProxyClass(clazz)) {
494 return true;
495 }
496 // class must not be denied, nor extend a denied class
497 if (deny(clazz)) {
498 return false;
499 }
500 // a subclass of a denied class is also denied; Object (the universal root) is skipped, and the
501 // null guard keeps the walk safe when clazz is an interface or Object itself (getSuperclass() null).
502 for (Class<?> walk = clazz.getSuperclass(); walk != null && walk != Object.class; walk = walk.getSuperclass()) {
503 if (deny(walk)) {
504 return false;
505 }
506 }
507 // a class is allowed only by its own package or class-level declaration: there is deliberately no
508 // reach-through via an allowed super-type, which would otherwise expose the whole foreign class.
509 return allowedClass(clazz);
510 }
511
512 /**
513 * Whether a class is allowed by its own package or class-level declaration.
514 *
515 * @param clazz the class to check (not null)
516 * @return true if explicitly allowed
517 */
518 private boolean allowedClass(final Class<?> clazz) {
519 return specifiedAllow(clazz, clazz, (njc, c) -> njc.isPositive() || !njc.isEmpty());
520 }
521
522 /**
523 * Check whether a method is allowed to be introspected in one superclass or interface.
524 *
525 * @param clazz the superclass or interface to check
526 * @param method the method
527 * @param explicit carries whether the package holding the method is explicitly allowed
528 * @return true if JEXL is allowed to introspect, false otherwise
529 */
530 private boolean allow(final Class<?> clazz, final Method method, final boolean[] explicit) {
531 try {
532 // check if the method in that class is declared thus overrides
533 final Method override = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
534 if (override != method) {
535 // should not be possible...
536 if (denyMethod(override)) {
537 return false;
538 }
539 // explicit |= ...
540 if (!explicit[0]) {
541 explicit[0] = specifiedAllow(clazz, override, (njc, m) -> !njc.deny(m));
542 }
543 }
544 return true;
545 } catch (final NoSuchMethodException ex) {
546 // will happen if not overriding method in clazz
547 return true;
548 } catch (final SecurityException ex) {
549 // unexpected, can't do much
550 return false;
551 }
552 }
553
554 /**
555 * Checks whether a constructor explicitly allows JEXL introspection.
556 *
557 * @param ctor the constructor to check
558 * @return true if JEXL is allowed to introspect, false otherwise
559 */
560 @Override
561 public boolean allow(final Constructor<?> ctor) {
562 // method must be not null, public
563 // check declared restrictions
564 if (!validate(ctor) || deny(ctor)) {
565 return false;
566 }
567 // class must agree
568 final Class<?> clazz = ctor.getDeclaringClass();
569 if (deny(clazz)) {
570 return false;
571 }
572 // check wildcards
573 return specifiedAllow(clazz, clazz, (njc, c) -> !njc.deny(ctor));
574 }
575
576
577 /**
578 * Checks whether a field explicitly allows JEXL introspection.
579 *
580 * @param field the field to check
581 * @return true if JEXL is allowed to introspect, false otherwise
582 */
583 @Override
584 public boolean allow(final Field field) {
585 // field must be public
586 // check declared restrictions
587 if (!validate(field) || deny(field)) {
588 return false;
589 }
590 // class must agree
591 final Class<?> clazz = field.getDeclaringClass();
592 if (deny(clazz)) {
593 return false;
594 }
595 // check wildcards
596 return specifiedAllow(clazz, field, (njc, m) -> !njc.deny(m));
597 }
598
599 @Override
600 public boolean allow(final Class<?> clazz, final Method method) {
601 if (!validate(clazz) || !validate(method)) {
602 return false;
603 }
604 if ((method.getModifiers() & Modifier.STATIC) == 0) {
605 final Class<?> declaring = method.getDeclaringClass();
606 if (clazz != declaring) {
607 // just check this is an override of a method in clazz, if not, it is not allowed (obviously)
608 if (deny(clazz) || !declaring.isAssignableFrom(clazz)) {
609 return false;
610 }
611 // an explicit permission on the concrete class can deny; otherwise fall through to
612 // allow(method) which honors carve-outs on the declaring class/interfaces (e.g. Object.getClass())
613 final NoJexlClass njc = getNoJexl(clazz, null);
614 if (njc != null && njc.deny(method)) {
615 return false;
616 }
617 }
618 }
619 return allow(method);
620 }
621
622 @Override
623 public boolean allow(final Class<?> clazz, final Field field) {
624 if (!validate(clazz) || !validate(field)) {
625 return false;
626 }
627 if ((field.getModifiers() & Modifier.STATIC) == 0) {
628 final Class<?> declaring = field.getDeclaringClass();
629 if (clazz != declaring) {
630 // just check this clazz extends/inherits from declaring, if not, it is not allowed (obviously)
631 if (deny(clazz) || !declaring.isAssignableFrom(clazz)) {
632 return false;
633 }
634 // an explicit permission on the concrete class can deny; otherwise fall through to
635 // allow(field) which honors carve-outs on the declaring class
636 final NoJexlClass njc = getNoJexl(clazz, null);
637 if (njc != null && njc.deny(field)) {
638 return false;
639 }
640 }
641 }
642 return allow(field);
643 }
644
645 /**
646 * Checks whether a method explicitly allows JEXL introspection.
647 * <p>Since methods can be overridden, this also checks that no superclass or interface
648 * explicitly disallows this method.</p>
649 *
650 * @param method the method to check
651 * @return true if JEXL is allowed to introspect, false otherwise
652 */
653 @Override
654 public boolean allow(final Method method) {
655 // method must be not null, public, not synthetic, not bridge
656 // method must be allowed
657 if (!validate(method) || denyMethod(method)) {
658 return false;
659 }
660 Class<?> clazz = method.getDeclaringClass();
661 // gather if the packages explicitly allow any implementation of the method
662 final boolean[] explicit = { specifiedAllow(clazz, method, (njc, m) -> !njc.deny(m)) };
663 // let's walk all interfaces
664 for (final Class<?> inter : clazz.getInterfaces()) {
665 if (!allow(inter, method, explicit)) {
666 return false;
667 }
668 }
669 // let's walk all super classes
670 clazz = clazz.getSuperclass();
671 while (clazz != null) {
672 if (!allow(clazz, method, explicit)) {
673 return false;
674 }
675 clazz = clazz.getSuperclass();
676 }
677 return explicit[0];
678 }
679
680 /**
681 * Checks whether a package explicitly disallows JEXL introspection.
682 *
683 * @param pack the package
684 * @return true if JEXL is allowed to introspect, false otherwise
685 */
686 @Override
687 public boolean allow(final Package pack) {
688 // field must be public
689 // check declared restrictions
690 if (!validate(pack) || deny(pack)) {
691 return false;
692 }
693 // an explicit package entry is allowed unless it is the deny marker
694 final String name = pack.getName();
695 final NoJexlPackage njp = packages.get(name);
696 return njp == null ? allowedPackage(name) : !Objects.equals(NOJEXL_PACKAGE, njp);
697 }
698
699
700 /**
701 * Tests whether a whole class is denied Jexl visibility.
702 * <p>Also checks package visibility.</p>
703 *
704 * @param clazz the class
705 * @return true if denied, false otherwise
706 */
707 private boolean deny(final Class<?> clazz) {
708 // Don't deny arrays
709 if (clazz.isArray()) {
710 return false;
711 }
712 // is clazz annotated with nojexl ?
713 final NoJexl nojexl = clazz.getAnnotation(NoJexl.class);
714 if (nojexl != null) {
715 return true;
716 }
717 final NoJexlPackage njp = packages.get(ClassTool.getPackageName(clazz));
718 return njp != null && Objects.equals(NOJEXL_CLASS, njp.getNoJexl(clazz));
719 }
720
721 /**
722 * Tests whether a constructor is denied Jexl visibility.
723 *
724 * @param ctor the constructor
725 * @return true if denied, false otherwise
726 */
727 private boolean deny(final Constructor<?> ctor) {
728 // is ctor annotated with nojexl ?
729 final NoJexl nojexl = ctor.getAnnotation(NoJexl.class);
730 if (nojexl != null) {
731 return true;
732 }
733 return getNoJexl(ctor.getDeclaringClass()).deny(ctor);
734 }
735
736 /**
737 * Tests whether a field is denied Jexl visibility.
738 *
739 * @param field the field
740 * @return true if denied, false otherwise
741 */
742 private boolean deny(final Field field) {
743 // is field annotated with nojexl ?
744 final NoJexl nojexl = field.getAnnotation(NoJexl.class);
745 if (nojexl != null) {
746 return true;
747 }
748 return getNoJexl(field.getDeclaringClass()).deny(field);
749 }
750
751 /**
752 * Tests whether a method is denied Jexl visibility.
753 *
754 * @param method the method
755 * @return true if denied, false otherwise
756 */
757 private boolean deny(final Method method) {
758 // is method annotated with nojexl ?
759 final NoJexl nojexl = method.getAnnotation(NoJexl.class);
760 if (nojexl != null) {
761 return true;
762 }
763 return getNoJexl(method.getDeclaringClass()).deny(method);
764 }
765
766 /**
767 * Tests whether a whole package is denied Jexl visibility.
768 *
769 * @param pack the package
770 * @return true if denied, false otherwise
771 */
772 private boolean deny(final Package pack) {
773 // is package annotated with nojexl ?
774 final NoJexl nojexl = pack.getAnnotation(NoJexl.class);
775 if (nojexl != null) {
776 return true;
777 }
778 return Objects.equals(NOJEXL_PACKAGE, packages.get(pack.getName()));
779 }
780
781 /**
782 * Tests whether a method is denied.
783 *
784 * @param method the method
785 * @return true if it has been disallowed through annotation or declaration
786 */
787 private boolean denyMethod(final Method method) {
788 // check declared restrictions, class must not be denied
789 return deny(method) || deny(method.getDeclaringClass());
790 }
791
792 /**
793 * Gets the class constraints.
794 * <p>If nothing was explicitly forbidden, everything is allowed.</p>
795 *
796 * @param clazz the class
797 * @return the class constraints instance, not-null.
798 */
799 private NoJexlClass getNoJexl(final Class<?> clazz) {
800 return getNoJexl(clazz, JEXL_CLASS);
801 }
802 private NoJexlClass getNoJexl(final Class<?> clazz, final NoJexlClass ifNone) {
803 final String pkgName = ClassTool.getPackageName(clazz);
804 final NoJexlPackage njp = getNoJexlPackage(pkgName);
805 if (njp != null) {
806 final NoJexlClass njc = njp.getNoJexl(clazz);
807 if (njc != null) {
808 return njc;
809 }
810 }
811 return ifNone;
812 }
813 }