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