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