/* * 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 */ 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.List; import java.util.Properties; import org.apache.harmony.security.Util; import org.apache.harmony.security.fortress.PolicyUtils.GeneralExpansionHandler; import org.apache.harmony.security.internal.nls.Messages; /** * This class consist of a number of static methods, which provide a common * functionality for various policy and configuration providers. * */ public class PolicyUtils { /** * 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); } } /** * 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 org.apache.harmony.security.PolicyUtils#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; } /** * Auxiliary action for loading a provider by specific security property. */ public static class ProviderLoader<T> implements PrivilegedAction<T> { private final String key; /** * Acceptable provider superclass. */ private final 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. */ @Override public T run() { final String klassName = Security.getProperty(key); if (klassName == null || klassName.length() == 0) { throw new SecurityException(Messages.getString("security.14C", //$NON-NLS-1$ key)); } // TODO accurate classloading try { final Class<?> klass = Class.forName(klassName, true, Thread .currentThread().getContextClassLoader()); if (expectedType != null && klass.isAssignableFrom(expectedType)) { throw new SecurityException(Messages.getString( "security.14D", //$NON-NLS-1$ klassName, expectedType.getName())); } // FIXME expectedType.cast(klass.newInstance()); return (T) klass.newInstance(); } catch (final SecurityException se) { throw se; } catch (final Exception e) { // TODO log error ?? final SecurityException se = new SecurityException( Messages.getString("security.14E", klassName)); //$NON-NLS-1$ se.initCause(e); throw se; } } } /** * 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. */ @Override public String run() { return Security.getProperty(key); } } /** * Auxiliary action for accessing system properties in a bundle. */ public static class SystemKit implements PrivilegedAction<Properties> { /** * Returns system properties. */ @Override 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 "provide key and supply * action" code block, for reusing existing action instance. */ public PrivilegedAction<String> key(String key) { this.key = key; return this; } /** * Returns specified system property. */ @Override public String run() { return System.getProperty(key); } } /** * 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. */ @Override public InputStream run() throws Exception { return location.openStream(); } } /** * 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"; //$NON-NLS-1$ /** * 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"; //$NON-NLS-1$ /** * Positive value of switching properties. */ public static final String TRUE = "true"; //$NON-NLS-1$ /** * Negative value of switching properties. */ public static final String FALSE = "false"; //$NON-NLS-1$ // 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 }; /** * 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))); } /** * 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 = "${"; //$NON-NLS-1$ final String END_MARK = "}"; //$NON-NLS-1$ final int START_OFFSET = START_MARK.length(); final int END_OFFSET = END_MARK.length(); final StringBuilder result = new StringBuilder(str); int start = result.indexOf(START_MARK); while (start >= 0) { final int end = result.indexOf(END_MARK, start); if (end >= 0) { final String key = result.substring(start + START_OFFSET, end); final String value = properties.getProperty(key); if (value != null) { result.replace(start, end + END_OFFSET, value); start += value.length(); } else { throw new ExpansionFailedException(Messages.getString( "security.14F", key)); //$NON-NLS-1$ } } start = result.indexOf(START_MARK, start); } return result.toString(); } /** * 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 = "${{"; //$NON-NLS-1$ final String END_MARK = "}}"; //$NON-NLS-1$ final int START_OFFSET = START_MARK.length(); final int END_OFFSET = END_MARK.length(); final StringBuilder result = new StringBuilder(str); int start = result.indexOf(START_MARK); while (start >= 0) { final int end = result.indexOf(END_MARK, start); if (end >= 0) { final String key = result.substring(start + START_OFFSET, end); final int separator = key.indexOf(':'); final String protocol = (separator >= 0) ? key.substring(0, separator) : key; final String data = (separator >= 0) ? key .substring(separator + 1) : null; final 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(); } /** * 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, '/'); } /** * Converts a file path to URI without accessing file system (like * {File#toURI()} does). * * @param path * - file path. * @return - the resulting URI. * @throws URISyntaxException */ public static URI filePathToURI(String path) throws URISyntaxException { path = path.replace(File.separatorChar, '/'); if (!path.startsWith("/")) { //$NON-NLS-1$ return new URI("file", null, //$NON-NLS-1$ new StringBuilder(path.length() + 1).append('/') .append(path).toString(), null, null); } return new URI("file", null, path, null, null); //$NON-NLS-1$ } /** * 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 "policy.allowSystemProperty" * 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 "==" (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 "policy.expandProperties" * 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("=")) { //$NON-NLS-1$ // 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>() { @Override public URL run() throws Exception { if (f.exists()) { return f.toURI().toURL(); } else { return null; } } }); if (dynamicURL == null) { dynamicURL = new URL(location); } } catch (final 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); final URL anURL = new URL(location); if (anURL != null) { urls.add(anURL); } } catch (final 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()]); } /** * 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 { final Constructor<?> ctor = targetType .getConstructor(argTypes[i]); return (Permission) ctor.newInstance(args[i]); } catch (final NoSuchMethodException ignore) { } } throw new IllegalArgumentException(Messages.getString( "security.150", targetType));//$NON-NLS-1$ } /** * 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 (final Object element : what) { if (element != null) { if (where == null) { return false; } boolean found = false; for (final Object element2 : where) { if (element.equals(element2)) { found = true; break; } } if (!found) { return false; } } } return true; } /** * 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())) { //$NON-NLS-1$ 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 (final Exception e) { // Ignore } } return codebase; } /** * 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) { final Permissions pc = new Permissions(); if (perms != null) { for (final Permission element : perms) { pc.add(element); } } return pc; } // No reason to instantiate private PolicyUtils() { } }