Created
October 1, 2017 10:14
-
-
Save anonymous/e1a3ab0436baec0b460e7d2b38a731b7 to your computer and use it in GitHub Desktop.
Answer to https://stackoverflow.com/questions/46406148 demonstrating an approach towards more fine-grained authorization of reflective operations.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security; | |
import java.io.FilePermission; | |
import java.lang.reflect.ReflectPermission; | |
import java.security.AllPermission; | |
import java.security.BasicPermission; | |
import java.security.Permission; | |
import java.security.PermissionCollection; | |
import java.security.Permissions; | |
import java.security.SecurityPermission; | |
/** | |
* <code>AllUnsafePermission</code> {@linkplain #implies(Permission) implies} a set of | |
* <code>Permission</code>s that, under the default security infrastructure, are known to | |
* considerably weaken the sandbox when granted. | |
* <p> | |
* This class is logically a {@linkplain #INSTANCE singleton}―instantiation is primarily allowed for | |
* use by <code>Policy</code> providers unaware of this class. | |
* </p> | |
* <p> | |
* The {@linkplain #getName() name} and {@linkplain #getActions() actions} of an | |
* <code>AllUnsafePermission</code> are always respectively equal to {@link #NAME}, | |
* {@link #ACTIONS}. | |
* </p> | |
*/ | |
public final class AllUnsafePermission extends BasicPermission { | |
/** | |
* The {@linkplain #getName() name} shared between all <code>AllUnsafePermission</code> instances. | |
*/ | |
public static final String NAME = "<all unsafe permissions>"; | |
/** | |
* The {@linkplain #getActions() actions} shared between all <code>AllUnsafePermission</code> | |
* instances. | |
*/ | |
public static final String ACTIONS = "<all unsafe actions>"; | |
/** | |
* Singleton instance. | |
*/ | |
public static final AllUnsafePermission INSTANCE = new AllUnsafePermission(); | |
private static final Permission ALL_PERMS = new AllPermission(); | |
private static final PermissionCollection UNSAFE_PERMS = new Permissions(); | |
static { | |
UNSAFE_PERMS.add(new RuntimePermission("createClassLoader")); | |
UNSAFE_PERMS.add(new RuntimePermission("accessClassInPackage.*")); | |
UNSAFE_PERMS.add(new RuntimePermission("accessDeclaredMembers")); | |
UNSAFE_PERMS.add(new RuntimePermission("defineClassInPackage.*")); | |
UNSAFE_PERMS.add(new RuntimePermission("setSecurityManager")); | |
UNSAFE_PERMS.add(new ReflectPermission("*")); | |
UNSAFE_PERMS.add(new FilePermission("<<ALL FILES>>", "write, execute")); | |
UNSAFE_PERMS.add(new SecurityPermission("createAccessControlContext")); | |
UNSAFE_PERMS.add(new SecurityPermission("getDomainCombiner")); | |
UNSAFE_PERMS.add(new SecurityPermission("setPolicy")); | |
UNSAFE_PERMS.add(new SecurityPermission("createPolicy.*")); | |
UNSAFE_PERMS.add(new SecurityPermission("setProperty.*")); | |
UNSAFE_PERMS.add(new SecurityPermission("insertProvider")); | |
UNSAFE_PERMS.add(new SecurityPermission("removeProvider.*")); | |
UNSAFE_PERMS.add(new SecurityPermission("clearProviderProperties")); | |
UNSAFE_PERMS.setReadOnly(); | |
} | |
private static final int HASH_CODE; | |
static { | |
final int prime = 31; | |
int hashCode = prime + NAME.hashCode(); | |
hashCode = prime * hashCode + (ACTIONS.hashCode()); | |
HASH_CODE = hashCode; | |
} | |
private static final long serialVersionUID = 3446963128032550094L; | |
/** | |
* Instantiates an <code>AllUnsafePermission</code>. | |
* | |
* @see #INSTANCE <small>INSTANCE</small | |
*/ | |
public AllUnsafePermission() { | |
super(NAME, ACTIONS); | |
} | |
/** | |
* Indicates whether the given <code>Permission</code> argument is unsafe.<br/> | |
* <br/> | |
* <code>true</code> is returned if for an instance, <em>u</em>, of any of the permission classes | |
* and respective name, actions pairs enumerated below, <code>(u.implies(p) == true)</code>. | |
* <dl> | |
* <dt>{@link RuntimePermission}</dt> | |
* <dd><code>"createClassLoader"</code></dd> | |
* <dd><code>"accessClassInPackage.*"</code></dd> | |
* <dd><code>"accessDeclaredMembers"</code></dd> | |
* <dd><code>"defineClassInPackage.*"</code></dd> | |
* <dd><code>"setSecurityManager"</code></dd> | |
* <dt>{@link ReflectPermission}</dt> | |
* <dd><code>"*"</code></dd> | |
* <dt>{@link FilePermission}</dt> | |
* <dd><code>"<<ALL FILES>></code>, <code>"write, execute"</code></dd> | |
* <dt>{@link SecurityPermission}</dt> | |
* <dd><code>"createAccessControlContext"</code></dd> | |
* <dd><code>"getDomainCombiner"</code></dd> | |
* <dd><code>"setPolicy"</code></dd> | |
* <dd><code>"setPolicy"</code></dd> | |
* <dd><code>"createPolicy.*"</code></dd> | |
* <dd><code>"setProperty.*"</code></dd> | |
* <dd><code>"insertProvider"</code></dd> | |
* <dd><code>"removeProvider.*"</code></dd> | |
* <dd><code>"removeProvider.*"</code></dd> | |
* <dd><code>"clearProviderProperties"</code></dd> | |
* </dl> | |
* Furthermore, <code>true</code> is also returned when: | |
* <ul> | |
* <li><code>(this.equals(p) == true)</code></li> | |
* <li><code>((p != null) && (p.implies(new AllPermission()) == true))</code></li> | |
* </ul> | |
* In any other case, <code>false</code> is returned. | |
*/ | |
@Override | |
public boolean implies(final Permission p) { | |
return equals(p) || ((p != null) && ((p.implies(ALL_PERMS) || UNSAFE_PERMS.implies(p)))); | |
} | |
/** | |
* Tests for equivalence between this instance and the provided argument. | |
* <p> | |
* <code>true</code> is returned iff <code>obj</code> is itself an <code>AllUnsafePermission</code>. | |
* </p> | |
* | |
* @return <code>true</code> iff this instance and the given one are equivalent. | |
*/ | |
@Override | |
public boolean equals(final Object obj) { | |
return obj instanceof AllUnsafePermission; | |
} | |
/** | |
* Returns the <code>AllUnsafePermission</code>'s hash code, based on its {@linkplain #getName() | |
* name} and {@linkplain #getActions() actions}. | |
* <p> | |
* The returned value is the same for all instances of this class. | |
* </p> | |
* | |
* @return The instance's hash code value. | |
*/ | |
@Override | |
public int hashCode() { | |
return HASH_CODE; | |
} | |
/** | |
* Returns the <code>AllUnsafePermission</code>'s actions string, which is unconditionally equal to | |
* the value of {@link #ACTIONS}. | |
* | |
* @return The actions string. | |
*/ | |
@Override | |
public String getActions() { | |
return ACTIONS; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security.util; | |
import java.util.Objects; | |
public final class Arrays { | |
public static <T> boolean contains(final T[] array, final T element) { | |
if (array == null) { | |
return false; | |
} | |
for (T _element : array) { | |
if (Objects.equals(_element, element)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
private Arrays() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security.util; | |
public final class CheckedFunctions { | |
@FunctionalInterface | |
public interface CheckedSupplier<T, E extends Exception> { | |
T get() throws E; | |
} | |
private CheckedFunctions() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Modifier; | |
import java.security.BasicPermission; | |
import java.security.Permission; | |
import java.security.UnresolvedPermission; | |
import java.text.MessageFormat; | |
import com.example.trusted.security.util.Strings; | |
/** | |
* A representation of a "negative" privilege. | |
* <p> | |
* A <code>DeniedPermission</code>, when "granted" to some <code>ProtectionDomain</code>, represents | |
* a privilege which <em>cannot</em> be exercised, regardless of any positive permissions | |
* (<code>AllPermission</code> included) possessed. In other words, if a set of granted permissions, | |
* <em>P</em>, contains a permission of this class, <em>D</em>, then the set of effectively granted | |
* permissions is<br/> | |
* <br/> | |
* <em>{ P<sub>implied</sub> - D<sub>implied</sub> }</em>. | |
* </p> | |
* <p> | |
* Each instance of this class encapsulates a <em>target permission</em>, representing the | |
* "positive" permission being negated. | |
* </p> | |
* Denied permissions employ the following naming scheme:<br/> | |
* <br/> | |
* <em><target_class_name>(:<target_name>(:<target_actions>))</em><br/> | |
* <br/> | |
* where: | |
* <ul> | |
* <li><em><target_class_name></em> is the fully qualified name of the target permission's | |
* class,</li> | |
* <li><em>(<target_name>)</em> is, optionally, the {@linkplain #getName() name} of the target | |
* permission,</li> | |
* <li><em>(<target_actions>)</em> are, optionally, the {@linkplain #getActions() actions} of | |
* the target permission, and</li> | |
* <li>the <em>':'</em> character stands for itself.</li> | |
* </ul> | |
* A denied permission, having a target permission <em>t</em>, is said to | |
* {@linkplain #implies(Permission) <em>imply</em>} another permission <em>p</em>, iff: | |
* <ul> | |
* <li>p <em>is not</em> itself a denied permission, and <code>(t.implies(p) == true)</code>, | |
* or</li> | |
* <li>p <em>is</em> a denied permission, with a target <em>t1</em>, and | |
* <code>(t.implies(t1) == true)</code>.</li> | |
* </ul> | |
* <p> | |
* It is the responsibility of the policy decision point (e.g., the <code>Policy</code> provider) to | |
* take denied permission semantics into account when issuing authorization statements. | |
* </p> | |
*/ | |
public final class DeniedPermission extends BasicPermission { | |
private static final String NULL_STR_ARG = "<null>", EMPTY_STR_ARG = "<empty> "; | |
private static final long serialVersionUID = -7962181390360140950L; | |
private final Permission target; | |
/** | |
* Instantiates a <code>DeniedPermission</code> that encapsulates a target permission of the | |
* indicated class, specified name and, optionally, actions. | |
* | |
* @throws IllegalArgumentException | |
* if: | |
* <ul> | |
* <li><code>targetClassName</code> is <code>null</code>, the empty string, does not | |
* refer to a concrete <code>Permission</code> descendant, or refers to | |
* <code>DeniedPermission.class</code> or <code>UnresolvedPermission.class</code>.</li> | |
* <li><code>targetClassName</code> cannot be instantiated, and it's the caller's fault; | |
* e.g., because <code>targetName</code> and/or <code>targetActions</code> do not adhere | |
* to the naming constraints of the target class; or due to the target class not | |
* exposing a constructor corresponding to the target name and actions components (or | |
* absence thereof).</li> | |
* </ul> | |
* @throws SecurityException | |
* if a <code>SecurityManager</code>, <code>sm</code>, is installed, and the invocation | |
* <code>sm.checkPackageAccess(targetClassPackage)</code> (where | |
* <code>targetClassPackage</code> is the package of the class referred to by | |
* <code>targetClassName</code>) denies access. | |
*/ | |
public static DeniedPermission newDeniedPermission(final String targetClassName, final String targetName, | |
final String targetActions) { | |
Strings.requireNotBlank(targetClassName, "[targetClassName] must not be null or empty."); | |
StringBuilder sb = new StringBuilder(targetClassName).append(":").append(targetName); | |
if (targetName != null) { | |
sb.append(":").append(targetName); | |
} | |
return new DeniedPermission(sb.toString()); | |
} | |
/** | |
* Instantiates a <code>DeniedPermission</code> that encapsulates the given target permission. | |
* | |
* @throws IllegalArgumentException | |
* if <code>target</code> is <code>null</code>, a <code>DeniedPermission</code>, or an | |
* <code>UnresolvedPermission</code>. | |
*/ | |
public static DeniedPermission newDeniedPermission(final Permission target) { | |
if (target == null) { | |
throw new IllegalArgumentException("[target] must not be null.", null); | |
} | |
if (target instanceof DeniedPermission || target instanceof UnresolvedPermission) { | |
throw new IllegalArgumentException("[target] must not be a DeniedPermission or an UnresolvedPermission.", | |
null); | |
} | |
StringBuilder sb = new StringBuilder(target.getClass().getName()); | |
String targetName = target.getName(); | |
if (targetName != null) { | |
sb.append(":").append(target.getName()); | |
} | |
String targetActions = target.getActions(); | |
if (targetActions != null) { | |
sb.append(":").append(targetActions); | |
} | |
return new DeniedPermission(sb.toString(), target); | |
} | |
private static Permission constructTargetPermission(String targetClassName, String targetName, | |
String targetActions) { | |
Class<?> targetClass; | |
try { | |
targetClass = Class.forName(targetClassName); | |
} | |
catch (ClassNotFoundException cnfe) { | |
if (targetClassName == null) { | |
targetClassName = NULL_STR_ARG; | |
} | |
else if (Strings.isWhitespace(targetClassName)) { | |
targetClassName = EMPTY_STR_ARG; | |
} | |
throw new IllegalArgumentException( | |
MessageFormat.format("Target Permission class [{0}] not found.", targetClassName), null); | |
} | |
if (!Permission.class.isAssignableFrom(targetClass) || Modifier.isAbstract(targetClass.getModifiers())) { | |
throw new IllegalArgumentException(MessageFormat | |
.format("Target Permission class [{0}] is not a (concrete) Permission.", targetClassName), null); | |
} | |
if (targetClass == DeniedPermission.class || targetClass == UnresolvedPermission.class) { | |
throw new IllegalArgumentException( | |
"Target Permission class must not be a DeniedPermission itself, nor an UnresolvedPermission.", | |
null); | |
} | |
Constructor<?> targetCtor; | |
Object[] ctorArgs; | |
try { | |
if (targetName == null) { | |
targetCtor = targetClass.getConstructor(); | |
ctorArgs = new Object[0]; | |
} | |
else if (targetActions == null) { | |
targetCtor = targetClass.getConstructor(String.class); | |
ctorArgs = new Object[] { targetName }; | |
} | |
else { | |
targetCtor = targetClass.getConstructor(String.class, String.class); | |
ctorArgs = new Object[] { targetName, targetActions }; | |
} | |
} | |
catch (final NoSuchMethodException nsme) { | |
throw new IllegalArgumentException(MessageFormat.format( | |
"Target Permission class [{0}] does not expose a no-arg, or a single or double String-arg constructor.", | |
targetClassName), null); | |
} | |
try { | |
return (Permission) targetCtor.newInstance(ctorArgs); | |
} | |
catch (final ReflectiveOperationException roe) { | |
if (roe instanceof InvocationTargetException) { | |
if (targetName == null) { | |
targetName = NULL_STR_ARG; | |
} | |
else if (Strings.isWhitespace(targetName)) { | |
targetName = EMPTY_STR_ARG; | |
} | |
if (targetActions == null) { | |
targetActions = NULL_STR_ARG; | |
} | |
else if (Strings.isWhitespace(targetActions)) { | |
targetActions = EMPTY_STR_ARG; | |
} | |
throw new IllegalArgumentException(MessageFormat.format( | |
"Could not instantiate target Permission class [{0}]; provided target name [{1}] and/or target [{2}] actions potentially erroneous.", | |
targetClassName, targetName, targetActions), roe); | |
} | |
throw new RuntimeException(MessageFormat.format( | |
"Could not instantiate target Permission class [{0}] - an unforeseen error occurred, see attached cause for details.", | |
targetClassName), roe); | |
} | |
} | |
/** | |
* Instantiates a <code>DeniedPermission</code> that encapsulates a target permission of the class, | |
* name and, optionally, actions, collectively provided as the <code>name</code> argument. | |
* | |
* @throws IllegalArgumentException | |
* if: | |
* <ul> | |
* <li><code>name</code>'s target permission class name component is empty, does not | |
* refer to a concrete <code>Permission</code> descendant, or refers to | |
* <code>DeniedPermission.class</code> or <code>UnresolvedPermission.class</code>.</li> | |
* <li>the target permission class cannot be instantiated, and it's the caller's fault; | |
* e.g., because <code>name</code>'s target name and/or target actions component(s) do | |
* not adhere to the naming constraints of the target class; or due to the target class | |
* not exposing a constructor corresponding to the target name and actions components | |
* (or absence thereof).</li> | |
* </ul> | |
* @throws SecurityException | |
* if a <code>SecurityManager</code>, <code>sm</code>, is installed, and the invocation | |
* <code>sm.checkPackageAccess(targetClassPackage)</code> (where | |
* <code>targetClassPackage</code> is the package of the class referred to by | |
* <code>name</code>'s target name component) denies access. | |
*/ | |
public DeniedPermission(final String name) { | |
super(name); | |
String[] comps = new String[3], tmp = name.split(":"); | |
if (tmp.length > 3) { | |
throw new IllegalArgumentException(MessageFormat.format("Malformed [name] argument: {0}", name), null); | |
} | |
System.arraycopy(tmp, 0, comps, 0, tmp.length); | |
this.target = constructTargetPermission(comps[0], comps[1], comps[2]); | |
} | |
private DeniedPermission(final String name, final Permission target) { | |
super(name); | |
this.target = target; | |
} | |
/** | |
* Checks whether the given permission is implied by this one, as per the | |
* {@linkplain DeniedPermission overview}. | |
*/ | |
@Override | |
public boolean implies(final Permission p) { | |
if (p instanceof DeniedPermission) { | |
return target.implies(((DeniedPermission) p).target); | |
} | |
return target.implies(p); | |
} | |
/** | |
* Returns this denied permission's target permission. | |
*/ | |
public Permission getTargetPermission() { | |
return target; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security; | |
import java.security.AccessController; | |
import java.security.CodeSource; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.NoSuchProviderException; | |
import java.security.Permission; | |
import java.security.PermissionCollection; | |
import java.security.Policy; | |
import java.security.PrivilegedAction; | |
import java.security.ProtectionDomain; | |
import java.security.UnresolvedPermission; | |
import java.util.Enumeration; | |
/** | |
* Wrapper that adds rudimentary {@link DeniedPermission} processing capabilities to the standard | |
* file-backed <code>Policy</code>. | |
*/ | |
public final class DenyingPolicy extends Policy { | |
/* | |
* doPrivileged needed just in case there's already a SecurityManager installed at class loading | |
* time. | |
*/ | |
private static final ProtectionDomain OWN_PD = AccessController | |
.doPrivileged((PrivilegedAction<ProtectionDomain>) DenyingPolicy.class::getProtectionDomain); | |
private final Policy defaultPolicy; | |
/** | |
* Instantiates a <code>DenyingPolicy</code>. | |
* | |
* @throws SecurityException | |
* if a <code>SecurityManager</code>, <code>sm</code>, is present, and the invocation | |
* <code>sm.checkPermission(new SecurityPermission("createPolicy.javaPolicy"))</code> | |
* denies instantiation of the to-be-wrapped System-default provider. | |
*/ | |
public DenyingPolicy() { | |
try { | |
// will fail unless the calling acc has SecurityPermission "createPolicy.javaPolicy" | |
defaultPolicy = Policy.getInstance("javaPolicy", null, "SUN"); | |
} | |
catch (final NoSuchProviderException | NoSuchAlgorithmException e) { | |
throw new RuntimeException("Could not acquire default Policy.", e); | |
} | |
} | |
/** | |
* {@inheritDoc}<br/> | |
* <br/> | |
* This implementation returns <code>true</code> iff: | |
* <ul> | |
* <li><code>permission</code> <em>is not</em> an instance of <code>DeniedPermission</code>, | |
* and</li> | |
* <li>an <code>implies(domain, permission)</code> invocation on the system-default | |
* <code>Policy</code> yields <code>true</code>, and</li> | |
* <li><code>permission</code> <em>is not</em> implied by any <code>DeniedPermission</code>s having | |
* potentially been assigned to <code>domain</code>.</li> | |
* </ul> | |
*/ | |
@Override | |
public boolean implies(final ProtectionDomain domain, final Permission permission) { | |
if (OWN_PD.equals(domain)) { | |
/* | |
* Recursive invocation due to a privilege-requiring method we invoked. If you're uncomfortable with | |
* this, get rid of it and grant (via .policy) a RuntimePermission "accessClassInPackage.*" to | |
* OWN_PD. | |
*/ | |
return true; | |
} | |
if (permission instanceof DeniedPermission) { | |
/* | |
* At the policy decision level, DeniedPermissions can only themselves imply, not be implied (as | |
* they take away, rather than grant, privileges). Returning true for a deny rule would be | |
* confusing. | |
*/ | |
return false; | |
} | |
if (!defaultPolicy.implies(domain, permission)) { | |
// permission not granted--no need to check whether denied | |
return false; | |
} | |
/* | |
* Permission granted--now check whether there's an overriding DeniedPermission. The following | |
* assumes that defaultPolicy (its wrapped PolicySpi) is a sun.security.provider.PolicySpiFile | |
* (different implementations might not support #getPermissions(ProtectionDomain) and/or handle | |
* resolution of UnresolvedPermissions differently). | |
*/ | |
Enumeration<Permission> perms = defaultPolicy.getPermissions(domain).elements(); | |
while (perms.hasMoreElements()) { | |
Permission p = perms.nextElement(); | |
/* | |
* DeniedPermissions will generally remain unresolved, as no code is expected to check whether other | |
* code has been "granted" such a permission. | |
*/ | |
if (p instanceof UnresolvedPermission) { | |
UnresolvedPermission up = (UnresolvedPermission) p; | |
if (up.getUnresolvedType().equals(DeniedPermission.class.getName())) { | |
// evaluate right away, to avoid reiterating over the collection | |
try { | |
p = AccessController.doPrivileged( | |
(PrivilegedAction<Permission>) () -> new DeniedPermission(up.getUnresolvedName())); | |
} | |
catch (final RuntimeException re) { | |
// erroneous DeniedPermission (target Permission) name and/or actions; disregard | |
continue; | |
} | |
// force resolution in order to speed up future evaluations | |
defaultPolicy.implies(domain, up); | |
} | |
} | |
if (p instanceof DeniedPermission && p.implies(permission)) { | |
// permission denied | |
return false; | |
} | |
} | |
// permission granted | |
return true; | |
} | |
@Override | |
public PermissionCollection getPermissions(final CodeSource codesource) { | |
return defaultPolicy.getPermissions(codesource); | |
} | |
@Override | |
public PermissionCollection getPermissions(final ProtectionDomain domain) { | |
return defaultPolicy.getPermissions(domain); | |
} | |
@Override | |
public void refresh() { | |
defaultPolicy.refresh(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security.util; | |
import java.text.MessageFormat; | |
import java.util.Objects; | |
public interface InvertibleEnum<I> { | |
/** | |
* Finds an enumerator constant within the specified enumeration by its enumeration-unique | |
* {@linkplain #getId() id} identifier. | |
* | |
* @throws NullPointerException | |
* if <code>enumeration</code> is <code>null</code>. | |
* @throws IllegalArgumentException | |
* if no enumerator constant with the given <code>id</code> could be matched within | |
* <code>enumeration</code>. | |
*/ | |
static <E extends Enum<E> & InvertibleEnum<I>, I> E getById(final Class<E> enumeration, final I id) { | |
Objects.requireNonNull(enumeration, "The [enumeration] argument must not be null."); | |
for (E enumerator : enumeration.getEnumConstants()) { | |
if (enumerator.getId().equals(id)) { | |
return enumerator; | |
} | |
} | |
throw new IllegalArgumentException(MessageFormat.format("Unknown {0} enumerator constant: \"{1}\"", | |
enumeration.getSimpleName(), Objects.toString(id)), null); | |
} | |
/** | |
* Returns an enumeration-unique identifier for this enumerator constant. | |
*/ | |
I getId(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security; | |
import java.lang.reflect.AccessibleObject; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Member; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.ReflectPermission; | |
import java.security.AccessController; | |
import java.security.Permission; | |
import java.security.PrivilegedActionException; | |
import java.security.PrivilegedExceptionAction; | |
import java.text.MessageFormat; | |
import java.util.Objects; | |
import com.example.trusted.security.SelectiveReflectPermission.ReflectiveOperationKind; | |
import com.example.trusted.security.SelectiveReflectPermission.TargetKind; | |
import com.example.trusted.security.util.Arrays; | |
import com.example.trusted.security.util.CheckedFunctions.CheckedSupplier; | |
import com.example.trusted.security.util.Strings; | |
/** | |
* Contains alternatives to some common core reflection methods, allowing for more fine-grained | |
* access control. | |
* <p> | |
* Specifically, this class provides to its clients selective access to retrieve and modify the | |
* accessibility flag of declared members of classes outside of their "isolation unit" (their | |
* <code>ClassLoader</code>, <code>ProtectionDomain</code> pair), based on | |
* {@link SelectiveReflectPermission}s accorded to their domain, in addition to the standard | |
* all-or-nothing {@link RuntimePermission} <code>"accessDeclaredMembers"</code> and | |
* {@link ReflectPermission} <code>"suppressAccessChecks"</code> checked by core reflection. | |
* </p> | |
* <p> | |
* This class does not intend to provide the means for restricting reflective access within clients' | |
* isolation units, or safeguarding against privilege escalation in reflective operations (e.g. due | |
* to operations making themselves use of core reflection facilities)―it solely addresses | |
* accessibility, attempting to give policy configuration administrators additional control over | |
* which members are exposed to particular units of code, thereby promoting more conscious decision | |
* making. | |
* </p> | |
* <p> | |
* Implementations delegate to the corresponding standard methods, upon successful client | |
* authorization. Unless noted otherwise, passing a <code>null</code> argument, or a <em>blank</em> | |
* (<code>null</code>, whitespace-only, or empty) <code>String</code> argument to a method will | |
* cause a <code>NullPointerException</code> or <code>IllegalArgumentException</code>, respectively, | |
* to be raised. | |
* </p> | |
*/ | |
public final class ReflectionGatekeeper { | |
private static final Permission DECLARED_MEMBER_ACCESS_PERMISSION = new RuntimePermission("accessDeclaredMembers"); | |
private static final Permission ACCESS_CHECK_SUPPRESSION_PERMISSION = new ReflectPermission("suppressAccessChecks"); | |
private static final String NULL_TARGET_CLASS_ARG = "The [targetClass] argument must not be null."; | |
private static final String BLANK_FIELD_NAME_ARG; | |
private static final String BLANK_METHOD_NAME_ARG; | |
static { | |
String blankArgFmt = "The [{0}] argument must not be blank."; | |
BLANK_FIELD_NAME_ARG = MessageFormat.format(blankArgFmt, "fieldName"); | |
BLANK_METHOD_NAME_ARG = MessageFormat.format(blankArgFmt, "methodName"); | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredField(String) targetClass.getDeclaredField(fieldName)}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#FIELD},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>fieldName</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws NoSuchFieldException | |
* if <code>targetClass</code> has declared no field of the given name. | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Field getDeclaredField(final Class<?> targetClass, final String fieldName) | |
throws NoSuchFieldException { | |
return (Field) getDeclaredFields0(targetClass, fieldName, false); | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredFields() targetClass.getDeclaredFields()}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#FIELD},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>"*"</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Field[] getDeclaredFields(final Class<?> targetClass) { | |
try { | |
return (Field[]) getDeclaredFields0(targetClass, null, true); | |
} | |
catch (final NoSuchFieldException nsfe) { | |
throw new AssertionError(null, nsfe); | |
} | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredMethod(String, Class...) | |
* targetClass.getDeclaredMethod(methodName, parameterTypes)}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#EXECUTABLE},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>methodName</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws NoSuchMethodException | |
* if <code>targetClass</code> has declared no method of the given name and parameter | |
* types. | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Method getDeclaredMethod(final Class<?> targetClass, final String methodName, | |
final Class<?>... parameterTypes) throws NoSuchMethodException { | |
return (Method) getDeclaredExecutables(targetClass, methodName, false, false, parameterTypes); | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredMethods() targetClass.getDeclaredMethods()}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#EXECUTABLE},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>"*"</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Method[] getDeclaredMethods(final Class<?> targetClass) { | |
try { | |
return (Method[]) getDeclaredExecutables(targetClass, null, true, false); | |
} | |
catch (final NoSuchMethodException nsme) { | |
throw new AssertionError(null, nsme); | |
} | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredConstructor(Class...) | |
* targetClass.getDeclaredConstructor(parameterTypes)}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#EXECUTABLE},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>"<init>"</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws NoSuchMethodException | |
* if <code>targetClass</code> has declared no constructor of the given parameter types. | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static <T> Constructor<T> getDeclaredConstructor(final Class<T> targetClass, | |
final Class<?>... parameterTypes) throws NoSuchMethodException { | |
@SuppressWarnings("unchecked") | |
Constructor<T> ret = (Constructor<T>) getDeclaredExecutables(targetClass, null, false, true, parameterTypes); | |
return ret; | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredConstructors() targetClass.getDeclaredConstructors()}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#EXECUTABLE},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>"<init>"</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Constructor<?>[] getDeclaredConstructors(final Class<?> targetClass) { | |
try { | |
return (Constructor<?>[]) getDeclaredExecutables(targetClass, null, true, true); | |
} | |
catch (final NoSuchMethodException nsme) { | |
throw new AssertionError(null, nsme); | |
} | |
} | |
/** | |
* Delegates to {@link Class#getDeclaredClasses() targetClass.getDeclaredClasses()}. | |
* <p> | |
* If a <code>SecurityManager</code>, <code>sm</code>, is installed, | |
* <code>sm.checkPackageAccess(targetClass.getPackage().getName())</code> is first invoked to check | |
* whether the calling <code>AccessControlContext</code> is allowed to access | |
* <code>targetClass</code>'s package. If successful, <code>sm.checkPermission(Permission)</code> is | |
* invoked once or, should the first test fail, twice, so as to determine actual reflective access | |
* authorization; the permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>RuntimePermission</code> <code>"accessDeclaredMembers"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#MEMBER_CLASS},</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetClass.getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetClass.getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>"*"</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#RETRIEVE_DECLARED} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static Class<?>[] getDeclaredClasses(final Class<?> targetClass) { | |
return (Class<?>[]) getDeclaredClasses0(targetClass); | |
} | |
/** | |
* Delegates to {@link AccessibleObject#setAccessible(boolean) targetMember.setAccessible(flag)}. | |
* <p> | |
* If <code>targetMember</code>'s current {@linkplain AccessibleObject#isAccessible() accessibility | |
* flag} differs from the <code>flag</code> argument, and a <code>SecurityManager</code>, | |
* <code>sm</code>, is installed, <code>sm.checkPermission(Permission)</code> is invoked once or, | |
* should the first test fail, twice, so as to determine reflective access authorization; the | |
* permissions passed are in order: | |
* </p> | |
* <ul> | |
* <li><code>ReflectPermission</code> <code>"suppressAccessChecks"</code>, and</li> | |
* <li><code>SelectiveReflectPermission</code> with the following attributes: | |
* <ul> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetKind() target kind} of | |
* {@link TargetKind#FIELD}, if <code>targetMember</code> is a <code>Field</code>, or | |
* {@link TargetKind#EXECUTABLE} otherwise,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetPackageName() target package name} of | |
* <code>targetMember.getDeclaringClass().getPackage().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetClassName() target class name} of | |
* <code>targetMember.getDeclaringClass().getName()</code>,</li> | |
* <li>{@linkplain SelectiveReflectPermission#getTargetMemberName() target member name} of | |
* <code>targetMember.getName()</code>, and</li> | |
* <li>{@linkplain SelectiveReflectPermission#getReflectiveOperationKinds() reflective operation | |
* kinds} of { {@link ReflectiveOperationKind#MODIFY_ACCESSIBILITY} }.</li> | |
* </ul> | |
* </li> | |
* </ul> | |
* | |
* @throws SecurityException | |
* if the reflective operation is denied. | |
* | |
*/ | |
public static <T extends AccessibleObject & Member> void setAccessible(final T targetMember, final boolean flag) { | |
setAccessible0(targetMember, flag); | |
} | |
private static Object getDeclaredFields0(final Class<?> targetClass, final String fieldName, final boolean all) | |
throws NoSuchFieldException { | |
Objects.requireNonNull(targetClass, NULL_TARGET_CLASS_ARG); | |
if (!all) { | |
Strings.requireNotBlank(fieldName, BLANK_FIELD_NAME_ARG); | |
} | |
SelectiveReflectPermission perm = createReflectiveOperationDescriptor(TargetKind.FIELD, targetClass, fieldName, | |
ReflectiveOperationKind.RETRIEVE_DECLARED); | |
CheckedSupplier<Object, ReflectiveOperationException> action = () -> (all) ? targetClass.getDeclaredFields() | |
: targetClass.getDeclaredField(fieldName); | |
try { | |
return checkAndDelegateReflectiveOperation(action, perm); | |
} | |
catch (final ReflectiveOperationException roe) { | |
if (roe instanceof NoSuchFieldException) { | |
throw (NoSuchFieldException) roe; | |
} | |
throw new AssertionError(null, roe); | |
} | |
} | |
private static Object getDeclaredExecutables(final Class<?> targetClass, final String executableName, | |
final boolean all, boolean constructors, final Class<?>... parameterTypes) throws NoSuchMethodException { | |
Objects.requireNonNull(targetClass, NULL_TARGET_CLASS_ARG); | |
if (!all && !constructors) { | |
Strings.requireNotBlank(executableName, BLANK_METHOD_NAME_ARG); | |
} | |
SelectiveReflectPermission perm = createReflectiveOperationDescriptor(TargetKind.EXECUTABLE, targetClass, | |
(constructors) ? "<init>" : executableName, ReflectiveOperationKind.RETRIEVE_DECLARED); | |
CheckedSupplier<Object, ReflectiveOperationException> action = () -> (constructors) | |
? (all) ? targetClass.getDeclaredConstructors() : targetClass.getDeclaredConstructor(parameterTypes) | |
: (all) ? targetClass.getDeclaredMethods() | |
: targetClass.getDeclaredMethod(executableName, parameterTypes); | |
try { | |
return checkAndDelegateReflectiveOperation(action, perm); | |
} | |
catch (final ReflectiveOperationException roe) { | |
if (roe instanceof NoSuchMethodException) { | |
throw (NoSuchMethodException) roe; | |
} | |
throw new AssertionError(null, roe); | |
} | |
} | |
private static Object getDeclaredClasses0(final Class<?> targetClass) { | |
Objects.requireNonNull(targetClass, NULL_TARGET_CLASS_ARG); | |
SelectiveReflectPermission perm = createReflectiveOperationDescriptor(TargetKind.MEMBER_CLASS, targetClass, | |
null, ReflectiveOperationKind.RETRIEVE_DECLARED); | |
CheckedSupplier<Object, ReflectiveOperationException> action = targetClass::getDeclaredClasses; | |
try { | |
return checkAndDelegateReflectiveOperation(action, perm); | |
} | |
catch (final ReflectiveOperationException roe) { | |
throw new AssertionError(null, roe); | |
} | |
} | |
private static <T extends AccessibleObject & Member> void setAccessible0(final T targetMember, final boolean flag) { | |
Objects.requireNonNull(targetMember, NULL_TARGET_CLASS_ARG); | |
if (targetMember.isAccessible() != flag) { | |
TargetKind kind = (targetMember instanceof Field) ? TargetKind.FIELD : TargetKind.EXECUTABLE; | |
SelectiveReflectPermission perm = createReflectiveOperationDescriptor(kind, | |
targetMember.getDeclaringClass(), targetMember.getName(), | |
ReflectiveOperationKind.MODIFY_ACCESSIBILITY); | |
CheckedSupplier<Object, ReflectiveOperationException> action = () -> { | |
targetMember.setAccessible(flag); | |
return null; | |
}; | |
try { | |
checkAndDelegateReflectiveOperation(action, perm); | |
} | |
catch (final ReflectiveOperationException roe) { | |
throw new AssertionError(null, roe); | |
} | |
} | |
} | |
private static SelectiveReflectPermission createReflectiveOperationDescriptor(final TargetKind targetKind, | |
final Class<?> targetClass, final String memberName, final ReflectiveOperationKind opKind) { | |
StringBuilder sb = new StringBuilder(targetKind.getId()).append("!").append(targetClass.getPackage().getName()) | |
.append("!").append(targetClass.getSimpleName()).append("!"); | |
if (!Strings.isBlank(memberName)) { | |
sb.append(memberName); | |
} | |
return new SelectiveReflectPermission(sb.toString(), opKind.getId()); | |
} | |
private static Object checkAndDelegateReflectiveOperation( | |
final CheckedSupplier<Object, ReflectiveOperationException> action, final SelectiveReflectPermission perm) | |
throws ReflectiveOperationException { | |
SecurityManager sm = System.getSecurityManager(); | |
if (sm != null) { | |
Permission privScope; | |
if (Arrays.contains(perm.getReflectiveOperationKinds(), ReflectiveOperationKind.RETRIEVE_DECLARED)) { | |
/* | |
* In theory we could call sm.checkPermission with a RuntimePermission "package.access." + | |
* targetPkgName instead, thereby achieving a much stricter accessibility policy. Unfortunately core | |
* reflection doesn't, meaning that we can't force the client to play by our rules... | |
*/ | |
sm.checkPackageAccess(perm.getTargetPackageName()); | |
privScope = DECLARED_MEMBER_ACCESS_PERMISSION; | |
} | |
else { | |
// likewise - no accessibility test here, since the standard API itself doesn't bother either | |
privScope = ACCESS_CHECK_SUPPRESSION_PERMISSION; | |
} | |
try { | |
sm.checkPermission(privScope); | |
} | |
catch (final SecurityException general) { | |
try { | |
sm.checkPermission(perm); | |
} | |
catch (final SecurityException specific) { | |
specific.addSuppressed(general); | |
throw specific; | |
} | |
} | |
try { | |
/* | |
* Note that in the case of executables we're merely retrieving them for the client, not invoking | |
* them, so this won't result in privilege escalation. | |
*/ | |
return AccessController.doPrivileged((PrivilegedExceptionAction<Object>) action::get, null, privScope); | |
} | |
catch (final PrivilegedActionException pae) { | |
throw (ReflectiveOperationException) pae.getCause(); | |
} | |
} | |
else { | |
return action.get(); | |
} | |
} | |
private ReflectionGatekeeper() { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security; | |
import java.lang.reflect.AccessibleObject; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Member; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.ReflectPermission; | |
import java.security.BasicPermission; | |
import java.security.Permission; | |
import java.text.MessageFormat; | |
import java.util.EnumSet; | |
import java.util.Iterator; | |
import java.util.Set; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import com.example.trusted.security.util.InvertibleEnum; | |
import com.example.trusted.security.util.Strings; | |
/** | |
* Permission class for limited reflective access, as opposed to the standard | |
* {@link RuntimePermission} <code>"accessDeclaredMembers"</code> and {@link ReflectPermission} | |
* <code>"suppressAccessChecks"</code>, all-or-nothing ones, employed by core reflection.<br/> | |
* <br/> | |
* The {@linkplain #getName() name} format followed is:<br/> | |
* <br/> | |
* <em>((<target_kind>)!(<target_package_name>)!(<target_class_name>)!(<target_member_name>))</em><br/> | |
* <br/> | |
* where: | |
* <ul> | |
* <li><em>(<target_kind>)</em> is the {@linkplain TargetKind#getId() id} of a | |
* {@link TargetKind} enumerator constant,</li> | |
* <li><em>(<target_package_name>)</em> is the package name of the class of the member to | |
* which reflective access is to be granted (to the authorization subject),</li> | |
* <li><em>(<target_class_name>)</em> is the unqualified name (e.g., <code>"Foo$Bar"</code>) | |
* of the class of the member to which reflective access is to be granted, and</li> | |
* <li><em>(<target_member_name>)</em> is the name of the member to which reflective access is | |
* to be granted (<code>"<init>"</code> for constructor matching).</li> | |
* </ul> | |
* The <code>'!'</code> character stands for itself. Using a blank (empty or whitespace-only) | |
* string, or <code>"*"</code> as the value for any of the segments is allowed and indicates any | |
* {@linkplain #implies(Permission) match}. The suffix <code>".*"</code> may additionally be | |
* appended to <em>(<target_package_name>)</em>, unless blank / wildcard, to indicate | |
* "sub-package" matching. As a shorthand for any match, the blank or <code>"*"</code> string may be | |
* used as the entire name, in which case it gets canonicalized to <code>"*!*!*!*"</code>.<br/> | |
* <br/> | |
* The {@linkplain #getActions() actions} format followed is:<br/> | |
* <br/> | |
* <em>(<reflective_operation_kinds>)</em><br/> | |
* <br/> | |
* where (<reflective_operation_kinds>) is a list of comma-separated | |
* {@linkplain ReflectiveOperationKind#getId() ids} of any {@link ReflectiveOperationKind} | |
* enumerator constants, or a blank string, or <code>"*"</code>, which indicate all actions. On | |
* instantiation, duplicates are removed, and the order is canonicalized against | |
* <code>ReflectiveOperationKind</code>'s enumerator constant declaration order.<br/> | |
* <br/> | |
* Example usage for the System-default <code>Policy</code> provider's configuration: | |
* <ul> | |
* <li><code>permission com.example.trusted.security.SelectiveReflectPermission;</code><br/> | |
* Full access, equivalent to that granted by <code>RuntimePermission</code> | |
* <code>"accessDeclaredMembers"</code> <em>and</em> <code>ReflectPermission</code> | |
* <code>"suppressAccessChecks"</code>.</li> | |
* <li><code>permission com.example.trusted.security.SelectiveReflectPermission "field!com.example.*!Comp!foo", "retrieveDeclared";</code><br/> | |
* Grant access to set (but not retrieve, unless granted separately) the | |
* {@linkplain AccessibleObject#isAccessible() accessibility flag} of the field named | |
* <code>"foo"</code>, within classes named <code>"Comp"</code>, under sub-packages of | |
* <code>com.example</code>.</li> | |
* </ul> | |
*/ | |
public final class SelectiveReflectPermission extends BasicPermission { | |
/** | |
* The kind of member(s) that the authorization subject is to be authorized to interact with, i.e., | |
* perform reflective operations on. | |
*/ | |
public enum TargetKind implements InvertibleEnum<String> { | |
/** | |
* {@link Method} or {@link Constructor} access. | |
*/ | |
EXECUTABLE, | |
/** | |
* {@link Field} access. | |
*/ | |
FIELD, | |
/** | |
* Member {@link Class} access. | |
*/ | |
MEMBER_CLASS("memberClass"), | |
/** | |
* Any {@link Member}. | |
*/ | |
ANY(WILDCARD_NAME_COMP) { | |
@Override | |
public boolean implies(final TargetKind other) { | |
return true; | |
} | |
}; | |
private final String id; | |
private TargetKind() { | |
this(null); | |
} | |
private TargetKind(final String id) { | |
this.id = (id == null) ? name().toLowerCase() : id; | |
} | |
@Override | |
public String getId() { | |
return id; | |
} | |
/** | |
* Indicates whether the supplied enumerator constant is implied by this one. | |
* <p> | |
* <code>true</code> is returned iff <code>other</code> is equal to this enumerator, or this | |
* enumerator is {@link #ANY}. | |
* </p> | |
*/ | |
public boolean implies(final TargetKind other) { | |
return this == other; | |
} | |
} | |
/** | |
* The kind of operation(s) that the authorization subject is to be permitted to perform. | |
*/ | |
public enum ReflectiveOperationKind implements InvertibleEnum<String> { | |
/** | |
* Access declared {@link Member}s. | |
*/ | |
RETRIEVE_DECLARED("retrieveDeclared"), | |
/** | |
* Modify a {@link Member}'s {@linkplain AccessibleObject#isAccessible() accessibility flag}. | |
*/ | |
MODIFY_ACCESSIBILITY("modifyAccessibility"), | |
/** | |
* Any operation. | |
*/ | |
ANY(WILDCARD_NAME_COMP) { | |
@Override | |
public boolean implies(final ReflectiveOperationKind other) { | |
return true; | |
} | |
}; | |
private final String id; | |
private ReflectiveOperationKind() { | |
this(null); | |
} | |
private ReflectiveOperationKind(final String id) { | |
this.id = (id == null) ? name().toLowerCase() : id; | |
} | |
@Override | |
public String getId() { | |
return id; | |
} | |
/** | |
* Indicates whether the supplied enumerator constant is implied by this one. | |
* <p> | |
* <code>true</code> is returned iff <code>other</code> is equal to this enumerator, or this | |
* enumerator is {@link #ANY}. | |
* </p> | |
*/ | |
public boolean implies(final ReflectiveOperationKind other) { | |
return this == other; | |
} | |
} | |
private static final class NameInfo { | |
private static final String WILDCARD_NAME = MessageFormat.format("{0}{1}{0}{1}{0}{1}{0}", WILDCARD_NAME_COMP, | |
NAME_COMP_SEP); | |
private static final NameInfo WILDCARD_NAME_INFO = new NameInfo(WILDCARD_NAME, TargetKind.ANY, | |
WILDCARD_NAME_COMP, WILDCARD_NAME_COMP, WILDCARD_NAME_COMP); | |
private static NameInfo validateAndCreate(final String name) { | |
String _name = name; | |
TargetKind targetKind; | |
String targetPackageName, targetClassName, targetMemberName; | |
if (Strings.isBlank(_name)) { | |
return WILDCARD_NAME_INFO; | |
} | |
Matcher nameValidator = NAME_FMT.matcher(_name); | |
if (!nameValidator.matches()) { | |
throw new IllegalArgumentException(MessageFormat.format(MALFORMED_ARG, "name", _name), null); | |
} | |
String comp = nameValidator.group(2); | |
if (Strings.isBlank(comp)) { | |
comp = WILDCARD_NAME_COMP; | |
} | |
targetKind = InvertibleEnum.getById(TargetKind.class, comp); | |
StringBuilder sb = new StringBuilder(targetKind.id).append(NAME_COMP_SEP); | |
comp = nameValidator.group(4); | |
if (Strings.isBlank(comp)) { | |
comp = WILDCARD_NAME_COMP; | |
} | |
sb.append(targetPackageName = comp).append(NAME_COMP_SEP); | |
comp = nameValidator.group(10); | |
if (Strings.isBlank(comp)) { | |
comp = WILDCARD_NAME_COMP; | |
} | |
sb.append(targetClassName = comp).append(NAME_COMP_SEP); | |
comp = nameValidator.group(16); | |
if (Strings.isBlank(comp)) { | |
comp = WILDCARD_NAME_COMP; | |
} | |
sb.append(targetMemberName = comp); | |
_name = sb.toString(); | |
if (WILDCARD_NAME.equals(_name)) { | |
return WILDCARD_NAME_INFO; | |
} | |
return new NameInfo(_name, targetKind, targetPackageName, targetClassName, targetMemberName); | |
} | |
private final TargetKind targetKind; | |
private final String name; | |
private final String targetPackageName; | |
private final String targetClassName; | |
private final String targetMemberName; | |
private final boolean wildcard; | |
private final int hashCode; | |
private NameInfo(final String name, final TargetKind targetKind, final String targetPackageName, | |
final String targetClassName, final String targetMemberName) { | |
this.name = name; | |
this.targetKind = targetKind; | |
this.targetPackageName = targetPackageName; | |
this.targetClassName = targetClassName; | |
this.targetMemberName = targetMemberName; | |
wildcard = WILDCARD_NAME.equals(name); | |
hashCode = 31 + name.hashCode(); | |
} | |
@Override | |
public int hashCode() { | |
return hashCode; | |
} | |
@Override | |
public boolean equals(final Object obj) { | |
if (this == obj) { | |
return true; | |
} | |
if (obj == null) { | |
return false; | |
} | |
if (!(obj instanceof NameInfo)) { | |
return false; | |
} | |
NameInfo other = (NameInfo) obj; | |
if (!name.equals(other.name)) { | |
return false; | |
} | |
return true; | |
} | |
private boolean implies(final NameInfo other) { | |
return equals(other) || isWildcard() | |
|| (targetKind.implies(other.targetKind) && targetPackageNameImplies(other.targetPackageName) | |
&& targetClassNameImplies(other.targetClassName) | |
&& targetMemberNameImplies(other.targetMemberName)); | |
} | |
private boolean isWildcard() { | |
return wildcard; | |
} | |
private boolean targetPackageNameImplies(final String otherTargetPackageName) { | |
return targetPackageName.equals(WILDCARD_NAME_COMP) || targetPackageName.equals(otherTargetPackageName) | |
|| (targetPackageName.endsWith("." + WILDCARD_NAME_COMP) && otherTargetPackageName | |
.startsWith(targetPackageName.substring(0, targetPackageName.length() - 1))); | |
} | |
private boolean targetClassNameImplies(final String otherTargetClassName) { | |
return targetClassName.equals(WILDCARD_NAME_COMP) || targetClassName.equals(otherTargetClassName); | |
} | |
private boolean targetMemberNameImplies(final String otherTargetMemberName) { | |
return targetMemberName.equals(WILDCARD_NAME_COMP) || targetMemberName.equals(otherTargetMemberName); | |
} | |
} | |
private static final class ActionsInfo { | |
private static final String WILDCARD_ACTIONS = "*"; | |
private static final Set<ReflectiveOperationKind> ALL_REFLECTIVE_OPS = EnumSet.of(ReflectiveOperationKind.ANY); | |
private static final ActionsInfo WILDCARD_ACTIONS_INFO = new ActionsInfo(WILDCARD_ACTIONS, ALL_REFLECTIVE_OPS); | |
private static ActionsInfo validateAndCreate(final String actions) { | |
String _actions = actions; | |
Set<ReflectiveOperationKind> reflectiveOperationKinds; | |
if (Strings.isBlank(_actions)) { | |
return WILDCARD_ACTIONS_INFO; | |
} | |
else { | |
Matcher nameValidator = ACTIONS_FMT.matcher(_actions); | |
if (!nameValidator.matches()) { | |
throw new IllegalArgumentException(MessageFormat.format(MALFORMED_ARG, "actions", _actions), null); | |
} | |
String[] reflectiveOperationKindStrs = _actions.split(", *"); | |
reflectiveOperationKinds = EnumSet.noneOf(ReflectiveOperationKind.class); | |
for (String reflectiveOperationKindStr : reflectiveOperationKindStrs) { | |
ReflectiveOperationKind opKind = InvertibleEnum.getById(ReflectiveOperationKind.class, | |
reflectiveOperationKindStr); | |
if (opKind == ReflectiveOperationKind.ANY) { | |
return WILDCARD_ACTIONS_INFO; | |
} | |
reflectiveOperationKinds.add(opKind); | |
} | |
StringBuilder sb = new StringBuilder(); | |
Iterator<ReflectiveOperationKind> itr = reflectiveOperationKinds.iterator(); | |
do { | |
sb.append(itr.next().id); | |
if (itr.hasNext()) { | |
sb.append(","); | |
} | |
} while (itr.hasNext()); | |
_actions = sb.toString(); | |
} | |
return new ActionsInfo(_actions, reflectiveOperationKinds); | |
} | |
private final String actions; | |
private final Set<ReflectiveOperationKind> reflectiveOperationKinds; | |
private final boolean wildcard; | |
private final int hashCode; | |
private ActionsInfo(final String actions, final Set<ReflectiveOperationKind> reflectiveOperationKinds) { | |
this.actions = actions; | |
this.reflectiveOperationKinds = reflectiveOperationKinds; | |
hashCode = 31 + actions.hashCode(); | |
this.wildcard = WILDCARD_ACTIONS.equals(actions); | |
} | |
@Override | |
public int hashCode() { | |
return hashCode; | |
} | |
@Override | |
public boolean equals(final Object obj) { | |
if (this == obj) { | |
return true; | |
} | |
if (obj == null) { | |
return false; | |
} | |
if (!(obj instanceof ActionsInfo)) { | |
return false; | |
} | |
ActionsInfo other = (ActionsInfo) obj; | |
if (!actions.equals(other.actions)) { | |
return false; | |
} | |
return true; | |
} | |
private boolean implies(final ActionsInfo other) { | |
if (equals(other) || isWildcard()) { | |
return true; | |
} | |
outer: for (ReflectiveOperationKind reflectiveOperationKind : reflectiveOperationKinds) { | |
for (ReflectiveOperationKind otherReflectiveOperationKind : other.reflectiveOperationKinds) { | |
if (!reflectiveOperationKind.implies(otherReflectiveOperationKind)) { | |
continue outer; | |
} | |
} | |
return true; | |
} | |
return false; | |
} | |
private boolean isWildcard() { | |
return wildcard; | |
} | |
} | |
private static final String WILDCARD_NAME_COMP = "*"; | |
private static final String NAME_COMP_SEP = "!"; | |
private static final Pattern NAME_FMT; | |
private static final Pattern ACTIONS_FMT; | |
static { | |
String alphaNum = "a-zA-Z0-9"; | |
String wcd = WILDCARD_NAME_COMP; | |
String enumConstChars = alphaNum + wcd; | |
String sep = NAME_COMP_SEP; | |
String wsp = " *"; | |
String wcdOrWspGroup = "(\\" + wcd + "|" + wsp + ")"; | |
String enumConstGroup = "([" + enumConstChars + "]*|(\\" + wcd + "))"; | |
String enumConstsSeg = "[" + enumConstChars + "]+"; | |
String enumConstsGroup = "((" + enumConstsSeg + "+((," + wsp + enumConstsSeg + ")*)?)|" + wcdOrWspGroup + ")"; | |
String alphaNumSym = alphaNum + "$_<>"; | |
String jIdSeg = "[" + alphaNumSym + "]+"; | |
String jIdGroup = "((" + jIdSeg + "((\\." + jIdSeg + ")*)(\\.\\" + wcd + ")?)|" + wcdOrWspGroup + ")"; | |
NAME_FMT = Pattern.compile( | |
"(" + enumConstGroup + sep + jIdGroup + sep + jIdGroup + sep + jIdGroup + ")|" + wcdOrWspGroup); | |
ACTIONS_FMT = Pattern.compile(enumConstsGroup); | |
} | |
/** | |
* A <code>SelectiveReflectPermission</code> {@linkplain #implies(Permission) implying} all others, | |
* as well as <code>RuntimePermission</code> <code>"accessDeclaredMembers"</code> and | |
* <code>ReflectPermission</code> <code>"suppressAccessChecks"</code>. | |
*/ | |
public static final SelectiveReflectPermission WILDCARD_INSTANCE = new SelectiveReflectPermission(); | |
private static final String MALFORMED_ARG = "Malformed {0}: \"{1}\""; | |
private static final ActionsInfo ACCESS_DECLARED_MEMBERS_ACTIONS_INFO = ActionsInfo | |
.validateAndCreate(ReflectiveOperationKind.RETRIEVE_DECLARED.id), | |
SUPPRESS_ACCESS_CHECKS_ACTIONS_INFO = ActionsInfo | |
.validateAndCreate(ReflectiveOperationKind.MODIFY_ACCESSIBILITY.id); | |
private static final long serialVersionUID = 1L; | |
private final NameInfo nameInfo; | |
private final ActionsInfo actionsInfo; | |
private final boolean wildcard; | |
private final int hashCode; | |
/** | |
* Instantiates a <code>SelectiveReflectPermission</code> having a name equal to | |
* <code>"*!*!*!*"</code> and actions equal to <code>"*"</code>. | |
* | |
* @see #WILDCARD_INSTANCE | |
*/ | |
public SelectiveReflectPermission() { | |
this((String) null, (String) null); | |
} | |
/** | |
* Instantiates a <code>SelectiveReflectPermission</code> having the provided <code>name</code> and | |
* actions equal to <code>"*"</code>. | |
* <p> | |
* A blank (null, empty, or whitespace-only) <code>name</code> is interpreted as | |
* <code>"*!*!*!*"</code>. | |
* </p> | |
* | |
* @throws IllegalArgumentException | |
* if <code>name</code> fails to meet the name format, as per the | |
* {@linkplain SelectiveReflectPermission overview}. | |
*/ | |
public SelectiveReflectPermission(final String name) { | |
this(name, null); | |
} | |
/** | |
* Instantiates a <code>SelectiveReflectPermission</code> having the provided <code>name</code> and | |
* <code>actions</code>. | |
* <p> | |
* A blank (null, empty, or whitespace-only) <code>name</code> and/or <code>value</code> is | |
* respectively interpreted as <code>"*!*!*!*"</code> and <code>"*"</code>. | |
* </p> | |
* | |
* @throws IllegalArgumentException | |
* if <code>name</code> and/or <code>actions</code> fail to meet their corresponding | |
* format, as per the {@linkplain SelectiveReflectPermission overview}. | |
*/ | |
public SelectiveReflectPermission(final String name, final String actions) { | |
this(NameInfo.validateAndCreate(name), ActionsInfo.validateAndCreate(actions)); | |
} | |
private SelectiveReflectPermission(final NameInfo nameInfo, final ActionsInfo actionsInfo) { | |
super(nameInfo.name, actionsInfo.actions); | |
this.nameInfo = nameInfo; | |
this.actionsInfo = actionsInfo; | |
wildcard = (nameInfo.isWildcard() && actionsInfo.isWildcard()); | |
int prime = 31; | |
int hashCode = prime + nameInfo.hashCode; | |
hashCode = prime * hashCode + actionsInfo.hashCode; | |
this.hashCode = hashCode; | |
} | |
/** | |
* Indicates whether this <code>SelectiveReflectPermission</code> implies the provided permission | |
* argument.<br/> | |
* <br/> | |
* <code>true</code> is returned iff: | |
* <ul> | |
* <li><code>p</code> is a <code>SelectiveReflectPermission</code> {@linkplain #equals(Object) | |
* equal} to this one, or having a {@linkplain #getName() name} and {@linkplain #getActions() | |
* actions} that are, as per the {@linkplain SelectiveReflectPermission overview}, respectively | |
* implied (matched) by this instance's name and actions.</li> | |
* <li><code>p</code> is either a <code>RuntimePermission</code> with a name equal to | |
* <code>"accessDeclaredMembers"</code>, or a <code>ReflectPermission</code> with a name equal to | |
* <code>"suppressAccessChecks"</code>, and this instance {@linkplain #isWildcard() implies full} | |
* reflective privileges.</li> | |
* </ul> | |
*/ | |
@Override | |
public boolean implies(final Permission p) { | |
if (equals(p)) { | |
return true; | |
} | |
if (p == null) { | |
return false; | |
} | |
if (p instanceof SelectiveReflectPermission) { | |
SelectiveReflectPermission other = (SelectiveReflectPermission) p; | |
return nameInfo.implies(other.nameInfo) && actionsInfo.implies(other.actionsInfo); | |
} | |
boolean retrieve = (p instanceof RuntimePermission && "accessDeclaredMembers".equals(p.getName())); | |
if (retrieve || (p instanceof ReflectPermission && "suppressAccessChecks".equals(p.getName()))) { | |
if (isWildcard()) { | |
return true; | |
} | |
if (nameInfo.isWildcard()) { | |
if (retrieve) { | |
if (ACCESS_DECLARED_MEMBERS_ACTIONS_INFO.implies(actionsInfo)) { | |
return true; | |
} | |
} | |
else if (SUPPRESS_ACCESS_CHECKS_ACTIONS_INFO.implies(actionsInfo)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
/** | |
* Indicates whether this <code>SelectiveReflectPermission</code> implies full reflective | |
* privileges. | |
* <p> | |
* <code>true</code> is returned iff the instance's {@linkplain #getName() name} equals | |
* <code>"*!*!*!*"</code>, and its {@linkplain #getActions() actions} equal <code>"*"</code>. | |
* </p> | |
* | |
* @see #WILDCARD_INSTANCE | |
*/ | |
public boolean isWildcard() { | |
return wildcard; | |
} | |
/** | |
* Returns the <code>SelectiveReflectPermission</code>'s hash code, based on its | |
* {@linkplain #getName() name} and {@linkplain #getActions() actions}. | |
* | |
* @return The instance's hash code value. | |
*/ | |
@Override | |
public int hashCode() { | |
return hashCode; | |
} | |
/** | |
* Tests for equivalence between this instance and the provided argument. | |
* <p> | |
* <code>true</code> is returned iff <code>obj</code> is itself a | |
* <code>SelectiveReflectPermission</code>, whose {@linkplain #getName() name} and | |
* {@linkplain #getActions() actions} are equal to this instance's ones. | |
* </p> | |
* | |
* @return <code>true</code> iff this instance and the given one are equivalent. | |
*/ | |
@Override | |
public boolean equals(final Object obj) { | |
if (this == obj) { | |
return true; | |
} | |
if (obj == null) { | |
return false; | |
} | |
if (!(obj instanceof SelectiveReflectPermission)) { | |
return false; | |
} | |
SelectiveReflectPermission other = (SelectiveReflectPermission) obj; | |
if (!nameInfo.equals(other.nameInfo)) { | |
return false; | |
} | |
if (!actionsInfo.equals(other.actionsInfo)) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Returns the {@link TargetKind} expressed by the instance's {@linkplain #getName() name}. | |
*/ | |
public TargetKind getTargetKind() { | |
return nameInfo.targetKind; | |
} | |
/** | |
* Returns the target package name expressed by the instance's {@linkplain #getName() name}. | |
*/ | |
public String getTargetPackageName() { | |
return nameInfo.targetPackageName; | |
} | |
/** | |
* Returns the target class name expressed by the instance's {@linkplain #getName() name}. | |
*/ | |
public String getTargetClassName() { | |
return nameInfo.targetClassName; | |
} | |
/** | |
* Returns the target member name expressed by the instance's {@linkplain #getName() name}. | |
*/ | |
public String getTargetMemberName() { | |
return nameInfo.targetMemberName; | |
} | |
/** | |
* Returns the instance's actions string. | |
* | |
* @return The actions string. | |
*/ | |
@Override | |
public String getActions() { | |
return actionsInfo.actions; | |
} | |
/** | |
* Returns {@link ReflectiveOperationKind}s expressed by the instance's {@linkplain #getActions() | |
* actions}. | |
* <p> | |
* Elements in the returned array are ordered according to <code>ReflectiveOperationKind</code>'s | |
* enumerator constant declaration order. A new array is returned on each invocation of this method; | |
* modifying it does not affect the permission. | |
* </p> | |
*/ | |
public ReflectiveOperationKind[] getReflectiveOperationKinds() { | |
return actionsInfo.reflectiveOperationKinds.toArray(new ReflectiveOperationKind[0]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted.security.util; | |
public final class Strings { | |
public static boolean isBlank(final String s) { | |
return (s == null) || isWhitespace(s); | |
} | |
public static boolean isWhitespace(final String s) { | |
return (s != null) && s.trim().isEmpty(); | |
} | |
public static String requireNotBlank(final String s) { | |
return requireNotBlank(s, "The argument must not be blank."); | |
} | |
public static String requireNotBlank(final String s, final String exMsg) { | |
if (isBlank(s)) { | |
throw new IllegalArgumentException(exMsg, null); | |
} | |
return s; | |
} | |
private Strings() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment