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