Created
August 11, 2017 17:28
-
-
Save anonymous/b8dcdd33fb838829b16e41d82b59fcab to your computer and use it in GitHub Desktop.
Answer code for https://stackoverflow.com/questions/36091323
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted; | |
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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"; | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted; | |
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"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.untrusted; | |
public class Nasty { | |
public static void doBadThingsToYou() { | |
System.out.println(System.getProperty("user.name")); | |
System.out.println("yay!"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted; | |
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.trusted; | |
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