/*
* 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 net.jini.security.policy;
import com.sun.jini.collection.WeakIdentityMap;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Principal;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import net.jini.security.GrantPermission;
/**
* Security policy provider that supports dynamic granting of permissions at
* run-time. This provider is designed as a wrapper to layer dynamic grant
* functionality on top of an underlying policy provider. If the underlying
* provider does not implement the {@link DynamicPolicy} interface, then its
* permission mappings are assumed to change only when its {@link
* Policy#refresh refresh} method is called. Permissions are granted on the
* granularity of class loader; granting a permission requires (of the calling
* context) {@link GrantPermission} for that permission.
*
* @author Sun Microsystems, Inc.
*
* @since 2.0
*/
public class DynamicPolicyProvider extends Policy implements DynamicPolicy {
private static final String basePolicyClassProperty =
"net.jini.security.policy." +
"DynamicPolicyProvider.basePolicyClass";
private static final String defaultBasePolicyClass =
"net.jini.security.policy.PolicyFileProvider";
private static final ProtectionDomain sysDomain = (ProtectionDomain)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() { return Object.class.getProtectionDomain(); }
});
private final Policy basePolicy;
private final boolean cacheBasePerms;
private final WeakIdentityMap domainPerms = new WeakIdentityMap();
private final WeakIdentityMap loaderGrants = new WeakIdentityMap();
private final Grants globalGrants = new Grants();
/**
* Creates a new <code>DynamicPolicyProvider</code> instance that wraps a
* default underlying policy. The underlying policy is created as follows:
* if the
* <code>net.jini.security.policy.DynamicPolicyProvider.basePolicyClass</code>
* security property is set, then its value is interpreted as the class
* name of the base (underlying) policy provider; otherwise, a default
* class name of
* <code>"net.jini.security.policy.PolicyFileProvider"</code>
* is used. The base policy is then instantiated using the no-arg public
* constructor of the named class. If the base 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 java.security.SecurityPermission} to
* read the
* <code>net.jini.security.policy.DynamicPolicyProvider.basePolicyClass</code>
* security property, and may require <code>"accessClassInPackage.*"</code>
* {@link RuntimePermission}s, depending on the package of the base policy
* class.
*
* @throws PolicyInitializationException if unable to construct the base
* policy
* @throws SecurityException if there is a security manager and the
* calling context does not have adequate permissions to read the
* <code>net.jini.security.policy.DynamicPolicyProvider.basePolicyClass</code>
* security property, or if the calling context does not have
* adequate permissions to access the base policy class
*/
public DynamicPolicyProvider() throws PolicyInitializationException {
String cname = Security.getProperty(basePolicyClassProperty);
if (cname == null) {
cname = defaultBasePolicyClass;
}
try {
basePolicy = (Policy) Class.forName(cname).newInstance();
} catch (SecurityException e) {
throw e;
} catch (Exception e) {
throw new PolicyInitializationException(
"unable to construct base policy", e);
}
cacheBasePerms = !(basePolicy instanceof DynamicPolicy);
ensureDependenciesResolved();
}
/**
* Creates a new <code>DynamicPolicyProvider</code> instance that wraps
* around the given non-<code>null</code> base policy object.
*
* @param basePolicy base policy object containing information about
* non-dynamic grants
* @throws NullPointerException if <code>basePolicy</code> is
* <code>null</code>
*/
public DynamicPolicyProvider(Policy basePolicy) {
if (basePolicy == null) {
throw new NullPointerException();
}
this.basePolicy = basePolicy;
cacheBasePerms = !(basePolicy instanceof DynamicPolicy);
ensureDependenciesResolved();
}
/**
* Behaves as specified by {@link Policy#getPermissions(CodeSource)}.
*/
public PermissionCollection getPermissions(CodeSource source) {
PermissionCollection pc = basePolicy.getPermissions(source);
Permission[] pa = globalGrants.get(null);
for (int i = 0; i < pa.length; i++) {
Permission p = pa[i];
if (!pc.implies(p)) {
pc.add(p);
}
}
return pc;
}
/**
* Behaves as specified by {@link Policy#getPermissions(ProtectionDomain)}.
*/
public PermissionCollection getPermissions(ProtectionDomain domain) {
return getDomainPermissions(domain).getPermissions(domain);
}
/**
* Behaves as specified by {@link Policy#implies}.
*/
public boolean implies(ProtectionDomain domain, Permission permission) {
return getDomainPermissions(domain).implies(permission, domain);
}
/**
* Behaves as specified by {@link Policy#refresh}.
*/
public void refresh() {
basePolicy.refresh();
if (cacheBasePerms) {
synchronized (domainPerms) {
domainPerms.clear();
}
}
}
// documentation inherited from DynamicPolicy.grantSupported
public boolean grantSupported() {
return true;
}
// documentation inherited from DynamicPolicy.grant
public void grant(Class cl,
Principal[] principals,
Permission[] permissions)
{
if (cl != null) {
checkDomain(cl);
}
if (principals != null && principals.length > 0) {
principals = (Principal[]) principals.clone();
checkNullElements(principals);
}
if (permissions == null || permissions.length == 0) {
return;
}
permissions = (Permission[]) permissions.clone();
checkNullElements(permissions);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new GrantPermission(permissions));
}
Grants g = (cl != null) ?
getLoaderGrants(getClassLoader(cl)) : globalGrants;
g.add(principals, permissions);
}
// documentation inherited from DynamicPolicy.getGrants
public Permission[] getGrants(Class cl, Principal[] principals) {
if (cl != null) {
checkDomain(cl);
}
if (principals != null && principals.length > 0) {
principals = (Principal[]) principals.clone();
checkNullElements(principals);
}
List l = Arrays.asList(globalGrants.get(principals));
if (cl != null) {
l = new ArrayList(l);
l.addAll(Arrays.asList(
getLoaderGrants(getClassLoader(cl)).get(principals)));
}
PermissionCollection pc = new Permissions();
for (Iterator i = l.iterator(); i.hasNext(); ) {
Permission p = (Permission) i.next();
if (!pc.implies(p)) {
pc.add(p);
}
}
l = Collections.list(pc.elements());
return (Permission[]) l.toArray(new Permission[l.size()]);
}
/**
* 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() {
// force class resolution by pre-invoking method called by implies()
getDomainPermissions(sysDomain);
}
private DomainPermissions getDomainPermissions(ProtectionDomain pd) {
DomainPermissions dp;
synchronized (domainPerms) {
dp = (DomainPermissions) domainPerms.get(pd);
}
if (dp == null) {
dp = new DomainPermissions(pd);
globalGrants.register(dp);
if (pd != null) {
getLoaderGrants(pd.getClassLoader()).register(dp);
}
synchronized (domainPerms) {
domainPerms.put(pd, dp);
}
}
return dp;
}
private Grants getLoaderGrants(ClassLoader ldr) {
synchronized (loaderGrants) {
Grants g = (Grants) loaderGrants.get(ldr);
if (g == null) {
loaderGrants.put(ldr, g = new Grants());
}
return g;
}
}
private static ClassLoader getClassLoader(final Class cl) {
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() { return cl.getClassLoader(); }
});
}
private static void checkDomain(final Class cl) {
ProtectionDomain pd = (ProtectionDomain) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() { return cl.getProtectionDomain(); }
});
if (pd != sysDomain && pd.getClassLoader() == null) {
throw new UnsupportedOperationException(
"ungrantable protection domain");
}
}
private static void checkNullElements(Object[] array) {
for (int i = 0; i < array.length; i++) {
if (array[i] == null) {
throw new NullPointerException();
}
}
}
/**
* Class which holds permissions and principals of a ProtectionDomain. The
* domainPerms map associates ProtectionDomain instances to instances of
* this class.
*/
private class DomainPermissions {
private final Set principals;
private final PermissionCollection perms;
private final List grants = new ArrayList();
DomainPermissions(ProtectionDomain pd) {
Principal[] pra;
principals = (pd != null && (pra = pd.getPrincipals()).length > 0)
? new HashSet(Arrays.asList(pra)) : Collections.EMPTY_SET;
perms = cacheBasePerms ? basePolicy.getPermissions(pd) : null;
}
Set getPrincipals() {
return principals;
}
synchronized void add(Permission[] pa) {
for (int i = 0; i < pa.length; i++) {
Permission p = pa[i];
grants.add(p);
if (perms != null) {
perms.add(p);
}
}
}
synchronized PermissionCollection getPermissions(ProtectionDomain d) {
return getPermissions(true, d);
}
synchronized boolean implies(Permission p, ProtectionDomain domain) {
if (perms != null) {
return perms.implies(p);
}
if (basePolicy.implies(domain, p)) {
return true;
}
if (grants.isEmpty()) {
return false;
}
return getPermissions(false, domain).implies(p);
}
private PermissionCollection getPermissions(boolean compact,
ProtectionDomain domain)
{
// base policy permission collection may not be enumerable
assert Thread.holdsLock(this);
PermissionCollection pc = basePolicy.getPermissions(domain);
for (Iterator i = grants.iterator(); i.hasNext(); ) {
Permission p = (Permission) i.next();
if (!(compact && pc.implies(p))) {
pc.add(p);
}
}
return pc;
}
}
/**
* Class which tracks dynamic permission grants.
*/
private static class Grants {
private final Map principalGrants = new HashMap();
private final WeakGroup scope;
Grants() {
PrincipalGrants pg = new PrincipalGrants();
principalGrants.put(Collections.EMPTY_SET, pg);
scope = pg.scope;
}
synchronized void add(Principal[] pra, Permission[] pa) {
Set prs = (pra != null && pra.length > 0) ?
new HashSet(Arrays.asList(pra)) : Collections.EMPTY_SET;
PrincipalGrants pg = (PrincipalGrants) principalGrants.get(prs);
if (pg == null) {
pg = new PrincipalGrants();
for (Iterator i = scope.iterator(); i.hasNext();) {
DomainPermissions dp = (DomainPermissions) i.next();
if (containsAll(dp.getPrincipals(), prs)) {
pg.scope.add(dp);
}
}
principalGrants.put(prs, pg);
}
ArrayList l = new ArrayList();
for (int i = 0; i < pa.length; i++) {
Permission p = pa[i];
if (!pg.perms.implies(p)) {
pg.perms.add(p);
l.add(p);
}
}
if (l.size() > 0) {
pa = (Permission[]) l.toArray(new Permission[l.size()]);
for (Iterator i = pg.scope.iterator(); i.hasNext();) {
((DomainPermissions) i.next()).add(pa);
}
}
}
synchronized Permission[] get(Principal[] pra) {
Set prs = (pra != null && pra.length > 0) ?
new HashSet(Arrays.asList(pra)) : Collections.EMPTY_SET;
List l = new ArrayList();
for (Iterator i = principalGrants.entrySet().iterator();
i.hasNext(); )
{
Map.Entry me = (Map.Entry) i.next();
if (containsAll(prs, (Set) me.getKey())) {
PrincipalGrants pg = (PrincipalGrants) me.getValue();
l.addAll(Collections.list(pg.perms.elements()));
}
}
return (Permission[]) l.toArray(new Permission[l.size()]);
}
synchronized void register(DomainPermissions dp) {
Set prs = dp.getPrincipals();
for (Iterator i = principalGrants.entrySet().iterator();
i.hasNext(); )
{
Map.Entry me = (Map.Entry) i.next();
if (containsAll(prs, (Set) me.getKey())) {
PrincipalGrants pg = (PrincipalGrants) me.getValue();
pg.scope.add(dp);
List l = Collections.list(pg.perms.elements());
dp.add((Permission[]) l.toArray(new Permission[l.size()]));
}
}
}
private static boolean containsAll(Set s1, Set s2) {
return (s1.size() >= s2.size()) && s1.containsAll(s2);
}
private static class PrincipalGrants {
final WeakGroup scope = new WeakGroup();
final PermissionCollection perms = new Permissions();
PrincipalGrants() {}
}
}
/**
* Grouping of non-null, weakly-referenced objects. The structure is a
* doubly linked list. The resulting structure is not thread safe and
* must be synchronized externally.
*/
private static class WeakGroup {
private final ReferenceQueue rq = new ReferenceQueue();
private final Node head;
private final Node tail;
WeakGroup() {
head = Node.createEmptyList();
tail = head.getNext();
}
void add(Object obj) {
if (obj == null) {
throw new NullPointerException();
}
processQueue();
Node newNode = new Node(obj, rq);
newNode.insertAfter(head);
}
Iterator iterator() {
processQueue();
return new Iterator() {
private Node curNode = head.getNext();
private Object nextObj = getNext();
public Object next() {
if (nextObj == null) {
throw new NoSuchElementException();
}
Object obj = nextObj;
nextObj = getNext();
return obj;
}
public boolean hasNext() {
return nextObj != null;
}
public void remove() {
throw new UnsupportedOperationException();
}
private Object getNext() {
while (curNode != tail) {
Object obj = curNode.get();
if (obj != null) {
curNode = curNode.getNext();
return obj;
} else {
curNode.enqueue();
curNode = curNode.getNext();
}
}
return null;
}
};
}
private void processQueue() {
Node n;
while ((n = (Node) rq.poll()) != null) {
n.remove();
}
}
private static class Node extends WeakReference {
private Node next;
private Node prev;
static Node createEmptyList() {
Node head = new Node(null);
Node tail = new Node(null);
head.next = tail;
tail.prev = head;
return head;
}
// Constructor for initialization of head and tail nodes which
// should never be enqueued.
private Node(Object obj) {
super(obj);
}
Node(Object obj, ReferenceQueue rq) {
super(obj, rq);
}
/**
* Inserts this node between <code>pred</code> and its successor
*/
void insertAfter(Node pred) {
Node succ = pred.next;
next = succ;
prev = pred;
pred.next = this;
succ.prev = this;
}
void remove() {
prev.next = next;
next.prev = prev;
}
Node getNext() {
return next;
}
}
}
}