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    }