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 * "provide key and supply action" 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 "policy.allowSystemProperty" 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 "==" (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 "policy.expandProperties" 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