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}