Skip to content

Instantly share code, notes, and snippets.

Created October 1, 2017 10:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/e1a3ab0436baec0b460e7d2b38a731b7 to your computer and use it in GitHub Desktop.
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.
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>"&lt;&lt;ALL FILES&gt;&gt;</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;
}
}
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() {
}
}
package com.example.trusted.security.util;
public final class CheckedFunctions {
@FunctionalInterface
public interface CheckedSupplier<T, E extends Exception> {
T get() throws E;
}
private CheckedFunctions() {
}
}
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/>
* &nbsp;&nbsp;&nbsp;&nbsp;<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/>
* &nbsp;&nbsp;&nbsp;&nbsp;<em>&lt;target_class_name&gt;(:&lt;target_name&gt;(:&lt;target_actions&gt;))</em><br/>
* <br/>
* where:
* <ul>
* <li><em>&lt;target_class_name&gt;</em> is the fully qualified name of the target permission's
* class,</li>
* <li><em>(&lt;target_name&gt;)</em> is, optionally, the {@linkplain #getName() name} of the target
* permission,</li>
* <li><em>(&lt;target_actions&gt;)</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;
}
}
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();
}
}
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();
}
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>"&lt;init&gt;"</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>"&lt;init&gt;"</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() {
}
}
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/>
* &nbsp;&nbsp;&nbsp;&nbsp;<em>((&lt;target_kind&gt;)!(&lt;target_package_name&gt;)!(&lt;target_class_name&gt;)!(&lt;target_member_name&gt;))</em><br/>
* <br/>
* where:
* <ul>
* <li><em>(&lt;target_kind&gt;)</em> is the {@linkplain TargetKind#getId() id} of a
* {@link TargetKind} enumerator constant,</li>
* <li><em>(&lt;target_package_name&gt;)</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>(&lt;target_class_name&gt;)</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>(&lt;target_member_name&gt;)</em> is the name of the member to which reflective access is
* to be granted (<code>"&lt;init&gt;"</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>(&lt;target_package_name&gt;)</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/>
* &nbsp;&nbsp;&nbsp;&nbsp;<em>(&lt;reflective_operation_kinds&gt;)</em><br/>
* <br/>
* where (&lt;reflective_operation_kinds&gt;) 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/>
* &nbsp;&nbsp;&nbsp;&nbsp;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/>
* &nbsp;&nbsp;&nbsp;&nbsp;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]);
}
}
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