001 /* Subject.java -- a single entity in the system.
002 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.security.auth;
040
041 import java.io.IOException;
042 import java.io.ObjectInputStream;
043 import java.io.ObjectOutputStream;
044 import java.io.Serializable;
045
046 import java.security.AccessControlContext;
047 import java.security.AccessController;
048 import java.security.DomainCombiner;
049 import java.security.Principal;
050 import java.security.PrivilegedAction;
051 import java.security.PrivilegedActionException;
052 import java.security.PrivilegedExceptionAction;
053
054 import java.util.AbstractSet;
055 import java.util.Collection;
056 import java.util.Collections;
057 import java.util.HashSet;
058 import java.util.Iterator;
059 import java.util.LinkedList;
060 import java.util.Set;
061
062 public 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 }