1 /* 2 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.security.auth; 27 28 import java.security.AccessController; 29 import java.security.Permission; 30 import java.security.Permissions; 31 import java.security.PermissionCollection; 32 import java.security.Policy; 33 import java.security.Principal; 34 import java.security.PrivilegedAction; 35 import java.security.ProtectionDomain; 36 import java.security.Security; 37 import java.util.Set; 38 import java.util.WeakHashMap; 39 import java.lang.ref.WeakReference; 40 41 /** 42 * A <code>SubjectDomainCombiner</code> updates ProtectionDomains 43 * with Principals from the <code>Subject</code> associated with this 44 * <code>SubjectDomainCombiner</code>. 45 * 46 */ 47 public class SubjectDomainCombiner implements java.security.DomainCombiner { 48 49 private Subject subject; 50 private WeakKeyValueMap<ProtectionDomain, ProtectionDomain> cachedPDs = 51 new WeakKeyValueMap<>(); 52 private Set<Principal> principalSet; 53 private Principal[] principals; 54 55 private static final sun.security.util.Debug debug = 56 sun.security.util.Debug.getInstance("combiner", 57 "\t[SubjectDomainCombiner]"); 58 59 // Note: check only at classloading time, not dynamically during combine() 60 private static final boolean useJavaxPolicy = 61 javax.security.auth.Policy.isCustomPolicySet(debug); 62 63 // Relevant only when useJavaxPolicy is true 64 private static final boolean allowCaching = 65 (useJavaxPolicy && cachePolicy()); 66 67 /** 68 * Associate the provided <code>Subject</code> with this 69 * <code>SubjectDomainCombiner</code>. 70 * 71 * <p> 72 * 73 * @param subject the <code>Subject</code> to be associated with 74 * with this <code>SubjectDomainCombiner</code>. 75 */ 76 public SubjectDomainCombiner(Subject subject) { 77 this.subject = subject; 78 79 if (subject.isReadOnly()) { 80 principalSet = subject.getPrincipals(); 81 principals = principalSet.toArray 82 (new Principal[principalSet.size()]); 83 } 84 } 85 86 /** 87 * Get the <code>Subject</code> associated with this 88 * <code>SubjectDomainCombiner</code>. 89 * 90 * <p> 91 * 92 * @return the <code>Subject</code> associated with this 93 * <code>SubjectDomainCombiner</code>, or <code>null</code> 94 * if no <code>Subject</code> is associated with this 95 * <code>SubjectDomainCombiner</code>. 96 * 97 * @exception SecurityException if the caller does not have permission 98 * to get the <code>Subject</code> associated with this 99 * <code>SubjectDomainCombiner</code>. 100 */ 101 public Subject getSubject() { 102 java.lang.SecurityManager sm = System.getSecurityManager(); 103 if (sm != null) { 104 sm.checkPermission(new AuthPermission 105 ("getSubjectFromDomainCombiner")); 106 } 107 return subject; 108 } 109 110 /** 111 * Update the relevant ProtectionDomains with the Principals 112 * from the <code>Subject</code> associated with this 113 * <code>SubjectDomainCombiner</code>. 114 * 115 * <p> A new <code>ProtectionDomain</code> instance is created 116 * for each <code>ProtectionDomain</code> in the 117 * <i>currentDomains</i> array. Each new <code>ProtectionDomain</code> 118 * instance is created using the <code>CodeSource</code>, 119 * <code>Permission</code>s and <code>ClassLoader</code> 120 * from the corresponding <code>ProtectionDomain</code> in 121 * <i>currentDomains</i>, as well as with the Principals from 122 * the <code>Subject</code> associated with this 123 * <code>SubjectDomainCombiner</code>. 124 * 125 * <p> All of the newly instantiated ProtectionDomains are 126 * combined into a new array. The ProtectionDomains from the 127 * <i>assignedDomains</i> array are appended to this new array, 128 * and the result is returned. 129 * 130 * <p> Note that optimizations such as the removal of duplicate 131 * ProtectionDomains may have occurred. 132 * In addition, caching of ProtectionDomains may be permitted. 133 * 134 * <p> 135 * 136 * @param currentDomains the ProtectionDomains associated with the 137 * current execution Thread, up to the most recent 138 * privileged <code>ProtectionDomain</code>. 139 * The ProtectionDomains are are listed in order of execution, 140 * with the most recently executing <code>ProtectionDomain</code> 141 * residing at the beginning of the array. This parameter may 142 * be <code>null</code> if the current execution Thread 143 * has no associated ProtectionDomains.<p> 144 * 145 * @param assignedDomains the ProtectionDomains inherited from the 146 * parent Thread, or the ProtectionDomains from the 147 * privileged <i>context</i>, if a call to 148 * AccessController.doPrivileged(..., <i>context</i>) 149 * had occurred This parameter may be <code>null</code> 150 * if there were no ProtectionDomains inherited from the 151 * parent Thread, or from the privileged <i>context</i>. 152 * 153 * @return a new array consisting of the updated ProtectionDomains, 154 * or <code>null</code>. 155 */ 156 public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, 157 ProtectionDomain[] assignedDomains) { 158 if (debug != null) { 159 if (subject == null) { 160 debug.println("null subject"); 161 } else { 162 final Subject s = subject; 163 AccessController.doPrivileged 164 (new java.security.PrivilegedAction<Void>() { 165 public Void run() { 166 debug.println(s.toString()); 167 return null; 168 } 169 }); 170 } 171 printInputDomains(currentDomains, assignedDomains); 172 } 173 174 if (currentDomains == null || currentDomains.length == 0) { 175 // No need to optimize assignedDomains because it should 176 // have been previously optimized (when it was set). 177 178 // Note that we are returning a direct reference 179 // to the input array - since ACC does not clone 180 // the arrays when it calls combiner.combine, 181 // multiple ACC instances may share the same 182 // array instance in this case 183 184 return assignedDomains; 185 } 186 187 // optimize currentDomains 188 // 189 // No need to optimize assignedDomains because it should 190 // have been previously optimized (when it was set). 191 192 currentDomains = optimize(currentDomains); 193 if (debug != null) { 194 debug.println("after optimize"); 195 printInputDomains(currentDomains, assignedDomains); 196 } 197 198 if (currentDomains == null && assignedDomains == null) { 199 return null; 200 } 201 202 // maintain backwards compatibility for developers who provide 203 // their own custom javax.security.auth.Policy implementations 204 if (useJavaxPolicy) { 205 return combineJavaxPolicy(currentDomains, assignedDomains); 206 } 207 208 int cLen = (currentDomains == null ? 0 : currentDomains.length); 209 int aLen = (assignedDomains == null ? 0 : assignedDomains.length); 210 211 // the ProtectionDomains for the new AccessControlContext 212 // that we will return 213 ProtectionDomain[] newDomains = new ProtectionDomain[cLen + aLen]; 214 215 boolean allNew = true; 216 synchronized(cachedPDs) { 217 if (!subject.isReadOnly() && 218 !subject.getPrincipals().equals(principalSet)) { 219 220 // if the Subject was mutated, clear the PD cache 221 Set<Principal> newSet = subject.getPrincipals(); 222 synchronized(newSet) { 223 principalSet = new java.util.HashSet<Principal>(newSet); 224 } 225 principals = principalSet.toArray 226 (new Principal[principalSet.size()]); 227 cachedPDs.clear(); 228 229 if (debug != null) { 230 debug.println("Subject mutated - clearing cache"); 231 } 232 } 233 234 ProtectionDomain subjectPd; 235 for (int i = 0; i < cLen; i++) { 236 ProtectionDomain pd = currentDomains[i]; 237 238 subjectPd = cachedPDs.getValue(pd); 239 240 if (subjectPd == null) { 241 subjectPd = new ProtectionDomain(pd.getCodeSource(), 242 pd.getPermissions(), 243 pd.getClassLoader(), 244 principals); 245 cachedPDs.putValue(pd, subjectPd); 246 } else { 247 allNew = false; 248 } 249 newDomains[i] = subjectPd; 250 } 251 } 252 253 if (debug != null) { 254 debug.println("updated current: "); 255 for (int i = 0; i < cLen; i++) { 256 debug.println("\tupdated[" + i + "] = " + 257 printDomain(newDomains[i])); 258 } 259 } 260 261 // now add on the assigned domains 262 if (aLen > 0) { 263 System.arraycopy(assignedDomains, 0, newDomains, cLen, aLen); 264 265 // optimize the result (cached PDs might exist in assignedDomains) 266 if (!allNew) { 267 newDomains = optimize(newDomains); 268 } 269 } 270 271 // if aLen == 0 || allNew, no need to further optimize newDomains 272 273 if (debug != null) { 274 if (newDomains == null || newDomains.length == 0) { 275 debug.println("returning null"); 276 } else { 277 debug.println("combinedDomains: "); 278 for (int i = 0; i < newDomains.length; i++) { 279 debug.println("newDomain " + i + ": " + 280 printDomain(newDomains[i])); 281 } 282 } 283 } 284 285 // return the new ProtectionDomains 286 if (newDomains == null || newDomains.length == 0) { 287 return null; 288 } else { 289 return newDomains; 290 } 291 } 292 293 /** 294 * Use the javax.security.auth.Policy implementation 295 */ 296 private ProtectionDomain[] combineJavaxPolicy( 297 ProtectionDomain[] currentDomains, 298 ProtectionDomain[] assignedDomains) { 299 300 if (!allowCaching) { 301 java.security.AccessController.doPrivileged 302 (new PrivilegedAction<Void>() { 303 public Void run() { 304 // Call refresh only caching is disallowed 305 javax.security.auth.Policy.getPolicy().refresh(); 306 return null; 307 } 308 }); 309 } 310 311 int cLen = (currentDomains == null ? 0 : currentDomains.length); 312 int aLen = (assignedDomains == null ? 0 : assignedDomains.length); 313 314 // the ProtectionDomains for the new AccessControlContext 315 // that we will return 316 ProtectionDomain[] newDomains = new ProtectionDomain[cLen + aLen]; 317 318 synchronized(cachedPDs) { 319 if (!subject.isReadOnly() && 320 !subject.getPrincipals().equals(principalSet)) { 321 322 // if the Subject was mutated, clear the PD cache 323 Set<Principal> newSet = subject.getPrincipals(); 324 synchronized(newSet) { 325 principalSet = new java.util.HashSet<Principal>(newSet); 326 } 327 principals = principalSet.toArray 328 (new Principal[principalSet.size()]); 329 cachedPDs.clear(); 330 331 if (debug != null) { 332 debug.println("Subject mutated - clearing cache"); 333 } 334 } 335 336 for (int i = 0; i < cLen; i++) { 337 ProtectionDomain pd = currentDomains[i]; 338 ProtectionDomain subjectPd = cachedPDs.getValue(pd); 339 340 if (subjectPd == null) { 341 342 // XXX 343 // we must first add the original permissions. 344 // that way when we later add the new JAAS permissions, 345 // any unresolved JAAS-related permissions will 346 // automatically get resolved. 347 348 // get the original perms 349 Permissions perms = new Permissions(); 350 PermissionCollection coll = pd.getPermissions(); 351 java.util.Enumeration e; 352 if (coll != null) { 353 synchronized (coll) { 354 e = coll.elements(); 355 while (e.hasMoreElements()) { 356 Permission newPerm = 357 (Permission)e.nextElement(); 358 perms.add(newPerm); 359 } 360 } 361 } 362 363 // get perms from the policy 364 365 final java.security.CodeSource finalCs = pd.getCodeSource(); 366 final Subject finalS = subject; 367 PermissionCollection newPerms = 368 java.security.AccessController.doPrivileged 369 (new PrivilegedAction<PermissionCollection>() { 370 public PermissionCollection run() { 371 return 372 javax.security.auth.Policy.getPolicy().getPermissions 373 (finalS, finalCs); 374 } 375 }); 376 377 // add the newly granted perms, 378 // avoiding duplicates 379 synchronized (newPerms) { 380 e = newPerms.elements(); 381 while (e.hasMoreElements()) { 382 Permission newPerm = (Permission)e.nextElement(); 383 if (!perms.implies(newPerm)) { 384 perms.add(newPerm); 385 if (debug != null) 386 debug.println ( 387 "Adding perm " + newPerm + "\n"); 388 } 389 } 390 } 391 subjectPd = new ProtectionDomain 392 (finalCs, perms, pd.getClassLoader(), principals); 393 394 if (allowCaching) 395 cachedPDs.putValue(pd, subjectPd); 396 } 397 newDomains[i] = subjectPd; 398 } 399 } 400 401 if (debug != null) { 402 debug.println("updated current: "); 403 for (int i = 0; i < cLen; i++) { 404 debug.println("\tupdated[" + i + "] = " + newDomains[i]); 405 } 406 } 407 408 // now add on the assigned domains 409 if (aLen > 0) { 410 System.arraycopy(assignedDomains, 0, newDomains, cLen, aLen); 411 } 412 413 if (debug != null) { 414 if (newDomains == null || newDomains.length == 0) { 415 debug.println("returning null"); 416 } else { 417 debug.println("combinedDomains: "); 418 for (int i = 0; i < newDomains.length; i++) { 419 debug.println("newDomain " + i + ": " + 420 newDomains[i].toString()); 421 } 422 } 423 } 424 425 // return the new ProtectionDomains 426 if (newDomains == null || newDomains.length == 0) { 427 return null; 428 } else { 429 return newDomains; 430 } 431 } 432 433 private static ProtectionDomain[] optimize(ProtectionDomain[] domains) { 434 if (domains == null || domains.length == 0) 435 return null; 436 437 ProtectionDomain[] optimized = new ProtectionDomain[domains.length]; 438 ProtectionDomain pd; 439 int num = 0; 440 for (int i = 0; i < domains.length; i++) { 441 442 // skip domains with AllPermission 443 // XXX 444 // 445 // if (domains[i].implies(ALL_PERMISSION)) 446 // continue; 447 448 // skip System Domains 449 if ((pd = domains[i]) != null) { 450 451 // remove duplicates 452 boolean found = false; 453 for (int j = 0; j < num && !found; j++) { 454 found = (optimized[j] == pd); 455 } 456 if (!found) { 457 optimized[num++] = pd; 458 } 459 } 460 } 461 462 // resize the array if necessary 463 if (num > 0 && num < domains.length) { 464 ProtectionDomain[] downSize = new ProtectionDomain[num]; 465 System.arraycopy(optimized, 0, downSize, 0, downSize.length); 466 optimized = downSize; 467 } 468 469 return ((num == 0 || optimized.length == 0) ? null : optimized); 470 } 471 472 private static boolean cachePolicy() { 473 String s = AccessController.doPrivileged 474 (new PrivilegedAction<String>() { 475 public String run() { 476 return Security.getProperty("cache.auth.policy"); 477 } 478 }); 479 if (s != null) { 480 return Boolean.parseBoolean(s); 481 } 482 483 // cache by default 484 return true; 485 } 486 487 private static void printInputDomains(ProtectionDomain[] currentDomains, 488 ProtectionDomain[] assignedDomains) { 489 if (currentDomains == null || currentDomains.length == 0) { 490 debug.println("currentDomains null or 0 length"); 491 } else { 492 for (int i = 0; currentDomains != null && 493 i < currentDomains.length; i++) { 494 if (currentDomains[i] == null) { 495 debug.println("currentDomain " + i + ": SystemDomain"); 496 } else { 497 debug.println("currentDomain " + i + ": " + 498 printDomain(currentDomains[i])); 499 } 500 } 501 } 502 503 if (assignedDomains == null || assignedDomains.length == 0) { 504 debug.println("assignedDomains null or 0 length"); 505 } else { 506 debug.println("assignedDomains = "); 507 for (int i = 0; assignedDomains != null && 508 i < assignedDomains.length; i++) { 509 if (assignedDomains[i] == null) { 510 debug.println("assignedDomain " + i + ": SystemDomain"); 511 } else { 512 debug.println("assignedDomain " + i + ": " + 513 printDomain(assignedDomains[i])); 514 } 515 } 516 } 517 } 518 519 private static String printDomain(final ProtectionDomain pd) { 520 if (pd == null) { 521 return "null"; 522 } 523 return AccessController.doPrivileged(new PrivilegedAction<String>() { 524 public String run() { 525 return pd.toString(); 526 } 527 }); 528 } 529 530 /** 531 * A HashMap that has weak keys and values. 532 * 533 * Key objects in this map are the "current" ProtectionDomain instances 534 * received via the combine method. Each "current" PD is mapped to a 535 * new PD instance that holds both the contents of the "current" PD, 536 * as well as the principals from the Subject associated with this combiner. 537 * 538 * The newly created "principal-based" PD values must be stored as 539 * WeakReferences since they contain strong references to the 540 * corresponding key object (the "current" non-principal-based PD), 541 * which will prevent the key from being GC'd. Specifically, 542 * a "principal-based" PD contains strong references to the CodeSource, 543 * signer certs, PermissionCollection and ClassLoader objects 544 * in the "current PD". 545 */ 546 private static class WeakKeyValueMap<K,V> extends 547 WeakHashMap<K,WeakReference<V>> { 548 549 public V getValue(K key) { 550 WeakReference<V> wr = super.get(key); 551 if (wr != null) { 552 return wr.get(); 553 } 554 return null; 555 } 556 557 public V putValue(K key, V value) { 558 WeakReference<V> wr = super.put(key, new WeakReference<V>(value)); 559 if (wr != null) { 560 return wr.get(); 561 } 562 return null; 563 } 564 } 565 }