/*
* 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 java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.Security;
import java.util.Collections;
import java.util.List;
import net.jini.security.GrantPermission;
/**
* Security policy provider that wraps the J2SE(TM) default
* <a href="http://java.sun.com/j2se/1.4/docs/guide/security/PolicyFiles.html">
* "PolicyFile" security policy provider</a> distributed as part of the
* Java(TM) 2 Platform, Standard Edition. This provider augments the J2SE
* default policy provider in two ways: it provides an additional constructor
* for creating a policy based on an explicitly named policy file, and supports
* the use of {@link UmbrellaGrantPermission}s as shorthand notation for
* {@link GrantPermission}s covering all permissions authorized to given
* protection domains.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* This implementation's no-argument constructor uses a default class name of
* <code>"sun.security.provider.PolicyFile"</code> to instantiate base policy
* objects, if the
* <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
* security property is not set.
*/
public class PolicyFileProvider extends Policy {
private static final String basePolicyClassProperty =
"net.jini.security.policy." +
"PolicyFileProvider.basePolicyClass";
private static final String defaultBasePolicyClass =
"sun.security.provider.PolicyFile";
private static final String policyProperty = "java.security.policy";
private static final Object propertyLock = new Object();
private final String policyFile;
private final Policy basePolicy;
/**
* Creates a <code>PolicyFileProvider</code> whose starting set of
* permission mappings is the same as those that would result from
* constructing a new instance of the J2SE default security policy provider
* with the current <code>java.security.policy</code> system property
* setting (if any), except that <code>UmbrellaGrantPermission</code>s are
* expanded into <code>GrantPermission</code>s as described in the
* documentation for {@link UmbrellaGrantPermission}.
* <p>
* The constructed <code>PolicyFileProvider</code> contains an instance of
* the J2SE default security policy provider, which is created as follows:
* if the
* <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
* security property is set, then its value is interpreted as the class
* name of the base (underlying) J2SE default policy provider; otherwise,
* an implementation-specific default class name 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 or 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.PolicyFileProvider.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.PolicyFileProvider.basePolicyClass</code>
* security property, or if the calling context does not have
* adequate permissions to access the base policy class
*/
public PolicyFileProvider() throws PolicyInitializationException {
policyFile = null;
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);
}
ensureDependenciesResolved();
}
/**
* Creates a <code>PolicyFileProvider</code> whose starting set of
* permission mappings is the same as those that would result from
* constructing a new instance of the J2SE default security policy provider
* with the <code>java.security.policy</code> system property set to the
* value of <code>policyFile</code>, except that
* <code>UmbrellaGrantPermission</code>s are expanded into
* <code>GrantPermission</code>s as described in the documentation for
* {@link UmbrellaGrantPermission}.
* <p>
* The constructed <code>PolicyFileProvider</code> contains an instance of
* the J2SE default security policy provider, which is created as described
* in the documentation for {@link #PolicyFileProvider()}. Before
* instantiating the base (underlying) J2SE default policy provider, this
* constructor sets the <code>java.security.policy</code> system property
* to the value of <code>policyFile</code>; after instantiation of the base
* policy instance has completed (normally or otherwise), the
* <code>java.security.policy</code> system property is reset to its prior
* value. Internal synchronization ensures that concurrent calls to this
* constructor and/or the {@link #refresh} method of this class (which may
* also modify <code>java.security.policy</code>) will not interfere with
* the <code>java.security.policy</code> values set and restored by each.
* No synchronization is done with any other accesses or modifications to
* <code>java.security.policy</code>.
* <p>
* Note that this constructor requires {@link java.util.PropertyPermission}
* to read and write the <code>java.security.policy</code> system property,
* {@link java.security.SecurityPermission} to read the
* <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
* security property, and may require <code>"accessClassInPackage.*"</code>
* {@link RuntimePermission}s, depending on the package of the base policy
* class.
*
* @param policyFile URL string specifying location of the policy file to
* use
* @throws NullPointerException if <code>policyFile</code> is
* <code>null</code>
* @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 and
* write the <code>java.security.policy</code> system property, to
* read the
* <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
* security property, or to access the base policy class
*/
public PolicyFileProvider(String policyFile)
throws PolicyInitializationException
{
if (policyFile == null) {
throw new NullPointerException();
}
this.policyFile = policyFile;
String cname = Security.getProperty(basePolicyClassProperty);
if (cname == null) {
cname = defaultBasePolicyClass;
}
try {
Class cl = Class.forName(cname);
synchronized (propertyLock) {
String oldp = System.getProperty(policyProperty);
System.setProperty(policyProperty, policyFile);
try {
basePolicy = (Policy) cl.newInstance();
} finally {
resetPolicyProperty(oldp);
}
}
} catch (SecurityException e) {
throw e;
} catch (Exception e) {
throw new PolicyInitializationException(
"unable to construct base policy", e);
}
ensureDependenciesResolved();
}
/**
* Behaves as specified by {@link Policy#getPermissions(CodeSource)}.
*/
public PermissionCollection getPermissions(CodeSource source) {
PermissionCollection pc = basePolicy.getPermissions(source);
expandUmbrella(pc);
return pc;
}
/**
* Behaves as specified by {@link Policy#getPermissions(ProtectionDomain)}.
*/
public PermissionCollection getPermissions(ProtectionDomain domain) {
PermissionCollection pc = basePolicy.getPermissions(domain);
expandUmbrella(pc);
return pc;
}
/**
* Behaves as specified by {@link Policy#implies}.
*/
public boolean implies(ProtectionDomain domain, Permission permission) {
// REMIND: cache expanded permission collections?
return (basePolicy.implies(domain, permission) ||
(permission instanceof GrantPermission &&
getPermissions(domain).implies(permission)));
}
/**
* Refreshes the policy configuration by calling <code>refresh</code> on
* the base policy. If this <code>PolicyFileProvider</code> instance was
* constructed with an explicit policy file value, then the
* <code>java.security.policy</code> system property is set to that value
* prior to invoking <code>refresh</code> on the base policy; once the base
* policy <code>refresh</code> call has completed, the
* <code>java.security.policy</code> system property is reset to its prior
* value. Internal synchronization ensures that concurrent invocations of
* this method and/or the {@link #PolicyFileProvider(String)} constructor
* (which also modifies <code>java.security.policy</code>) will not
* interfere with the <code>java.security.policy</code> values set and
* restored by each. No synchronization is done with any other accesses
* or modifications to <code>java.security.policy</code>.
*/
public void refresh() {
if (policyFile != null) {
synchronized (propertyLock) {
String oldp = System.getProperty(policyProperty);
System.setProperty(policyProperty, policyFile);
try {
basePolicy.refresh();
} finally {
resetPolicyProperty(oldp);
}
}
} else {
basePolicy.refresh();
}
}
/**
* 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 resolution of GrantPermission and UmbrellaGrantPermission
new GrantPermission(new UmbrellaGrantPermission());
}
private static void expandUmbrella(PermissionCollection pc) {
if (pc.implies(new UmbrellaGrantPermission())) {
List l = Collections.list(pc.elements());
pc.add(new GrantPermission(
(Permission[]) l.toArray(new Permission[l.size()])));
}
}
/** Resets policyProperty system property, removing it if the value to set
* is null. We do this in a privileged block to make sure that the operation
* does not fail, even if the calling context may not have the requisite
* permissions anymore (could happen in the refresh case).
*/
private static void resetPolicyProperty(final String value) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (value == null) {
// TODO: Use System.clearProperty when we move to 1.5
System.getProperties().remove(policyProperty);
} else {
System.setProperty(policyProperty, value);
}
return null;
}
});
}
}