blob: 43bc44ac7b63be1241727be565fe5d1eaaac1314 [file] [log] [blame]
/*
* 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.
*/
/**
* @author Alexey V. Varlamov
* @version $Revision$
*/
package org.apache.harmony.security.fortress;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.apache.harmony.security.Util;
/**
* This class consist of a number of static methods, which provide a common functionality
* for various policy and configuration providers.
*
*/
public class PolicyUtils {
// No reason to instantiate
private PolicyUtils() {}
/**
* Auxiliary action for opening InputStream from specified location.
*/
public static class URLLoader implements PrivilegedExceptionAction<InputStream> {
/**
* URL of target location.
*/
public URL location;
/**
* Constructor with target URL parameter.
*/
public URLLoader(URL location) {
this.location = location;
}
/**
* Returns InputStream from the target URL.
*/
public InputStream run() throws Exception {
return location.openStream();
}
}
/**
* Auxiliary action for accessing system properties in a bundle.
*/
public static class SystemKit implements PrivilegedAction<Properties> {
/**
* Returns system properties.
*/
public Properties run() {
return System.getProperties();
}
}
/**
* Auxiliary action for accessing specific system property.
*/
public static class SystemPropertyAccessor implements PrivilegedAction<String> {
/**
* A key of a required system property.
*/
public String key;
/**
* Constructor with a property key parameter.
*/
public SystemPropertyAccessor(String key) {
this.key = key;
}
/**
* Handy one-line replacement of
* &quot;provide key and supply action&quot; code block,
* for reusing existing action instance.
*/
public PrivilegedAction<String> key(String key) {
this.key = key;
return this;
}
/**
* Returns specified system property.
*/
public String run() {
return System.getProperty(key);
}
}
/**
* Auxiliary action for accessing specific security property.
*/
public static class SecurityPropertyAccessor implements PrivilegedAction<String> {
private String key;
/**
* Constructor with a property key parameter.
*/
public SecurityPropertyAccessor(String key) {
super();
this.key = key;
}
public PrivilegedAction<String> key(String key) {
this.key = key;
return this;
}
/**
* Returns specified security property.
*/
public String run() {
return Security.getProperty(key);
}
}
/**
* Auxiliary action for loading a provider by specific security property.
*/
public static class ProviderLoader<T> implements PrivilegedAction<T> {
private String key;
/**
* Acceptable provider superclass.
*/
private Class<T> expectedType;
/**
* Constructor taking property key and acceptable provider
* superclass parameters.
*/
public ProviderLoader(String key, Class<T> expected) {
super();
this.key = key;
this.expectedType = expected;
}
/**
* Returns provider instance by specified security property.
* The <code>key</code> should map to a fully qualified classname.
*
* @throws SecurityException if no value specified for the key
* in security properties or if an Exception has occurred
* during classloading and instantiating.
*/
public T run() {
String klassName = Security.getProperty(key);
if (klassName == null || klassName.length() == 0) {
throw new SecurityException("Provider implementation should be specified via '" +
key + "' security property");
}
// TODO accurate classloading
try {
Class<?> klass = Class.forName(klassName, true,
Thread.currentThread().getContextClassLoader());
if (expectedType != null && klass.isAssignableFrom(expectedType)){
throw new SecurityException("Provided class " + klassName +
" does not implement " + expectedType.getName());
}
//FIXME expectedType.cast(klass.newInstance());
return (T)klass.newInstance();
}
catch (SecurityException se){
throw se;
}
catch (Exception e) {
// TODO log error ??
SecurityException se = new SecurityException("Unable to instantiate provider: " + klassName);
se.initCause(e);
throw se;
}
}
}
/**
* Specific exception to signal that property expansion failed
* due to unknown key.
*/
public static class ExpansionFailedException extends Exception {
/**
* @serial
*/
private static final long serialVersionUID = 2869748055182612000L;
/**
* Constructor with user-friendly message parameter.
*/
public ExpansionFailedException(String message) {
super(message);
}
/**
* Constructor with user-friendly message and causing error.
*/
public ExpansionFailedException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Substitutes all entries like ${some.key}, found in specified string,
* for specified values.
* If some key is unknown, throws ExpansionFailedException.
* @param str the string to be expanded
* @param properties available key-value mappings
* @return expanded string
* @throws ExpansionFailedException
*/
public static String expand(String str, Properties properties)
throws ExpansionFailedException {
final String START_MARK = "${";
final String END_MARK = "}";
final int START_OFFSET = START_MARK.length();
final int END_OFFSET = END_MARK.length();
StringBuilder result = new StringBuilder(str);
int start = result.indexOf(START_MARK);
while (start >= 0) {
int end = result.indexOf(END_MARK, start);
if (end >= 0) {
String key = result.substring(start + START_OFFSET, end);
String value = properties.getProperty(key);
if (value != null) {
result.replace(start, end + END_OFFSET, value);
start += value.length();
} else {
throw new ExpansionFailedException("Unknown key: " + key);
}
}
start = result.indexOf(START_MARK, start);
}
return result.toString();
}
/**
* Handy shortcut for
* <code>expand(str, properties).replace(File.separatorChar, '/')</code>.
* @see #expand(String, Properties)
*/
public static String expandURL(String str, Properties properties)
throws ExpansionFailedException {
return expand(str, properties).replace(File.separatorChar, '/');
}
/**
* Normalizes URLs to standard ones, eliminating pathname symbols.
*
* @param codebase -
* the original URL.
* @return - the normalized URL.
*/
public static URL normalizeURL(URL codebase) {
if (codebase != null && "file".equals(codebase.getProtocol())) {
try {
if (codebase.getHost().length() == 0) {
String path = codebase.getFile();
if (path.length() == 0) {
// codebase is "file:"
path = "*";
}
return filePathToURI(new File(path)
.getAbsolutePath()).normalize().toURL();
} else {
// codebase is "file://<smth>"
return codebase.toURI().normalize().toURL();
}
} catch (Exception e) {
// Ignore
}
}
return codebase;
}
/**
* Converts a file path to URI without accessing file system
* (like {File#toURI()} does).
*
* @param path -
* file path.
* @return - the resulting URI.
* @throw URISyntaxException
*/
public static URI filePathToURI(String path) throws URISyntaxException {
path = path.replace(File.separatorChar, '/');
if (!path.startsWith("/")) {
return new URI("file", null,
new StringBuilder(path.length() + 1).append('/')
.append(path).toString(), null, null);
}
return new URI("file", null, path, null, null);
}
/**
* Instances of this interface are intended for resolving
* generalized expansion expressions, of the form ${{protocol:data}}.
* Such functionality is applicable to security policy files, for example.
* @see #expandGeneral(String, GeneralExpansionHandler)
*/
public static interface GeneralExpansionHandler {
/**
* Resolves general expansion expressions of the form ${{protocol:data}}.
* @param protocol denotes type of resolution
* @param data data to be resolved, optional (may be null)
* @return resolved value, must not be null
* @throws PolicyUtils.ExpansionFailedException if expansion is impossible
*/
String resolve(String protocol, String data)
throws ExpansionFailedException;
}
/**
* Substitutes all entries like ${{protocol:data}}, found in specified string,
* for values resolved by passed handler.
* The data part may be empty, and in this case expression
* may have simplified form, as ${{protocol}}.
* If some entry cannot be resolved, throws ExpansionFailedException;
* @param str the string to be expanded
* @param handler the handler to resolve data denoted by protocol
* @return expanded string
* @throws ExpansionFailedException
*/
public static String expandGeneral(String str,
GeneralExpansionHandler handler) throws ExpansionFailedException {
final String START_MARK = "${{";
final String END_MARK = "}}";
final int START_OFFSET = START_MARK.length();
final int END_OFFSET = END_MARK.length();
StringBuilder result = new StringBuilder(str);
int start = result.indexOf(START_MARK);
while (start >= 0) {
int end = result.indexOf(END_MARK, start);
if (end >= 0) {
String key = result.substring(start + START_OFFSET, end);
int separator = key.indexOf(':');
String protocol = (separator >= 0) ? key
.substring(0, separator) : key;
String data = (separator >= 0) ? key.substring(separator + 1)
: null;
String value = handler.resolve(protocol, data);
result.replace(start, end + END_OFFSET, value);
start += value.length();
}
start = result.indexOf(START_MARK, start);
}
return result.toString();
}
/**
* A key to security properties, deciding whether usage of
* dynamic policy location via system properties is allowed.
* @see #getPolicyURLs(Properties, String, String)
*/
public static final String POLICY_ALLOW_DYNAMIC = "policy.allowSystemProperty";
/**
* A key to security properties, deciding whether expansion of
* system properties is allowed
* (in security properties values, policy files, etc).
* @see #expand(String, Properties)
*/
public static final String POLICY_EXPAND = "policy.expandProperties";
/**
* Positive value of switching properties.
*/
public static final String TRUE = "true";
/**
* Negative value of switching properties.
*/
public static final String FALSE = "false";
/**
* Returns false if current security settings disable to perform
* properties expansion, true otherwise.
* @see #expand(String, Properties)
*/
public static boolean canExpandProperties() {
return !Util.equalsIgnoreCase(FALSE,AccessController
.doPrivileged(new SecurityPropertyAccessor(POLICY_EXPAND)));
}
/**
* Obtains a list of locations for a policy or configuration provider.
* The search algorithm is as follows:
* <ol>
* <li> Look in security properties for keys of form <code>prefix + n</code>,
* where <i>n</i> is an integer and <i>prefix</i> is a passed parameter.
* Sequence starts with <code>n=1</code>, and keeps incrementing <i>n</i>
* until next key is not found. <br>
* For each obtained key, try to construct an URL instance. On success,
* add the URL to the list; otherwise ignore it.
* <li>
* If security settings do not prohibit (through
* {@link #POLICY_ALLOW_DYNAMIC the &quot;policy.allowSystemProperty&quot; property})
* to use additional policy location, read the system property under the
* passed key parameter. If property exists, it may designate a file or
* an absolute URL. Thus, first check if there is a file with that name,
* and if so, convert the pathname to URL. Otherwise, try to instantiate
* an URL directly. If succeeded, append the URL to the list
* <li>
* If the additional location from the step above was specified to the
* system via &quot;==&quot; (i.e. starts with '='), discard all URLs above
* and use this only URL.
* </ol>
* <b>Note:</b> all property values (both security and system) related to URLs are
* subject to {@link #expand(String, Properties) property expansion}, regardless
* of the &quot;policy.expandProperties&quot; security setting.
*
* @param system system properties
* @param systemUrlKey key to additional policy location
* @param securityUrlPrefix prefix to numbered locations in security properties
* @return array of URLs to provider's configuration files, may be empty.
*/
public static URL[] getPolicyURLs(final Properties system,
final String systemUrlKey, final String securityUrlPrefix) {
final SecurityPropertyAccessor security = new SecurityPropertyAccessor(
null);
final List<URL> urls = new ArrayList<URL>();
boolean dynamicOnly = false;
URL dynamicURL = null;
//first check if policy is set via system properties
if (!Util.equalsIgnoreCase(FALSE, AccessController
.doPrivileged(security.key(POLICY_ALLOW_DYNAMIC)))) {
String location = system.getProperty(systemUrlKey);
if (location != null) {
if (location.startsWith("=")) {
//overrides all other urls
dynamicOnly = true;
location = location.substring(1);
}
try {
location = expandURL(location, system);
// location can be a file, but we need an url...
final File f = new File(location);
dynamicURL = AccessController
.doPrivileged(new PrivilegedExceptionAction<URL>() {
public URL run() throws Exception {
if (f.exists()) {
return f.toURI().toURL();
} else {
return null;
}
}
});
if (dynamicURL == null) {
dynamicURL = new URL(location);
}
}
catch (Exception e) {
// TODO: log error
// System.err.println("Error detecting system policy location: "+e);
}
}
}
//next read urls from security.properties
if (!dynamicOnly) {
int i = 1;
while (true) {
String location = AccessController
.doPrivileged(security.key(new StringBuilder(
securityUrlPrefix).append(i++).toString()));
if (location == null) {
break;
}
try {
location = expandURL(location, system);
URL anURL = new URL(location);
if (anURL != null) {
urls.add(anURL);
}
}
catch (Exception e) {
// TODO: log error
// System.err.println("Error detecting security policy location: "+e);
}
}
}
if (dynamicURL != null) {
urls.add(dynamicURL);
}
return urls.toArray(new URL[urls.size()]);
}
/**
* Converts common-purpose collection of Permissions to PermissionCollection.
*
* @param perms a collection containing arbitrary permissions, may be null
* @return mutable heterogeneous PermissionCollection containing all Permissions
* from the specified collection
*/
public static PermissionCollection toPermissionCollection(
Collection<Permission> perms) {
Permissions pc = new Permissions();
if (perms != null) {
for (Iterator<Permission> iter = perms.iterator(); iter.hasNext();) {
Permission element = iter.next();
pc.add(element);
}
}
return pc;
}
// Empty set of arguments to default constructor of a Permission.
private static final Class[] NO_ARGS = {};
// One-arg set of arguments to default constructor of a Permission.
private static final Class[] ONE_ARGS = { String.class };
// Two-args set of arguments to default constructor of a Permission.
private static final Class[] TWO_ARGS = { String.class, String.class };
/**
* Tries to find a suitable constructor and instantiate a new Permission
* with specified parameters.
*
* @param targetType class of expected Permission instance
* @param targetName name of expected Permission instance
* @param targetActions actions of expected Permission instance
* @return a new Permission instance
* @throws IllegalArgumentException if no suitable constructor found
* @throws Exception any exception thrown by Constructor.newInstance()
*/
public static Permission instantiatePermission(Class<?> targetType,
String targetName, String targetActions) throws Exception {
// let's guess the best order for trying constructors
Class[][] argTypes = null;
Object[][] args = null;
if (targetActions != null) {
argTypes = new Class[][] { TWO_ARGS, ONE_ARGS, NO_ARGS };
args = new Object[][] { { targetName, targetActions },
{ targetName }, {} };
} else if (targetName != null) {
argTypes = new Class[][] { ONE_ARGS, TWO_ARGS, NO_ARGS };
args = new Object[][] { { targetName },
{ targetName, targetActions }, {} };
} else {
argTypes = new Class[][] { NO_ARGS, ONE_ARGS, TWO_ARGS };
args = new Object[][] { {}, { targetName },
{ targetName, targetActions } };
}
// finally try to instantiate actual permission
for (int i = 0; i < argTypes.length; i++) {
try {
Constructor<?> ctor = targetType.getConstructor(argTypes[i]);
return (Permission)ctor.newInstance(args[i]);
}
catch (NoSuchMethodException ignore) {}
}
throw new IllegalArgumentException("No suitable constructors found in permission class " +
targetType + ". Zero, one or two-argument constructor is expected");
}
/**
* Checks whether the objects from <code>what</code> array are all
* presented in <code>where</code> array.
*
* @param what first array, may be <code>null</code>
* @param where second array, may be <code>null</code>
* @return <code>true</code> if the first array is <code>null</code>
* or if each and every object (ignoring null values)
* from the first array has a twin in the second array; <code>false</code> otherwise
*/
public static boolean matchSubset(Object[] what, Object[] where) {
if (what == null) {
return true;
}
for (int i = 0; i < what.length; i++) {
if (what[i] != null) {
if (where == null) {
return false;
}
boolean found = false;
for (int j = 0; j < where.length; j++) {
if (what[i].equals(where[j])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
}
return true;
}
}