Skip to content

Instantly share code, notes, and snippets.

Created August 11, 2017 17:28
Show Gist options
  • Save anonymous/b8dcdd33fb838829b16e41d82b59fcab to your computer and use it in GitHub Desktop.
Save anonymous/b8dcdd33fb838829b16e41d82b59fcab to your computer and use it in GitHub Desktop.
package com.example.trusted;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* <code>ClassLoader</code> that assigns a distinct <code>ProtectionDomain</code> to each class it
* {@linkplain #defineClass(String, byte[], int, int, ProtectionDomain) defines}. The
* <code>URL</code> of the <code>CodeSource</code> of every <code>ProtectionDomain</code> assigned,
* is one of "file" protocol, and path equal to the file system path of the corresponding .class
* file.
*/
public final class DiscriminatingClasspathClassLoader extends URLClassLoader {
private static final String JAVA_HOME = System.getProperty("java.home");
public DiscriminatingClasspathClassLoader(ClassLoader parent) {
super(new URL[0], parent);
String[] classpathEntries = System.getProperty("java.class.path").split(File.pathSeparator);
List<String> searchPath = new ArrayList<>(classpathEntries.length + 2);
//
searchPath.add(JAVA_HOME);
searchPath.addAll(Arrays.asList(classpathEntries));
for (String searchPathEntry : searchPath) {
try {
if (!searchPathEntry.endsWith(".jar") && !searchPathEntry.endsWith("/")) {
// URL ClassLoader assumes paths without a trailing '/' to be JARs by default
searchPathEntry = searchPathEntry.concat("/");
}
addURL(new URL("file:".concat(searchPathEntry)));
}
catch (MalformedURLException mue) {
System.err.println("Erroneous search path entry ".concat(searchPathEntry).concat(" skipped."));
}
}
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> ret;
synchronized (getClassLoadingLock(name)) {
ret = findLoadedClass(name);
if (ret != null) {
return ret;
}
// check whether "name" refers to a system class (privileged block needed because acc might not have
// permission to read from the bootstrap classpath)
// note (OpenJDK 1.8.0.141): using a lambda here yields a NPE at
// java.lang.invoke.LambdaForm$Name.<init>(LambdaForm.java:1407),
// when this instance is used as the system class loader during initialization
URL potentialSystemClassResource = AccessController.doPrivileged(new PrivilegedAction<URL>() {
@Override
public URL run() {
return getClassResource(name);
}
});
// getPath() starts with "file:" if potentialSystemClassResource is a jar URL ("file:" is not
// considered to be part of the protocol)
if (potentialSystemClassResource == null
|| potentialSystemClassResource.getPath().replace("file:", "").startsWith(JAVA_HOME)) {
// system class - delegate to default implementation (and ultimately to parent)
return super.loadClass(name, resolve);
}
ret = findClassInternal(name, potentialSystemClassResource);
if (resolve) {
resolveClass(ret);
}
}
return ret;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
URL classResource = getClassResource(name);
if (classResource == null) {
return super.findClass(name);
}
return findClassInternal(name, classResource);
}
private ByteBuffer readClassData(URL classResource) {
try (InputStream in = new BufferedInputStream(classResource.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int i;
while ((i = in.read()) != -1) {
out.write(i);
}
return ByteBuffer.wrap(out.toByteArray());
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private ProtectionDomain constructClassDomain(URL codeSourceLocation) {
CodeSource cs = new CodeSource(codeSourceLocation, (Certificate[]) null);
return new ProtectionDomain(cs, getPermissions(cs), this, null);
}
private URL getClassResource(String name) {
return getResource(name.replace(".", "/").concat(".class"));
}
private Class<?> findClassInternal(String name, URL classResource) {
return defineClass(name, readClassData(classResource), constructClassDomain(classResource));
}
}
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// no default permissions
grant {};
// trusted code
grant codeBase "file:/path/to/classpath-entry-for-trusted-code/-" {
permission java.security.AllPermission;
};
// dynamically whitelisted code
grant codeBase "file:/path/to/classpath-entry-for-untrusted-code/com/example/untrusted/Nasty.class?override" {
permission java.util.PropertyPermission "user.*", "read";
};
package com.example.trusted;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import com.example.trusted.SubstitutingDomainCombiner.DomainReplacementRule.SimpleProtectionDomainReplacement;
import com.example.untrusted.Nasty;
public class Main {
public static void main(String... args) {
System.setSecurityManager(new StricterThreadPolicyEnforcingSecurityManager());
ProtectionDomain sandboxedPd = Nasty.class.getProtectionDomain();
SimpleProtectionDomainReplacement whitelistedPd;
// copy static permissions from original domain
// you can assign further permissions here directly, if you don't feel like using the policy
PermissionCollection whitelistedPdPerms = new Permissions();
Enumeration<Permission> sandboxedPdPerms = sandboxedPd.getPermissions().elements();
while (sandboxedPdPerms.hasMoreElements()) {
whitelistedPdPerms.add(sandboxedPdPerms.nextElement());
}
CodeSource sandboxedPdCs = sandboxedPd.getCodeSource();
try {
// create the stand-in domain
whitelistedPd = new SimpleProtectionDomainReplacement(
new CodeSource(new URL(sandboxedPdCs.getLocation() + "?override"), sandboxedPdCs.getCertificates()),
whitelistedPdPerms, sandboxedPd.getClassLoader(), sandboxedPd.getPrincipals());
}
catch (MalformedURLException mue) {
throw new RuntimeException(mue);
}
SubstitutingDomainCombiner combiner = new SubstitutingDomainCombiner();
combiner.addDomainReplacementRule(
new SubstitutingDomainCombiner.StaticDomainReplacementRule(sandboxedPdCs, whitelistedPd));
combiner.setDebug(true);
SubstitutingDomainCombiner.doWithDomainReplacement((PrivilegedAction<Void>) () -> {
Nasty.doBadThingsToYou(); // whitelisted invocation - no AccessControlException
return null;
}, combiner);
try {
Nasty.doBadThingsToYou(); // sandboxed invocation - AccessControlException
}
catch (AccessControlException ace) {
System.out.println("nay");
}
}
}
package com.example.untrusted;
public class Nasty {
public static void doBadThingsToYou() {
System.out.println(System.getProperty("user.name"));
System.out.println("yay!");
}
}
package com.example.trusted;
import java.security.Permission;
/**
* <code>SecurityManager</code> that unconditionally enforces access control on
* {@link #checkAccess(Thread)} and {@link #checkAccess(ThreadGroup)}.
*/
public final class StricterThreadPolicyEnforcingSecurityManager extends SecurityManager {
private static final Permission MODIFY_THREAD_PERM = new RuntimePermission("modifyThread"),
MODIFY_THREAD_GROUP_PERM = new RuntimePermission("modifyThreadGroup");
@Override
public void checkAccess(Thread t) {
checkPermission(MODIFY_THREAD_PERM);
}
@Override
public void checkAccess(ThreadGroup g) {
checkPermission(MODIFY_THREAD_GROUP_PERM);
}
}
package com.example.trusted;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.BasicPermission;
import java.security.CodeSource;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
import com.example.trusted.SubstitutingDomainCombiner.DomainReplacementRule.ProtectionDomainReplacement;
/**
* A <code>SubstitutingDomainCombiner</code> performs <code>ProtectionDomain</code> replacements in
* the current <code>AccessControlContext</code>, based on the {@link DomainReplacementRule}s it is
* provided with.
* <p>
* When {@linkplain #setSubjectDomainCombiner(SubjectDomainCombiner) given} a
* <code>SubjectDomainCombiner</code>, this implementation forwards its
* {@linkplain #combine(ProtectionDomain[], ProtectionDomain[]) modified} stack of domains to that
* combiner, thereby preserving the semantics of <code>Subject</code>-based authorization.
* </p>
* <p>
* Instances of this class are thread-safe if all of their encapsulated domain replacement rules are
* thread-safe themselves.
* </p>
*/
public final class SubstitutingDomainCombiner implements DomainCombiner {
/**
* <code>DomainReplacementRule</code>s are consulted during
* {@linkplain SubstitutingDomainCombiner#combine(ProtectionDomain[], ProtectionDomain[]) combine}
* invocations in order to produce replacements for the current <code>AccessControlContext</code>'s
* <code>ProtectionDomain</code>s.
*/
public interface DomainReplacementRule
extends BiFunction<ProtectionDomain[], ProtectionDomain[], ProtectionDomain[]> {
/**
* Marker interface for indicating <code>ProtectionDomain</code>
* {@linkplain DomainReplacementRule#apply(ProtectionDomain[], ProtectionDomain[]) replacements}.
* Implementations <em>must</em> be <code>ProtectionDomain</code> subclasses.
*/
public interface ProtectionDomainReplacement {
}
/**
* {@link ProtectionDomainReplacement} that is functionally equivalent to a standard
* <code>ProtectionDomain</code>.
*/
public static class SimpleProtectionDomainReplacement extends ProtectionDomain
implements ProtectionDomainReplacement {
public SimpleProtectionDomainReplacement(CodeSource src, PermissionCollection perms) {
super(src, perms);
}
public SimpleProtectionDomainReplacement(CodeSource src, PermissionCollection perms, ClassLoader loader,
Principal[] pcps) {
super(src, perms, loader, pcps);
}
}
/**
* Modifies the current <code>AccessControlContext</code>'s <code>ProtectionDomain</code>s.<br/>
* <br/>
* The implementation <em>may</em> modify the <code>currentDomains</code> array by replacing any of
* its elements with:
* <ul>
* <li>a null domain, indicating that the element is to be discarded, or</li>
* <li>with a domain that implements the {@link ProtectionDomainReplacement} interface.</li>
* </ul>
* The implementation <em>must not</em>:
* <ul>
* <li>Replace any null elements.</li>
* <li>Replace any <code>ProtectionDomainReplacement</code>s already present.</li>
* <li>Replace any element with a domain that is not a
* <code>ProtectionDomainReplacement</code>.</li>
* </ul>
* <p>
* If the implementation chooses to return a different array, that array <em>must not</em> be a null
* reference. Additionally, that array <em>must</em> (except when valid replacements have been
* performed as listed above) contain the same elements as <code>currentDomains</code> and in the
* same order. Failing to meet any of the aforementioned requirements will cause the delegating
* <code>SubstitutingDomainCombiner</code> to disregard the implementation's changes.
* </p>
* <p>
* The <code>assignedDomains</code> represent the <code>AccessControlContext</code> inherited by a
* parent thread or a <code>doPrivileged</code> invocation. This stack of domains is only provided
* to facilitate replacement decisions; while the implementation is free to modify that array as it
* pleases, any changes made will be ignored by the delegator.
* </p>
* <p>
* Any exceptions raised from the implementation during
* {@linkplain SubstitutingDomainCombiner#combine(ProtectionDomain[], ProtectionDomain[])
* delegation} are ignored.
* </p>
*
* @param currentDomains
* The non-null array of the current access control context's domains, which may contain
* null elements, as well as replacements made by previously applied replacement rules.
* @param assignedDomains
* The non-null array of the inherited access control context's domains, which is not
* subject to further modification, and may contain null elements, as well as
* replacements made by previously applied replacement rules (during a previous
* invocation of the delegator's <code>combine</code> method by the runtime).
* @return <code>currentDomain</code>s or the copy wherein any replacements occurred (never null).
*/
@Override
ProtectionDomain[] apply(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains);
}
/**
* Immutable, deterministic <code>DomainReplacementRule</code>, that always replaces
* <code>ProtectionDomain</code>s matched against a specific <code>CodeSource</code> with the same
* domain. Both "matching rule" and replacement domain are given at instantiation time.
*/
public static final class StaticDomainReplacementRule implements DomainReplacementRule {
private final CodeSource matchingRule;
private final ProtectionDomain replacement;
/**
* @throws NullPointerException
* if any argument is null.
*/
public <R extends ProtectionDomain & ProtectionDomainReplacement> StaticDomainReplacementRule(
CodeSource matchingRule, R replacement) {
Objects.requireNonNull(matchingRule);
Objects.requireNonNull(replacement);
this.matchingRule = matchingRule;
this.replacement = replacement;
}
/**
* Attempts to find any <code>ProtectionDomain</code>s in <code>currentDomains</code> whose
* respective <code>CodeSource</code>s are {@linkplain CodeSource#implies(CodeSource) implied} by
* the <code>CodeSource</code> provided at instantiation. The first matched domain is replaced with
* the replacement domain initially provided; any subsequent matches are replaced with a null.
*/
@Override
public ProtectionDomain[] apply(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) {
boolean matched = false;
for (int i = 0; i < currentDomains.length; i++) {
ProtectionDomain pd = currentDomains[i];
if (pd instanceof ProtectionDomainReplacement) {
continue;
}
if (matchingRule.implies(pd.getCodeSource())) {
if (!matched) {
currentDomains[i] = replacement;
matched = true;
}
else {
currentDomains[i] = null;
}
}
}
return currentDomains;
}
}
/**
* <code>Permission</code> used to guard <code>SubstitutingDomainCombiner</code> accessor / mutator
* access.<br/>
* <br/>
* Names follow <code>BasicPermission</code>'s conventions and are restricted to:
* <dl>
* <dt>"SubjectDomainCombiner.get"</dt>
* <dd><code>SubjectDomainCombiner</code> retrieval</dd>
* <dt>"SubjectDomainCombiner.set"</dt>
* <dd><code>SubjectDomainCombiner</code> assignment</dd>
* <dt>"DomainReplacementRule.get"</dt>
* <dd><code>DomainReplacementRule</code> retrieval</dd>
* <dt>"DomainReplacementRule.set"</dt>
* <dd><code>DomainReplacementRule</code> association or removal</dd>
* <dt>"debug"</dt>
* <dd><code>Debug output activation or deactivation</dd>
* </dl>
*/
public static final class SubstitutingDomainCombinerPermission extends BasicPermission {
private static final Pattern NAME_SYNTAX = Pattern
.compile("(SubjectDomainCombiner|DomainReplacementRule|debug)(\\.(get|set|\\*))?|\\*");
private static final long serialVersionUID = 1300507099979907060L;
/**
* @see BasicPermission#BasicPermission(String)
*/
public SubstitutingDomainCombinerPermission(String name) {
this(name, null);
}
/**
* @throws IllegalArgumentException
* if an unrecognized <code>name</code> is provided, as per the
* {@linkplain SubstitutingDomainCombinerPermission overview}.
* @see BasicPermission#BasicPermission(String, String)
*/
public SubstitutingDomainCombinerPermission(String name, String actions) {
super(name, null);
if (!NAME_SYNTAX.matcher(name).matches()) {
throw new IllegalArgumentException(MessageFormat.format("Invalid name [{0}]", name));
}
}
}
private static final Subject BLANK_SUBJECT = new Subject(true, Collections.emptySet(), Collections.emptySet(),
Collections.emptySet());
private static final Permission SUBJECT_DOMAIN_COMBINER_GET_AND_SET_PERM = new SubstitutingDomainCombinerPermission(
"SubjectDomainCombiner.*"),
DOMAIN_REPLACEMENT_RULE_GET_AND_SET_PERM = new SubstitutingDomainCombinerPermission(
"DomainReplacementRule.*"),
DEBUG_SET_PERM = new SubstitutingDomainCombinerPermission("debug.*"),
CREATE_ACC_PERM = new SecurityPermission("createAccessControlContext"), ALL_PERMS = new AllPermission();
private static final ProtectionDomain[] NO_PDS = new ProtectionDomain[0];
private static final String LINE_SEP = AccessController
.doPrivileged((PrivilegedAction<String>) () -> System.getProperty("line.separator")), NULL_STR = "<null>",
EMPTY_STR = "<empty>", PD_STR_FMT = "ProtectionDomain '{' {0} '}'", CS_STR_FMT = "CodeSource '{' {0} '}'",
URL_STR_FMT = "Location = [ {0} ]";
/**
* Performs the specified <code>action</code> with the privileges of the current
* <code>AccessControlContext</code>, subject to modification by the given <code>combiner</code>.
* Whether the effective privileges during execution of the action will be greater or lesser than,
* or the same as, those currently accorded, depends entirely on the combiner's
* {@link DomainReplacementRule}s.
* <p>
* If a <code>SecurityManager</code> is installed and its <code>checkPermission</code> method denies
* the "createAccessControlContext" <code>SecurityPermission</code>, the <code>action</code> is
* performed without privilege modification.
* </p>
*
* @throws NullPointerException
* if any argument is null.
*/
public static <T> T doWithDomainReplacement(PrivilegedAction<T> action, SubstitutingDomainCombiner combiner) {
Objects.requireNonNull(action);
Objects.requireNonNull(combiner);
try {
checkPermission(CREATE_ACC_PERM);
}
catch (SecurityException se) {
return action.run();
}
return AccessController.doPrivileged(action, new AccessControlContext(AccessController.getContext(), combiner));
}
private static void checkPermission(Permission p) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(p);
}
}
private final Map<String, DomainReplacementRule> domainReplacementRules = new LinkedHashMap<>();
private final Object subjectDomainCombinerLock = new Object();
private SubjectDomainCombiner subjectDomainCombiner = new SubjectDomainCombiner(BLANK_SUBJECT);
private int replacementRuleCounter;
private volatile boolean debugEnabled;
/**
* Instantiates a <code>SubstitutingDomainCombiner</code> void of <code>ReplacementRule</code>s and
* <code>SubjectDomainCombiner</code>.
*/
public SubstitutingDomainCombiner() {
}
/**
* Replaces the <code>SubjectDomainCombiner</code> for delegation.
*
* @return The <code>SubjectDomainCombiner</code> previously in use, or null if unset.
* @throws SecurityException
* if a <code>SecurityManager</code> is installed and its <code>checkPermission</code>
* method denies the "SubjectDomainCombiner.*"
* <code>SubstitutingDomainCombinerPermission</code>.
*/
public SubjectDomainCombiner setSubjectDomainCombiner(SubjectDomainCombiner sdc) {
checkPermission(SUBJECT_DOMAIN_COMBINER_GET_AND_SET_PERM);
SubjectDomainCombiner ret = null;
synchronized (subjectDomainCombinerLock) {
if (sdc == null) {
if (AccessController
.doPrivileged((PrivilegedAction<Subject>) subjectDomainCombiner::getSubject) != null) {
ret = subjectDomainCombiner;
subjectDomainCombiner = new SubjectDomainCombiner(BLANK_SUBJECT);
}
}
else {
ret = subjectDomainCombiner;
subjectDomainCombiner = sdc;
}
return ret;
}
}
/**
* Adds the given <code>DomainReplacementRule</code> to the combiner's list of rules. The new rule
* will only be evaluated after all previously specified ones have yielded no result.
*
* @return A non-null identifier that can be passed to {@link #removeDomainReplacementRule(String)}
* in order to unregister the newly-added rule.
* @throws NullPointerException
* if the argument is null.
* @throws SecurityException
* if a <code>SecurityManager</code> is installed and its <code>checkPermission</code>
* method denies the "DomainReplacementRule.*"
* <code>SubstitutingDomainCombinerPermission</code>.
*/
public String addDomainReplacementRule(DomainReplacementRule rule) {
checkPermission(DOMAIN_REPLACEMENT_RULE_GET_AND_SET_PERM);
Objects.requireNonNull(rule);
synchronized (domainReplacementRules) {
for (Map.Entry<String, DomainReplacementRule> e : domainReplacementRules.entrySet()) {
if (e.getValue().equals(rule)) {
return e.getKey();
}
}
String ret = Integer.toString(++replacementRuleCounter);
domainReplacementRules.put(ret, rule);
return ret;
}
}
/**
* Dissociates the <code>DomainReplacementRule</code> identified by <code>ruleId</code> from the
* <code>SubstitutingDomainCombiner</code>.
*
* @param ruleId
* A rule identifier previously returned by a call to
* {@link #addDomainReplacementRule(DomainReplacementRule)}.
* @return The removed rule, or null if there isn't one identified by <code>ruleId</code>.
* @throws NullPointerException
* if the argument is null.
* @throws SecurityException
* if a <code>SecurityManager</code> is installed and its <code>checkPermission</code>
* method denies the "DomainReplacementRule.*"
* <code>SubstitutingDomainCombinerPermission</code>.
*/
public DomainReplacementRule removeDomainReplacementRule(String ruleId) {
checkPermission(DOMAIN_REPLACEMENT_RULE_GET_AND_SET_PERM);
Objects.requireNonNull(ruleId);
synchronized (domainReplacementRules) {
return domainReplacementRules.remove(ruleId);
}
}
/**
* Enables or disables <code>combine</code> debug output.
*
* @throws SecurityException
* if a <code>SecurityManager</code> is installed and its <code>checkPermission</code>
* method denies the "debug.*" <code>SubstitutingDomainCombinerPermission</code>.
*/
public void setDebug(boolean enable) {
checkPermission(DEBUG_SET_PERM);
debugEnabled = enable;
}
/**
* Combines the given <code>ProtectionDomain</code> arrays.<br/>
* <br/>
* Specifically:
* <ol>
* <li>The <code>currentDomains</code>, if any, are copied into a new array,
* <em>modifiedCurrentDomains</em>.</li>
* <li>The {@link DomainReplacementRule}s are consulted in
* {@linkplain #addDomainReplacementRule(DomainReplacementRule) insertion} order, until either all
* elements in <em>modifiedCurrentDomains</em> have been replaced, or there are no rules left to
* delegate to.</li>
* <li>Any duplicate or null domains, as well as any domains that
* {@linkplain ProtectionDomain#implies(Permission) imply} <code>AllPermission</code>, are removed
* from <em>modifiedCurrentDomains</em>.</li>
* <li><code>sdc.combine(modifiedCurrentDomains, assignedDomains)</code> is returned, where
* <code>sdc</code> is either the <code>SubjectDomainCombiner</code>
* {@linkplain #setSubjectDomainCombiner(SubjectDomainCombiner) associated} with this combiner, or,
* if absent, a new one encapsulating an empty <code>Subject</code> void of principals and
* credentials.</li>
* </ol>
*/
@Override
public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) {
debugCombineEntry(currentDomains, assignedDomains);
if (currentDomains == null || currentDomains.length == 0) {
// no SubjectDomainCombiner delegation, as it wouldn't care about the assignedDomains anyway
debugCombineReturn(assignedDomains);
return assignedDomains;
}
synchronized (domainReplacementRules) {
synchronized (subjectDomainCombinerLock) {
if (domainReplacementRules.isEmpty()) {
// no replacement rules to apply
return subjectDomainCombiner.combine(currentDomains, assignedDomains);
}
// documentation doesn't explicitly allow input array modification
ProtectionDomain[] currentPdsCopy = arrayCopy(currentDomains),
assignedPdsCopy = (assignedDomains == null || assignedDomains.length == 0) ? NO_PDS
: arrayCopy(assignedDomains);
// apply replacement rules
nextRule: for (DomainReplacementRule rule : domainReplacementRules.values()) {
// have the all domains been replaced by the previously applied rules?
for (ProtectionDomain pd : currentPdsCopy) {
if (pd != null && !(pd instanceof ProtectionDomainReplacement)) {
// no
break;
}
// yes
break nextRule;
}
// apply next rule
ProtectionDomain[] ruleOutput = null;
try {
ruleOutput = rule.apply(currentPdsCopy, assignedPdsCopy);
}
catch (RuntimeException re) {
// ignore rule output
}
// the rule could modify those later on - don't bother checking for equality
currentPdsCopy = arrayCopy(currentDomains);
assignedPdsCopy = arrayCopy(assignedDomains);
// validate rule output:
// - same number of elements
// - no replacement of domains that are themselves replacements previously introduced
// - no replacement of null domains (whether originally present or previously introduced)
if (ruleOutput == null || ruleOutput.length != currentPdsCopy.length
|| Arrays.equals(currentPdsCopy, ruleOutput)) {
continue;
}
ruleOutput = arrayCopy(ruleOutput);
for (int i = 0; i < currentPdsCopy.length; i++) {
ProtectionDomain pre = currentPdsCopy[i], post = ruleOutput[i];
if (pre == null) {
if (post != null) {
continue nextRule;
}
}
if (!pre.equals(post)) {
if (pre instanceof ProtectionDomainReplacement) {
continue nextRule;
}
if (post != null && !(post instanceof ProtectionDomainReplacement)) {
continue nextRule;
}
}
}
// validation passed - accept changes
currentPdsCopy = arrayCopy(ruleOutput);
}
Set<ProtectionDomain> processedCurrentPds = new LinkedHashSet<>(Arrays.asList(currentPdsCopy));
// get rid of the null domain, as well as any fully privileged domains, as neither would affect
// privilege intersection
processedCurrentPds.remove(null);
Iterator<ProtectionDomain> itr = processedCurrentPds.iterator();
while (itr.hasNext()) {
if (itr.next().implies(ALL_PERMS)) {
itr.remove();
}
}
if (!processedCurrentPds.isEmpty()) {
ProtectionDomain[] combined = subjectDomainCombiner.combine(processedCurrentPds.toArray(NO_PDS),
assignedDomains);
debugCombineReturn(combined);
return combined;
}
debugCombineReturn(assignedDomains);
// no SubjectDomainCombiner delegation, as it wouldn't care about the assignedDomains anyway
return assignedDomains;
}
}
}
private <T> T[] arrayCopy(T[] t) {
return Arrays.copyOf(t, t.length);
}
private void debugCombineEntry(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) {
if (debugEnabled) {
StringBuilder sb = new StringBuilder(LINE_SEP)
.append(MessageFormat.format("combine method entry on {0}.", this)).append(" Input PDs:")
.append(LINE_SEP).append("\tCurrent:");
appendDomains(currentDomains, sb);
sb.append(LINE_SEP).append("\t").append("Assigned:");
appendDomains(assignedDomains, sb);
sb.append(LINE_SEP);
System.out.println(sb);
}
}
private void debugCombineReturn(ProtectionDomain[] combined) {
if (debugEnabled) {
StringBuilder sb = new StringBuilder(LINE_SEP)
.append(MessageFormat.format("combine method return on {0}.", this)).append(" Output PDs:")
.append(LINE_SEP).append("\tCombined: ");
appendDomains(combined, sb);
sb.append(LINE_SEP);
System.out.println(sb);
}
}
private void appendDomains(ProtectionDomain[] domains, StringBuilder sb) {
if (domains == null) {
sb.append(NULL_STR);
}
else if (domains.length == 0) {
sb.append(EMPTY_STR);
}
else {
for (ProtectionDomain domain : domains) {
sb.append(LINE_SEP);
sb.append("\t\t");
appendDomain(domain, sb);
}
}
}
private void appendDomain(ProtectionDomain domain, StringBuilder sb) {
String pdStr;
if (domain == null) {
pdStr = NULL_STR;
}
else {
CodeSource cs = domain.getCodeSource();
String csStr;
if (cs == null) {
csStr = NULL_STR;
}
else {
URL u = cs.getLocation();
String uStr = MessageFormat.format(URL_STR_FMT, (u == null) ? NULL_STR : u);
csStr = MessageFormat.format(CS_STR_FMT, uStr);
}
pdStr = MessageFormat.format(PD_STR_FMT, csStr);
}
sb.append(pdStr);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment