Home | History | Annotate | Download | only in fortress
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Alexey V. Varlamov
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.fortress;
     24 
     25 import java.io.File;
     26 import java.io.InputStream;
     27 import java.lang.reflect.Constructor;
     28 import java.net.URI;
     29 import java.net.URISyntaxException;
     30 import java.net.URL;
     31 import java.security.AccessController;
     32 import java.security.Permission;
     33 import java.security.PermissionCollection;
     34 import java.security.Permissions;
     35 import java.security.PrivilegedAction;
     36 import java.security.PrivilegedExceptionAction;
     37 import java.security.Security;
     38 import java.util.ArrayList;
     39 import java.util.Collection;
     40 import java.util.Iterator;
     41 import java.util.List;
     42 import java.util.Properties;
     43 import org.apache.harmony.security.Util;
     44 
     45 /**
     46  * This class consist of a number of static methods, which provide a common functionality
     47  * for various policy and configuration providers.
     48  *
     49  */
     50 public class PolicyUtils {
     51 
     52     // No reason to instantiate
     53     private PolicyUtils() {}
     54 
     55     /**
     56      * Auxiliary action for opening InputStream from specified location.
     57      */
     58     public static class URLLoader implements PrivilegedExceptionAction<InputStream> {
     59 
     60         /**
     61          * URL of target location.
     62          */
     63         public URL location;
     64 
     65         /**
     66          *  Constructor with target URL parameter.
     67          */
     68         public URLLoader(URL location) {
     69             this.location = location;
     70         }
     71 
     72         /**
     73          * Returns InputStream from the target URL.
     74          */
     75         public InputStream run() throws Exception {
     76             return location.openStream();
     77         }
     78     }
     79 
     80     /**
     81      * Auxiliary action for accessing system properties in a bundle.
     82      */
     83     public static class SystemKit implements PrivilegedAction<Properties> {
     84 
     85         /**
     86          * Returns system properties.
     87          */
     88         public Properties run() {
     89             return System.getProperties();
     90         }
     91     }
     92 
     93     /**
     94      * Auxiliary action for accessing specific system property.
     95      */
     96     public static class SystemPropertyAccessor implements PrivilegedAction<String> {
     97 
     98         /**
     99          * A key of a required system property.
    100          */
    101         public String key;
    102 
    103         /**
    104          * Constructor with a property key parameter.
    105          */
    106         public SystemPropertyAccessor(String key) {
    107             this.key = key;
    108         }
    109 
    110         /**
    111          * Handy one-line replacement of
    112          * &quot;provide key and supply action&quot; code block,
    113          * for reusing existing action instance.
    114          */
    115         public PrivilegedAction<String> key(String key) {
    116             this.key = key;
    117             return this;
    118         }
    119 
    120         /**
    121          * Returns specified system property.
    122          */
    123         public String run() {
    124             return System.getProperty(key);
    125         }
    126     }
    127 
    128     /**
    129      * Auxiliary action for accessing specific security property.
    130      */
    131     public static class SecurityPropertyAccessor implements PrivilegedAction<String> {
    132 
    133         private String key;
    134 
    135         /**
    136          * Constructor with a property key parameter.
    137          */
    138         public SecurityPropertyAccessor(String key) {
    139             super();
    140             this.key = key;
    141         }
    142 
    143         public PrivilegedAction<String> key(String key) {
    144             this.key = key;
    145             return this;
    146         }
    147 
    148         /**
    149          * Returns specified security property.
    150          */
    151         public String run() {
    152             return Security.getProperty(key);
    153         }
    154     }
    155 
    156     /**
    157      * Auxiliary action for loading a provider by specific security property.
    158      */
    159     public static class ProviderLoader<T> implements PrivilegedAction<T> {
    160 
    161         private String key;
    162 
    163         /**
    164          * Acceptable provider superclass.
    165          */
    166         private Class<T> expectedType;
    167 
    168         /**
    169          * Constructor taking property key and acceptable provider
    170          * superclass parameters.
    171          */
    172         public ProviderLoader(String key, Class<T> expected) {
    173             super();
    174             this.key = key;
    175             this.expectedType = expected;
    176         }
    177 
    178         /**
    179          * Returns provider instance by specified security property.
    180          * The <code>key</code> should map to a fully qualified classname.
    181          *
    182          * @throws SecurityException if no value specified for the key
    183          * in security properties or if an Exception has occurred
    184          * during classloading and instantiating.
    185          */
    186         public T run() {
    187             String klassName = Security.getProperty(key);
    188             if (klassName == null || klassName.length() == 0) {
    189                 throw new SecurityException("Provider implementation should be specified via '" +
    190                         key + "' security property");
    191             }
    192             // TODO accurate classloading
    193             try {
    194                 Class<?> klass = Class.forName(klassName, true,
    195                         Thread.currentThread().getContextClassLoader());
    196                 if (expectedType != null && klass.isAssignableFrom(expectedType)){
    197                     throw new SecurityException("Provided class " + klassName +
    198                             " does not implement " + expectedType.getName());
    199                 }
    200                 //FIXME expectedType.cast(klass.newInstance());
    201                 return (T)klass.newInstance();
    202             }
    203             catch (SecurityException se){
    204                 throw se;
    205             }
    206             catch (Exception e) {
    207                 // TODO log error ??
    208                 SecurityException se = new SecurityException("Unable to instantiate provider: " + klassName);
    209                 se.initCause(e);
    210                 throw se;
    211             }
    212         }
    213     }
    214 
    215     /**
    216      * Specific exception to signal that property expansion failed
    217      * due to unknown key.
    218      */
    219     public static class ExpansionFailedException extends Exception {
    220 
    221         /**
    222          * @serial
    223          */
    224         private static final long serialVersionUID = 2869748055182612000L;
    225 
    226         /**
    227          * Constructor with user-friendly message parameter.
    228          */
    229         public ExpansionFailedException(String message) {
    230             super(message);
    231         }
    232 
    233         /**
    234          * Constructor with user-friendly message and causing error.
    235          */
    236         public ExpansionFailedException(String message, Throwable cause) {
    237             super(message, cause);
    238         }
    239     }
    240 
    241     /**
    242      * Substitutes all entries like ${some.key}, found in specified string,
    243      * for specified values.
    244      * If some key is unknown, throws ExpansionFailedException.
    245      * @param str the string to be expanded
    246      * @param properties available key-value mappings
    247      * @return expanded string
    248      * @throws ExpansionFailedException
    249      */
    250     public static String expand(String str, Properties properties)
    251             throws ExpansionFailedException {
    252         final String START_MARK = "${";
    253         final String END_MARK = "}";
    254         final int START_OFFSET = START_MARK.length();
    255         final int END_OFFSET = END_MARK.length();
    256 
    257         StringBuilder result = new StringBuilder(str);
    258         int start = result.indexOf(START_MARK);
    259         while (start >= 0) {
    260             int end = result.indexOf(END_MARK, start);
    261             if (end >= 0) {
    262                 String key = result.substring(start + START_OFFSET, end);
    263                 String value = properties.getProperty(key);
    264                 if (value != null) {
    265                     result.replace(start, end + END_OFFSET, value);
    266                     start += value.length();
    267                 } else {
    268                     throw new ExpansionFailedException("Unknown key: " + key);
    269                 }
    270             }
    271             start = result.indexOf(START_MARK, start);
    272         }
    273         return result.toString();
    274     }
    275 
    276     /**
    277      * Handy shortcut for
    278      * <code>expand(str, properties).replace(File.separatorChar, '/')</code>.
    279      * @see #expand(String, Properties)
    280      */
    281     public static String expandURL(String str, Properties properties)
    282             throws ExpansionFailedException {
    283         return expand(str, properties).replace(File.separatorChar, '/');
    284     }
    285 
    286     /**
    287      * Normalizes URLs to standard ones, eliminating pathname symbols.
    288      *
    289      * @param codebase -
    290      *            the original URL.
    291      * @return - the normalized URL.
    292      */
    293     public static URL normalizeURL(URL codebase) {
    294         if (codebase != null && "file".equals(codebase.getProtocol())) {
    295             try {
    296                 if (codebase.getHost().length() == 0) {
    297                     String path = codebase.getFile();
    298 
    299                     if (path.length() == 0) {
    300                         // codebase is "file:"
    301                         path = "*";
    302                     }
    303                     return filePathToURI(new File(path)
    304                             .getAbsolutePath()).normalize().toURL();
    305                 } else {
    306                     // codebase is "file://<smth>"
    307                     return codebase.toURI().normalize().toURL();
    308                 }
    309             } catch (Exception e) {
    310                 // Ignore
    311             }
    312         }
    313         return codebase;
    314     }
    315 
    316     /**
    317      * Converts a file path to URI without accessing file system
    318      * (like {File#toURI()} does).
    319      *
    320      * @param path -
    321      *            file path.
    322      * @return - the resulting URI.
    323      * @throw URISyntaxException
    324      */
    325     public static URI filePathToURI(String path) throws URISyntaxException {
    326         path = path.replace(File.separatorChar, '/');
    327 
    328         if (!path.startsWith("/")) {
    329             return new URI("file", null,
    330                     new StringBuilder(path.length() + 1).append('/')
    331                             .append(path).toString(), null, null);
    332         }
    333         return new URI("file", null, path, null, null);
    334     }
    335 
    336     /**
    337      * Instances of this interface are intended for resolving
    338      * generalized expansion expressions, of the form ${{protocol:data}}.
    339      * Such functionality is applicable to security policy files, for example.
    340      * @see #expandGeneral(String, GeneralExpansionHandler)
    341      */
    342     public static interface GeneralExpansionHandler {
    343 
    344         /**
    345          * Resolves general expansion expressions of the form ${{protocol:data}}.
    346          * @param protocol denotes type of resolution
    347          * @param data data to be resolved, optional (may be null)
    348          * @return resolved value, must not be null
    349          * @throws PolicyUtils.ExpansionFailedException if expansion is impossible
    350          */
    351         String resolve(String protocol, String data)
    352                 throws ExpansionFailedException;
    353     }
    354 
    355     /**
    356      * Substitutes all entries like ${{protocol:data}}, found in specified string,
    357      * for values resolved by passed handler.
    358      * The data part may be empty, and in this case expression
    359      * may have simplified form, as ${{protocol}}.
    360      * If some entry cannot be resolved, throws ExpansionFailedException;
    361      * @param str the string to be expanded
    362      * @param handler the handler to resolve data denoted by protocol
    363      * @return expanded string
    364      * @throws ExpansionFailedException
    365      */
    366     public static String expandGeneral(String str,
    367             GeneralExpansionHandler handler) throws ExpansionFailedException {
    368         final String START_MARK = "${{";
    369         final String END_MARK = "}}";
    370         final int START_OFFSET = START_MARK.length();
    371         final int END_OFFSET = END_MARK.length();
    372 
    373         StringBuilder result = new StringBuilder(str);
    374         int start = result.indexOf(START_MARK);
    375         while (start >= 0) {
    376             int end = result.indexOf(END_MARK, start);
    377             if (end >= 0) {
    378                 String key = result.substring(start + START_OFFSET, end);
    379                 int separator = key.indexOf(':');
    380                 String protocol = (separator >= 0) ? key
    381                         .substring(0, separator) : key;
    382                 String data = (separator >= 0) ? key.substring(separator + 1)
    383                         : null;
    384                 String value = handler.resolve(protocol, data);
    385                 result.replace(start, end + END_OFFSET, value);
    386                 start += value.length();
    387             }
    388             start = result.indexOf(START_MARK, start);
    389         }
    390         return result.toString();
    391     }
    392 
    393     /**
    394      * A key to security properties, deciding whether usage of
    395      * dynamic policy location via system properties is allowed.
    396      * @see #getPolicyURLs(Properties, String, String)
    397      */
    398     public static final String POLICY_ALLOW_DYNAMIC = "policy.allowSystemProperty";
    399 
    400     /**
    401      * A key to security properties, deciding whether expansion of
    402      * system properties is allowed
    403      * (in security properties values, policy files, etc).
    404      * @see #expand(String, Properties)
    405      */
    406     public static final String POLICY_EXPAND = "policy.expandProperties";
    407 
    408     /**
    409      * Positive value of switching properties.
    410      */
    411     public static final String TRUE = "true";
    412 
    413     /**
    414      * Negative value of switching properties.
    415      */
    416     public static final String FALSE = "false";
    417 
    418     /**
    419      * Returns false if current security settings disable to perform
    420      * properties expansion, true otherwise.
    421      * @see #expand(String, Properties)
    422      */
    423     public static boolean canExpandProperties() {
    424         return !Util.equalsIgnoreCase(FALSE,AccessController
    425                 .doPrivileged(new SecurityPropertyAccessor(POLICY_EXPAND)));
    426     }
    427 
    428     /**
    429      * Obtains a list of locations for a policy or configuration provider.
    430      * The search algorithm is as follows:
    431      * <ol>
    432      * <li> Look in security properties for keys of form <code>prefix + n</code>,
    433      * where <i>n</i> is an integer and <i>prefix</i> is a passed parameter.
    434      * Sequence starts with <code>n=1</code>, and keeps incrementing <i>n</i>
    435      * until next key is not found. <br>
    436      * For each obtained key, try to construct an URL instance. On success,
    437      * add the URL to the list; otherwise ignore it.
    438      * <li>
    439      *         If security settings do not prohibit (through
    440      *         {@link #POLICY_ALLOW_DYNAMIC the &quot;policy.allowSystemProperty&quot; property})
    441      *         to use additional policy location, read the system property under the
    442      *         passed key parameter. If property exists, it may designate a file or
    443      *         an absolute URL. Thus, first check if there is a file with that name,
    444      *         and if so, convert the pathname to URL. Otherwise, try to instantiate
    445      *         an URL directly. If succeeded, append the URL to the list
    446      * <li>
    447      *         If the additional location from the step above was specified to the
    448      *         system via &quot;==&quot; (i.e. starts with '='), discard all URLs above
    449      *         and use this only URL.
    450      * </ol>
    451      * <b>Note:</b> all property values (both security and system) related to URLs are
    452      * subject to {@link #expand(String, Properties) property expansion}, regardless
    453      * of the &quot;policy.expandProperties&quot; security setting.
    454      *
    455      * @param system system properties
    456      * @param systemUrlKey key to additional policy location
    457      * @param securityUrlPrefix prefix to numbered locations in security properties
    458      * @return array of URLs to provider's configuration files, may be empty.
    459      */
    460     public static URL[] getPolicyURLs(final Properties system,
    461             final String systemUrlKey, final String securityUrlPrefix) {
    462 
    463         final SecurityPropertyAccessor security = new SecurityPropertyAccessor(
    464                 null);
    465         final List<URL> urls = new ArrayList<URL>();
    466         boolean dynamicOnly = false;
    467         URL dynamicURL = null;
    468 
    469         //first check if policy is set via system properties
    470         if (!Util.equalsIgnoreCase(FALSE, AccessController
    471                 .doPrivileged(security.key(POLICY_ALLOW_DYNAMIC)))) {
    472             String location = system.getProperty(systemUrlKey);
    473             if (location != null) {
    474                 if (location.startsWith("=")) {
    475                     //overrides all other urls
    476                     dynamicOnly = true;
    477                     location = location.substring(1);
    478                 }
    479                 try {
    480                     location = expandURL(location, system);
    481                     // location can be a file, but we need an url...
    482                     final File f = new File(location);
    483                     dynamicURL = AccessController
    484                             .doPrivileged(new PrivilegedExceptionAction<URL>() {
    485 
    486                                 public URL run() throws Exception {
    487                                     if (f.exists()) {
    488                                         return f.toURI().toURL();
    489                                     } else {
    490                                         return null;
    491                                     }
    492                                 }
    493                             });
    494                     if (dynamicURL == null) {
    495                         dynamicURL = new URL(location);
    496                     }
    497                 }
    498                 catch (Exception e) {
    499                     // TODO: log error
    500                     // System.err.println("Error detecting system policy location: "+e);
    501                 }
    502             }
    503         }
    504         //next read urls from security.properties
    505         if (!dynamicOnly) {
    506             int i = 1;
    507             while (true) {
    508                 String location = AccessController
    509                         .doPrivileged(security.key(new StringBuilder(
    510                                 securityUrlPrefix).append(i++).toString()));
    511                 if (location == null) {
    512                     break;
    513                 }
    514                 try {
    515                     location = expandURL(location, system);
    516                     URL anURL = new URL(location);
    517                     if (anURL != null) {
    518                         urls.add(anURL);
    519                     }
    520                 }
    521                 catch (Exception e) {
    522                     // TODO: log error
    523                     // System.err.println("Error detecting security policy location: "+e);
    524                 }
    525             }
    526         }
    527         if (dynamicURL != null) {
    528             urls.add(dynamicURL);
    529         }
    530         return urls.toArray(new URL[urls.size()]);
    531     }
    532 
    533     /**
    534      * Converts common-purpose collection of Permissions to PermissionCollection.
    535      *
    536      * @param perms a collection containing arbitrary permissions, may be null
    537      * @return mutable heterogeneous PermissionCollection containing all Permissions
    538      * from the specified collection
    539      */
    540     public static PermissionCollection toPermissionCollection(
    541             Collection<Permission> perms) {
    542         Permissions pc = new Permissions();
    543         if (perms != null) {
    544             for (Iterator<Permission> iter = perms.iterator(); iter.hasNext();) {
    545                 Permission element = iter.next();
    546                 pc.add(element);
    547             }
    548         }
    549         return pc;
    550     }
    551 
    552     // Empty set of arguments to default constructor of a Permission.
    553     private static final Class[] NO_ARGS = {};
    554 
    555     // One-arg set of arguments to default constructor of a Permission.
    556     private static final Class[] ONE_ARGS = { String.class };
    557 
    558     // Two-args set of arguments to default constructor of a Permission.
    559     private static final Class[] TWO_ARGS = { String.class, String.class };
    560 
    561     /**
    562      * Tries to find a suitable constructor and instantiate a new Permission
    563      * with specified parameters.
    564      *
    565      * @param targetType class of expected Permission instance
    566      * @param targetName name of expected Permission instance
    567      * @param targetActions actions of expected Permission instance
    568      * @return a new Permission instance
    569      * @throws IllegalArgumentException if no suitable constructor found
    570      * @throws Exception any exception thrown by Constructor.newInstance()
    571      */
    572     public static Permission instantiatePermission(Class<?> targetType,
    573             String targetName, String targetActions) throws Exception {
    574 
    575         // let's guess the best order for trying constructors
    576         Class[][] argTypes = null;
    577         Object[][] args = null;
    578         if (targetActions != null) {
    579             argTypes = new Class[][] { TWO_ARGS, ONE_ARGS, NO_ARGS };
    580             args = new Object[][] { { targetName, targetActions },
    581                     { targetName }, {} };
    582         } else if (targetName != null) {
    583             argTypes = new Class[][] { ONE_ARGS, TWO_ARGS, NO_ARGS };
    584             args = new Object[][] { { targetName },
    585                     { targetName, targetActions }, {} };
    586         } else {
    587             argTypes = new Class[][] { NO_ARGS, ONE_ARGS, TWO_ARGS };
    588             args = new Object[][] { {}, { targetName },
    589                     { targetName, targetActions } };
    590         }
    591 
    592         // finally try to instantiate actual permission
    593         for (int i = 0; i < argTypes.length; i++) {
    594             try {
    595                 Constructor<?> ctor = targetType.getConstructor(argTypes[i]);
    596                 return (Permission)ctor.newInstance(args[i]);
    597             }
    598             catch (NoSuchMethodException ignore) {}
    599         }
    600         throw new IllegalArgumentException("No suitable constructors found in permission class " +
    601                 targetType + ". Zero, one or two-argument constructor is expected");
    602     }
    603 
    604     /**
    605      * Checks whether the objects from <code>what</code> array are all
    606      * presented in <code>where</code> array.
    607      *
    608      * @param what first array, may be <code>null</code>
    609      * @param where  second array, may be <code>null</code>
    610      * @return <code>true</code> if the first array is <code>null</code>
    611      * or if each and every object (ignoring null values)
    612      * from the first array has a twin in the second array; <code>false</code> otherwise
    613      */
    614     public static boolean matchSubset(Object[] what, Object[] where) {
    615         if (what == null) {
    616             return true;
    617         }
    618 
    619         for (int i = 0; i < what.length; i++) {
    620             if (what[i] != null) {
    621                 if (where == null) {
    622                     return false;
    623                 }
    624                 boolean found = false;
    625                 for (int j = 0; j < where.length; j++) {
    626                     if (what[i].equals(where[j])) {
    627                         found = true;
    628                         break;
    629                     }
    630                 }
    631                 if (!found) {
    632                     return false;
    633                 }
    634             }
    635         }
    636         return true;
    637     }
    638 }
    639