001/* Subject.java -- a single entity in the system. 002 Copyright (C) 2004, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.security.auth; 040 041import java.io.IOException; 042import java.io.ObjectInputStream; 043import java.io.ObjectOutputStream; 044import java.io.Serializable; 045 046import java.security.AccessControlContext; 047import java.security.AccessController; 048import java.security.DomainCombiner; 049import java.security.Principal; 050import java.security.PrivilegedAction; 051import java.security.PrivilegedActionException; 052import java.security.PrivilegedExceptionAction; 053 054import java.util.AbstractSet; 055import java.util.Collection; 056import java.util.Collections; 057import java.util.HashSet; 058import java.util.Iterator; 059import java.util.LinkedList; 060import java.util.Set; 061 062public final class Subject implements Serializable 063{ 064 // Fields. 065 // ------------------------------------------------------------------------- 066 067 private static final long serialVersionUID = -8308522755600156056L; 068 069 /** 070 * @serial The set of principals. The type of this field is SecureSet, a 071 * private inner class. 072 */ 073 private final Set principals; 074 075 /** 076 * @serial The read-only flag. 077 */ 078 private boolean readOnly; 079 080 private final transient SecureSet pubCred; 081 private final transient SecureSet privCred; 082 083 // Constructors. 084 // ------------------------------------------------------------------------- 085 086 public Subject() 087 { 088 principals = new SecureSet (this, SecureSet.PRINCIPALS); 089 pubCred = new SecureSet (this, SecureSet.PUBLIC_CREDENTIALS); 090 privCred = new SecureSet (this, SecureSet.PRIVATE_CREDENTIALS); 091 readOnly = false; 092 } 093 094 public Subject (final boolean readOnly, 095 final Set<? extends Principal> principals, 096 final Set<?> pubCred, final Set<?> privCred) 097 { 098 if (principals == null || pubCred == null || privCred == null) 099 { 100 throw new NullPointerException(); 101 } 102 this.principals = new SecureSet (this, SecureSet.PRINCIPALS, principals); 103 this.pubCred = new SecureSet (this, SecureSet.PUBLIC_CREDENTIALS, pubCred); 104 this.privCred = new SecureSet (this, SecureSet.PRIVATE_CREDENTIALS, privCred); 105 this.readOnly = readOnly; 106 } 107 108 // Class methods. 109 // ------------------------------------------------------------------------- 110 111 /** 112 * <p>Returns the subject associated with the given {@link 113 * AccessControlContext}.</p> 114 * 115 * <p>All this method does is retrieve the Subject object from the supplied 116 * context's {@link DomainCombiner}, if any, and if it is an instance of 117 * a {@link SubjectDomainCombiner}. 118 * 119 * @param context The context to retrieve the subject from. 120 * @return The subject assoctiated with the context, or <code>null</code> 121 * if there is none. 122 * @throws NullPointerException If <i>subject</i> is null. 123 * @throws SecurityException If the caller does not have permission to get 124 * the subject (<code>"getSubject"</code> target of {@link AuthPermission}. 125 */ 126 public static Subject getSubject (final AccessControlContext context) 127 { 128 final SecurityManager sm = System.getSecurityManager(); 129 if (sm != null) 130 { 131 sm.checkPermission (new AuthPermission ("getSubject")); 132 } 133 DomainCombiner dc = context.getDomainCombiner(); 134 if (!(dc instanceof SubjectDomainCombiner)) 135 { 136 return null; 137 } 138 return ((SubjectDomainCombiner) dc).getSubject(); 139 } 140 141 /** 142 * <p>Run a method as another subject. This method will obtain the current 143 * {@link AccessControlContext} for this thread, then creates another with 144 * a {@link SubjectDomainCombiner} with the given subject. The supplied 145 * action will then be run with the modified context.</p> 146 * 147 * @param subject The subject to run as. 148 * @param action The action to run. 149 * @return The value returned by the privileged action. 150 * @throws SecurityException If the caller is not allowed to run under a 151 * different identity (<code>"doAs"</code> target of {@link AuthPermission}. 152 */ 153 public static Object doAs (final Subject subject, final PrivilegedAction action) 154 { 155 final SecurityManager sm = System.getSecurityManager(); 156 if (sm != null) 157 { 158 sm.checkPermission (new AuthPermission ("doAs")); 159 } 160 AccessControlContext context = 161 new AccessControlContext (AccessController.getContext(), 162 new SubjectDomainCombiner (subject)); 163 return AccessController.doPrivileged (action, context); 164 } 165 166 /** 167 * <p>Run a method as another subject. This method will obtain the current 168 * {@link AccessControlContext} for this thread, then creates another with 169 * a {@link SubjectDomainCombiner} with the given subject. The supplied 170 * action will then be run with the modified context.</p> 171 * 172 * @param subject The subject to run as. 173 * @param action The action to run. 174 * @return The value returned by the privileged action. 175 * @throws SecurityException If the caller is not allowed to run under a 176 * different identity (<code>"doAs"</code> target of {@link AuthPermission}. 177 * @throws PrivilegedActionException If the action throws an exception. 178 */ 179 public static Object doAs (final Subject subject, 180 final PrivilegedExceptionAction action) 181 throws PrivilegedActionException 182 { 183 final SecurityManager sm = System.getSecurityManager(); 184 if (sm != null) 185 { 186 sm.checkPermission (new AuthPermission ("doAs")); 187 } 188 AccessControlContext context = 189 new AccessControlContext (AccessController.getContext(), 190 new SubjectDomainCombiner(subject)); 191 return AccessController.doPrivileged (action, context); 192 } 193 194 /** 195 * <p>Run a method as another subject. This method will create a new 196 * {@link AccessControlContext} derived from the given one, with a 197 * {@link SubjectDomainCombiner} with the given subject. The supplied 198 * action will then be run with the modified context.</p> 199 * 200 * @param subject The subject to run as. 201 * @param action The action to run. 202 * @param acc The context to use. 203 * @return The value returned by the privileged action. 204 * @throws SecurityException If the caller is not allowed to run under a 205 * different identity (<code>"doAsPrivileged"</code> target of {@link 206 * AuthPermission}. 207 */ 208 public static Object doAsPrivileged (final Subject subject, 209 final PrivilegedAction action, 210 final AccessControlContext acc) 211 { 212 final SecurityManager sm = System.getSecurityManager(); 213 if (sm != null) 214 { 215 sm.checkPermission (new AuthPermission ("doAsPrivileged")); 216 } 217 AccessControlContext context = 218 new AccessControlContext (acc, new SubjectDomainCombiner (subject)); 219 return AccessController.doPrivileged (action, context); 220 } 221 222 /** 223 * <p>Run a method as another subject. This method will create a new 224 * {@link AccessControlContext} derived from the given one, with a 225 * {@link SubjectDomainCombiner} with the given subject. The supplied 226 * action will then be run with the modified context.</p> 227 * 228 * @param subject The subject to run as. 229 * @param action The action to run. 230 * @param acc The context to use. 231 * @return The value returned by the privileged action. 232 * @throws SecurityException If the caller is not allowed to run under a 233 * different identity (<code>"doAsPrivileged"</code> target of 234 * {@link AuthPermission}. 235 * @throws PrivilegedActionException If the action throws an exception. 236 */ 237 public static Object doAsPrivileged (final Subject subject, 238 final PrivilegedExceptionAction action, 239 AccessControlContext acc) 240 throws PrivilegedActionException 241 { 242 final SecurityManager sm = System.getSecurityManager(); 243 if (sm != null) 244 { 245 sm.checkPermission (new AuthPermission ("doAsPrivileged")); 246 } 247 if (acc == null) 248 acc = new AccessControlContext (new java.security.ProtectionDomain[0]); 249 AccessControlContext context = 250 new AccessControlContext (acc, new SubjectDomainCombiner (subject)); 251 return AccessController.doPrivileged (action, context); 252 } 253 254 // Instance methods. 255 // ------------------------------------------------------------------------- 256 257 public boolean equals (Object o) 258 { 259 if (!(o instanceof Subject)) 260 { 261 return false; 262 } 263 Subject that = (Subject) o; 264 return principals.containsAll (that.getPrincipals()) && 265 pubCred.containsAll (that.getPublicCredentials()) && 266 privCred.containsAll (that.getPrivateCredentials()); 267 } 268 269 public Set<Principal> getPrincipals() 270 { 271 return principals; 272 } 273 274 public <T extends Principal> Set<T> getPrincipals(Class<T> clazz) 275 { 276 HashSet result = new HashSet (principals.size()); 277 for (Iterator it = principals.iterator(); it.hasNext(); ) 278 { 279 Object o = it.next(); 280 if (o != null && clazz.isAssignableFrom (o.getClass())) 281 { 282 result.add(o); 283 } 284 } 285 return Collections.unmodifiableSet (result); 286 } 287 288 public Set<Object> getPrivateCredentials() 289 { 290 return privCred; 291 } 292 293 public <T> Set<T> getPrivateCredentials (Class<T> clazz) 294 { 295 HashSet result = new HashSet (privCred.size()); 296 for (Iterator it = privCred.iterator(); it.hasNext(); ) 297 { 298 Object o = it.next(); 299 if (o != null && clazz.isAssignableFrom (o.getClass())) 300 { 301 result.add(o); 302 } 303 } 304 return Collections.unmodifiableSet (result); 305 } 306 307 public Set<Object> getPublicCredentials() 308 { 309 return pubCred; 310 } 311 312 public <T> Set<T> getPublicCredentials (Class<T> clazz) 313 { 314 HashSet result = new HashSet (pubCred.size()); 315 for (Iterator it = pubCred.iterator(); it.hasNext(); ) 316 { 317 Object o = it.next(); 318 if (o != null && clazz.isAssignableFrom (o.getClass())) 319 { 320 result.add(o); 321 } 322 } 323 return Collections.unmodifiableSet (result); 324 } 325 326 public int hashCode() 327 { 328 return principals.hashCode() + privCred.hashCode() + pubCred.hashCode(); 329 } 330 331 /** 332 * <p>Returns whether or not this subject is read-only.</p> 333 * 334 * @return True is this subject is read-only. 335 */ 336 public boolean isReadOnly() 337 { 338 return readOnly; 339 } 340 341 /** 342 * <p>Marks this subject as read-only.</p> 343 * 344 * @throws SecurityException If the caller does not have permission to 345 * set this subject as read-only (<code>"setReadOnly"</code> target of 346 * {@link AuthPermission}. 347 */ 348 public void setReadOnly() 349 { 350 final SecurityManager sm = System.getSecurityManager(); 351 if (sm != null) 352 { 353 sm.checkPermission (new AuthPermission ("setReadOnly")); 354 } 355 readOnly = true; 356 } 357 358 public String toString() 359 { 360 return Subject.class.getName() + " [ principals=" + principals + 361 ", private credentials=" + privCred + ", public credentials=" + 362 pubCred + ", read-only=" + readOnly + " ]"; 363 } 364 365// Inner class. 366 // ------------------------------------------------------------------------- 367 368 /** 369 * An undocumented inner class that is used for sets in the parent class. 370 */ 371 private static class SecureSet extends AbstractSet implements Serializable 372 { 373 // Fields. 374 // ----------------------------------------------------------------------- 375 376 private static final long serialVersionUID = 7911754171111800359L; 377 378 static final int PRINCIPALS = 0; 379 static final int PUBLIC_CREDENTIALS = 1; 380 static final int PRIVATE_CREDENTIALS = 2; 381 382 private final Subject subject; 383 private final LinkedList elements; 384 private final transient int type; 385 386 // Constructors. 387 // ----------------------------------------------------------------------- 388 389 SecureSet (final Subject subject, final int type, final Collection inElements) 390 { 391 this (subject, type); 392 for (Iterator it = inElements.iterator(); it.hasNext(); ) 393 { 394 Object o = it.next(); 395 if (type == PRINCIPALS && !(o instanceof Principal)) 396 { 397 throw new IllegalArgumentException(o+" is not a Principal"); 398 } 399 if (!this.elements.contains (o)) 400 { 401 this.elements.add (o); 402 } 403 } 404 } 405 406 SecureSet (final Subject subject, final int type) 407 { 408 this.subject = subject; 409 this.type = type; 410 this.elements = new LinkedList(); 411 } 412 413 // Instance methods. 414 // ----------------------------------------------------------------------- 415 416 public synchronized int size() 417 { 418 return elements.size(); 419 } 420 421 public Iterator iterator() 422 { 423 return elements.iterator(); 424 } 425 426 public synchronized boolean add(Object element) 427 { 428 if (subject.isReadOnly()) 429 { 430 throw new IllegalStateException ("subject is read-only"); 431 } 432 final SecurityManager sm = System.getSecurityManager(); 433 switch (type) 434 { 435 case PRINCIPALS: 436 if (sm != null) 437 { 438 sm.checkPermission (new AuthPermission ("modifyPrincipals")); 439 } 440 if (!(element instanceof Principal)) 441 { 442 throw new IllegalArgumentException ("element is not a Principal"); 443 } 444 break; 445 446 case PUBLIC_CREDENTIALS: 447 if (sm != null) 448 { 449 sm.checkPermission (new AuthPermission ("modifyPublicCredentials")); 450 } 451 break; 452 453 case PRIVATE_CREDENTIALS: 454 if (sm != null) 455 { 456 sm.checkPermission (new AuthPermission ("modifyPrivateCredentials")); 457 } 458 break; 459 460 default: 461 throw new Error ("this statement should be unreachable"); 462 } 463 464 if (elements.contains (element)) 465 { 466 return false; 467 } 468 469 return elements.add (element); 470 } 471 472 public synchronized boolean remove (final Object element) 473 { 474 if (subject.isReadOnly()) 475 { 476 throw new IllegalStateException ("subject is read-only"); 477 } 478 final SecurityManager sm = System.getSecurityManager(); 479 switch (type) 480 { 481 case PRINCIPALS: 482 if (sm != null) 483 { 484 sm.checkPermission (new AuthPermission ("modifyPrincipals")); 485 } 486 if (!(element instanceof Principal)) 487 { 488 throw new IllegalArgumentException ("element is not a Principal"); 489 } 490 break; 491 492 case PUBLIC_CREDENTIALS: 493 if (sm != null) 494 { 495 sm.checkPermission (new AuthPermission ("modifyPublicCredentials")); 496 } 497 break; 498 499 case PRIVATE_CREDENTIALS: 500 if (sm != null) 501 { 502 sm.checkPermission (new AuthPermission ("modifyPrivateCredentials")); 503 } 504 break; 505 506 default: 507 throw new Error("this statement should be unreachable"); 508 } 509 510 return elements.remove(element); 511 } 512 513 public synchronized boolean contains (final Object element) 514 { 515 return elements.contains (element); 516 } 517 518 public boolean removeAll (final Collection c) 519 { 520 if (subject.isReadOnly()) 521 { 522 throw new IllegalStateException ("subject is read-only"); 523 } 524 return super.removeAll (c); 525 } 526 527 public boolean retainAll (final Collection c) 528 { 529 if (subject.isReadOnly()) 530 { 531 throw new IllegalStateException ("subject is read-only"); 532 } 533 return super.retainAll (c); 534 } 535 536 public void clear() 537 { 538 if (subject.isReadOnly()) 539 { 540 throw new IllegalStateException ("subject is read-only"); 541 } 542 elements.clear(); 543 } 544 545 private synchronized void writeObject (ObjectOutputStream out) 546 throws IOException 547 { 548 throw new UnsupportedOperationException ("FIXME: determine serialization"); 549 } 550 551 private void readObject (ObjectInputStream in) 552 throws ClassNotFoundException, IOException 553 { 554 throw new UnsupportedOperationException ("FIXME: determine serialization"); 555 } 556 } 557}