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