/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.start;
import com.sun.jini.collection.WeakIdentityMap;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Permissions;
import java.security.PermissionCollection;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.Security;
import java.security.SecurityPermission;
import java.util.Map;
import java.util.WeakHashMap;
import net.jini.security.SecurityContext;
import net.jini.security.policy.DynamicPolicy;
import net.jini.security.policy.PolicyInitializationException;
import net.jini.security.policy.SecurityContextSource;
/**
* Security policy provider which supports associating security sub-policies
* with context class loaders. Permission queries and grants (if supported),
* as well as <code>implies</code> and <code>refresh</code> operations are
* delegated to the currently active sub-policy.
* <p>
* The currently active sub-policy is determined as follows: if the current
* thread does not override the {@link Thread#getContextClassLoader
* getContextClassLoader} method, then that method is called to obtain the
* context class loader. If the context class loader is associated with a
* sub-policy (via a previous call to <code>setPolicy</code>), then that
* sub-policy is the currently active sub-policy. If no such association
* exists, then the same check is performed on each non-<code>null</code>
* parent of the context class loader, proceeding up the chain of class loader
* delegation, until a sub-policy association is found, in which case the
* associated sub-policy is the currently active sub-policy. If no sub-policy
* association is found for the context class loader or any of its parents,
* then a fallback sub-policy, the main policy, is the currently active
* sub-policy. Also, if the current thread overrides the
* <code>getContextClassLoader</code> method, then
* <code>getContextClassLoader</code> is not called and the main policy is the
* currently active sub-policy.
*
* @author Sun Microsystems, Inc.
*
* @since 2.0
*/
public class AggregatePolicyProvider
extends Policy implements DynamicPolicy, SecurityContextSource
{
private static final String mainPolicyClassProperty =
"com.sun.jini.start.AggregatePolicyProvider.mainPolicyClass";
private static final String defaultMainPolicyClass =
"net.jini.security.policy.DynamicPolicyProvider";
private static final Map trustGetCCL = new WeakHashMap();
private static final ProtectionDomain myDomain = (ProtectionDomain)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return AggregatePolicyProvider.class.getProtectionDomain();
}
});
private WeakIdentityMap subPolicies = new WeakIdentityMap();
private WeakIdentityMap subPolicyCache = new WeakIdentityMap();
private Policy mainPolicy;
/**
* Creates a new <code>AggregatePolicyProvider</code> instance, containing
* a main policy created as follows: if the
* <code>com.sun.jini.start.AggregatePolicyProvider.mainPolicyClass</code>
* security property is set, then its value is interpreted as the class
* name of the main policy provider; otherwise, a default class name of
* <code>"net.jini.security.policy.DynamicPolicyProvider"</code>
* is used. The main policy is then instantiated using the no-arg public
* constructor of the named class. If the main policy class is not found,
* is not instantiable via a public no-arg constructor, or if invocation of
* its constructor fails, then a <code>PolicyInitializationException</code>
* is thrown.
* <p>
* Note that this constructor requires the appropriate
* <code>"getProperty"</code> {@link SecurityPermission} to read the
* <code>com.sun.jini.start.AggregatePolicyProvider.mainPolicyClass</code>
* security property, and may require <code>"accessClassInPackage.*"</code>
* {@link RuntimePermission}s, depending on the package of the main policy
* class.
*
* @throws PolicyInitializationException if unable to construct the main
* policy
* @throws SecurityException if there is a security manager and the
* calling context does not have <code>SecurityPermission</code>
* for reading the
* <code>com.sun.jini.start.AggregatePolicyProvider.mainPolicy</code>
* security property, or if the calling context does not have
* adequate permissions to access the main policy class
*/
public AggregatePolicyProvider() throws PolicyInitializationException {
String cname = Security.getProperty(mainPolicyClassProperty);
if (cname == null) {
cname = defaultMainPolicyClass;
}
try {
mainPolicy = (Policy) Class.forName(cname).newInstance();
} catch (SecurityException e) {
throw e;
} catch (Exception e) {
throw new PolicyInitializationException(
"unable to construct main policy", e);
}
ensureDependenciesResolved();
}
/**
* Creates a new <code>AggregatePolicyProvider</code> instance with the
* given main policy, which must be non-<code>null</code>.
*
* @param mainPolicy main policy
* @throws NullPointerException if main policy is <code>null</code>
*/
public AggregatePolicyProvider(Policy mainPolicy) {
if (mainPolicy == null) {
throw new NullPointerException();
}
this.mainPolicy = mainPolicy;
ensureDependenciesResolved();
}
/**
* Delegates to the corresponding <code>getPermissions</code> method of the
* currently active sub-policy to return the set of permissions allowed for
* code from the specified code source, as a newly-created mutable
* <code>PermissionCollection</code> which supports heterogeneous
* permission types.
*
* @param source code source for which to look up permissions
* @return set of permissions allowed for the given code source
*/
public PermissionCollection getPermissions(CodeSource source) {
return getCurrentSubPolicy().getPermissions(source);
}
/**
* If the given protection domain is the protection domain of this class,
* then a newly-created <code>PermissionCollection</code> containing {@link
* AllPermission} is returned. Otherwise, delegates to the corresponding
* <code>getPermissions</code> method of the currently active sub-policy to
* return the set of permissions allowed for code in the specified
* protection domain, as a newly-created mutable
* <code>PermissionCollection</code> which supports heterogeneous
* permission types.
*
* @param domain protection domain for which to look up permissions
* @return set of permissions allowed for given protection domain
*/
public PermissionCollection getPermissions(ProtectionDomain domain) {
if (domain == myDomain) {
PermissionCollection pc = new Permissions();
pc.add(new AllPermission());
return pc;
} else {
return getCurrentSubPolicy().getPermissions(domain);
}
}
/**
* If the given protection domain is the protection domain of this class,
* then <code>true</code> is returned. Otherwise, delegates to the
* <code>implies</code> method of the currently active sub-policy to
* determine if the given permission is implied by the permissions for the
* specified protection domain.
*
* @param domain protection domain in which to check implication
* @param permission permission to test implication of
* @return <code>true</code> if permission is implied by permissions of
* given protection domain, <code>false</code> otherwise
*/
public boolean implies(ProtectionDomain domain, Permission permission) {
return (domain == myDomain) ||
getCurrentSubPolicy().implies(domain, permission);
}
/**
* Refreshes the currently active sub-policy by delegating to its
* <code>refresh</code> method.
*/
public void refresh() {
getCurrentSubPolicy().refresh();
}
/**
* Changes sub-policy association with given class loader. If
* <code>subPolicy</code> is non-<code>null</code>, then it is used as a
* new sub-policy to associate with the given class loader, overriding any
* previous sub-policy associated with the loader. If
* <code>subPolicy</code> is <code>null</code>, then any previous
* association between a sub-policy and the given class loader is removed.
* If loader is <code>null</code>, then <code>subPolicy</code> is used as
* the new main policy, and must be non-<code>null</code>. If there is a
* security manager, its <code>checkPermission</code> method is called with
* the <code>"setPolicy"</code> {@link SecurityPermission}.
*
* @param loader class loader with which to associate sub-policy, or
* <code>null</code> if setting main policy
* @param subPolicy sub-policy to associate with given class loader, or
* <code>null</code> if removing sub-policy association
* @throws NullPointerException if both <code>loader</code> and
* <code>subPolicy</code> are <code>null</code>
* @throws SecurityException if there is a security manager and the
* calling context does not have the <code>"setPolicy"
* SecurityPermission</code>
*/
public void setPolicy(ClassLoader loader, Policy subPolicy) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SecurityPermission("setPolicy"));
}
synchronized (subPolicies) {
subPolicyCache.clear();
if (loader != null) {
if (subPolicy != null) {
subPolicies.put(loader, subPolicy);
subPolicyCache.put(loader, subPolicy);
} else {
subPolicies.remove(loader);
}
} else {
if (subPolicy == null) {
throw new NullPointerException();
}
mainPolicy = subPolicy;
}
}
}
/**
* Returns <code>true</code> if the currently active sub-policy supports
* dynamic grants; this is determined by delegating to the
* <code>grantSupported</code> method of the currently active sub-policy if
* it implements the {@link DynamicPolicy} interface. If the currently
* active sub-policy does not implement <code>DynamicPolicy</code>, then
* <code>false</code> is returned.
*
* @return <code>true</code> if the currently active sub-policy supports
* dynamic grants, or <code>false</code> otherwise
*/
public boolean grantSupported() {
Policy p = getCurrentSubPolicy();
return (p instanceof DynamicPolicy &&
((DynamicPolicy) p).grantSupported());
}
/**
* If the currently active sub-policy supports dynamic permission grants,
* delegates to the corresponding <code>grant</code> method of the
* currently active sub-policy to grant the specified permissions to all
* protection domains (including ones not yet created) which are associated
* with the class loader of the given class and possess at least the given
* set of principals.
*
* @param cl {@inheritDoc}
* @param principals {@inheritDoc}
* @param permissions {@inheritDoc}
* @throws UnsupportedOperationException {@inheritDoc}
* @throws SecurityException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void grant(Class cl,
Principal[] principals,
Permission[] permissions)
{
Policy p = getCurrentSubPolicy();
if (p instanceof DynamicPolicy) {
((DynamicPolicy) p).grant(cl, principals, permissions);
} else {
throw new UnsupportedOperationException("grants not supported");
}
}
/**
* If the currently active sub-policy supports dynamic permission grants,
* delegates to the corresponding <code>getGrants</code> method of the
* currently active sub-policy to return a new array containing the set of
* permissions dynamically granted to protection domains which are
* associated with the class loader of the given class and possess at least
* the given set of principals.
*
* @param cl {@inheritDoc}
* @param principals {@inheritDoc}
* @return {@inheritDoc}
* @throws UnsupportedOperationException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Permission[] getGrants(Class cl, Principal[] principals) {
Policy p = getCurrentSubPolicy();
if (p instanceof DynamicPolicy) {
return ((DynamicPolicy) p).getGrants(cl, principals);
} else {
throw new UnsupportedOperationException("grants not supported");
}
}
/**
* Returns a snapshot of the current security context, which can be used to
* restore the context at a later time.
* <p>
* The security context returned by this method contains the security
* context of the currently active sub-policy (or an equivalent of the
* default security context described in the documentation for {@link
* net.jini.security.Security#getContext
* Security.getContext}, if the currently active sub-policy does not
* implement {@link SecurityContextSource}), as well as the current context
* class loader. The privileged action wrappers it creates restore the
* saved context class loader before delegating to the action wrappers of
* the underlying sub-policy security context. The
* <code>getAccessControlContext</code> method of the returned security
* context delegates to the corresponding method of the sub-policy security
* context.
*
* @return {@inheritDoc}
*/
public SecurityContext getContext() {
Policy p = getCurrentSubPolicy();
SecurityContext sc = (p instanceof SecurityContextSource) ?
((SecurityContextSource) p).getContext() :
new DefaultSecurityContext();
return new AggregateSecurityContext(sc);
}
/**
* Ensures that any classes depended on by this policy provider are
* resolved. This is to preclude lazy resolution of such classes during
* operation of the provider, which can result in deadlock as described by
* bug 4911907.
*/
private void ensureDependenciesResolved() {
// get any non-null class loader
ClassLoader ldr = getClass().getClassLoader();
if (ldr == null) {
ldr = ClassLoader.getSystemClassLoader();
}
// force class resolution by pre-invoking methods called by implies()
trustGetContextClassLoader0(Thread.class);
getContextClassLoader();
synchronized (subPolicies) {
lookupSubPolicy(ldr);
}
}
/**
* Returns currently active sub-policy.
*/
private Policy getCurrentSubPolicy() {
final Thread t = Thread.currentThread();
if (!trustGetContextClassLoader(t)) {
return mainPolicy;
}
ClassLoader ccl = getContextClassLoader();
synchronized (subPolicies) {
Policy policy = (Policy) subPolicyCache.get(ccl);
if (policy == null) {
policy = lookupSubPolicy(ccl);
subPolicyCache.put(ccl, policy);
}
return policy;
}
}
/**
* Returns sub-policy associated with the given class loader. This method
* should only be called when already synchronized on subPolicies.
*/
private Policy lookupSubPolicy(final ClassLoader ldr) {
assert Thread.holdsLock(subPolicies);
return (Policy) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
for (ClassLoader l = ldr; l != null; l = l.getParent()) {
Policy p = (Policy) subPolicies.get(l);
if (p != null) {
return p;
}
}
return mainPolicy;
}
});
}
/**
* Returns current context class loader.
*/
static ClassLoader getContextClassLoader() {
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
/**
* Returns true if the given thread does not override
* Thread.getContextClassLoader(), false otherwise.
*/
private static boolean trustGetContextClassLoader(Thread t) {
Class cl = t.getClass();
if (cl == Thread.class) {
return true;
}
Boolean b;
synchronized (trustGetCCL) {
b = (Boolean) trustGetCCL.get(cl);
}
if (b == null) {
b = trustGetContextClassLoader0(cl);
synchronized (trustGetCCL) {
trustGetCCL.put(cl, b);
}
}
return b.booleanValue();
}
private static Boolean trustGetContextClassLoader0(final Class cl) {
return (Boolean) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
Method m = cl.getMethod(
"getContextClassLoader", new Class[0]);
return Boolean.valueOf(m.getDeclaringClass() == Thread.class);
} catch (NoSuchMethodException ex) {
throw new InternalError(
"Thread.getContextClassLoader() not found");
}
}
});
}
/**
* Stand-in "default" security context for sub-policies that do not
* implement SecurityContextSource.
*/
private static class DefaultSecurityContext implements SecurityContext {
private final AccessControlContext acc =
AccessController.getContext();
public PrivilegedAction wrap(PrivilegedAction a) {
if (a == null) {
throw new NullPointerException();
}
return a;
}
public PrivilegedExceptionAction wrap(PrivilegedExceptionAction a) {
if (a == null) {
throw new NullPointerException();
}
return a;
}
public AccessControlContext getAccessControlContext() {
return acc;
}
}
/**
* Security context that produces privileged action wrappers which restore
* the context class loader before delegating to the sub-policy context's
* wrapped action.
*/
private static class AggregateSecurityContext implements SecurityContext {
private final ClassLoader ccl = getContextClassLoader();
private final SecurityContext sc;
AggregateSecurityContext(SecurityContext sc) {
if (sc == null) {
throw new NullPointerException();
}
this.sc = sc;
}
public PrivilegedAction wrap(PrivilegedAction a) {
final PrivilegedAction wa = sc.wrap(a);
return new PrivilegedAction() {
public Object run() {
ClassLoader sccl = setCCL(ccl, false);
try {
return wa.run();
} finally {
setCCL(sccl, sccl != ccl);
}
}
};
}
public PrivilegedExceptionAction wrap(PrivilegedExceptionAction a) {
final PrivilegedExceptionAction wa = sc.wrap(a);
return new PrivilegedExceptionAction() {
public Object run() throws Exception {
ClassLoader sccl = setCCL(ccl, false);
try {
return wa.run();
} finally {
setCCL(sccl, sccl != ccl);
}
}
};
}
public AccessControlContext getAccessControlContext() {
return sc.getAccessControlContext();
}
private ClassLoader setCCL(final ClassLoader ldr, final boolean force) {
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
Thread t = Thread.currentThread();
ClassLoader old = null;
if (force || ldr != (old = t.getContextClassLoader())) {
t.setContextClassLoader(ldr);
}
return old;
}
});
}
}
}