/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.harmony.javax.security.auth; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.security.AccessControlContext; import java.security.AccessController; import java.security.DomainCombiner; import java.security.Permission; import java.security.Principal; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; /** * The central class of the {@code javax.security.auth} package representing an * authenticated user or entity (both referred to as "subject"). IT defines also * the static methods that allow code to be run, and do modifications according * to the subject's permissions. *

* A subject has the following features: *

*/ public final class Subject implements Serializable { private static final long serialVersionUID = -8308522755600156056L; private static final AuthPermission _AS = new AuthPermission("doAs"); //$NON-NLS-1$ private static final AuthPermission _AS_PRIVILEGED = new AuthPermission( "doAsPrivileged"); //$NON-NLS-1$ private static final AuthPermission _SUBJECT = new AuthPermission( "getSubject"); //$NON-NLS-1$ private static final AuthPermission _PRINCIPALS = new AuthPermission( "modifyPrincipals"); //$NON-NLS-1$ private static final AuthPermission _PRIVATE_CREDENTIALS = new AuthPermission( "modifyPrivateCredentials"); //$NON-NLS-1$ private static final AuthPermission _PUBLIC_CREDENTIALS = new AuthPermission( "modifyPublicCredentials"); //$NON-NLS-1$ private static final AuthPermission _READ_ONLY = new AuthPermission( "setReadOnly"); //$NON-NLS-1$ private final Set principals; private boolean readOnly; // set of private credentials private transient SecureSet privateCredentials; // set of public credentials private transient SecureSet publicCredentials; /** * The default constructor initializing the sets of public and private * credentials and principals with the empty set. */ public Subject() { super(); principals = new SecureSet(_PRINCIPALS); publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); readOnly = false; } /** * The constructor for the subject, setting its public and private * credentials and principals according to the arguments. * * @param readOnly * {@code true} if this {@code Subject} is read-only, thus * preventing any modifications to be done. * @param subjPrincipals * the set of Principals that are attributed to this {@code * Subject}. * @param pubCredentials * the set of public credentials that distinguish this {@code * Subject}. * @param privCredentials * the set of private credentials that distinguish this {@code * Subject}. */ public Subject(boolean readOnly, Set subjPrincipals, Set pubCredentials, Set privCredentials) { if (subjPrincipals == null || pubCredentials == null || privCredentials == null) { throw new NullPointerException(); } principals = new SecureSet(_PRINCIPALS, subjPrincipals); publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS, pubCredentials); privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS, privCredentials); this.readOnly = readOnly; } /** * Runs the code defined by {@code action} using the permissions granted to * the {@code Subject} itself and to the code as well. * * @param subject * the distinguished {@code Subject}. * @param action * the code to be run. * @return the {@code Object} returned when running the {@code action}. */ @SuppressWarnings("unchecked") public static Object doAs(Subject subject, PrivilegedAction action) { checkPermission(_AS); return doAs_PrivilegedAction(subject, action, AccessController.getContext()); } /** * Run the code defined by {@code action} using the permissions granted to * the {@code Subject} and to the code itself, additionally providing a more * specific context. * * @param subject * the distinguished {@code Subject}. * @param action * the code to be run. * @param context * the specific context in which the {@code action} is invoked. * if {@code null} a new {@link AccessControlContext} is * instantiated. * @return the {@code Object} returned when running the {@code action}. */ @SuppressWarnings("unchecked") public static Object doAsPrivileged(Subject subject, PrivilegedAction action, AccessControlContext context) { checkPermission(_AS_PRIVILEGED); if (context == null) { return doAs_PrivilegedAction(subject, action, new AccessControlContext( new ProtectionDomain[0])); } return doAs_PrivilegedAction(subject, action, context); } // instantiates a new context and passes it to AccessController @SuppressWarnings("unchecked") private static Object doAs_PrivilegedAction(Subject subject, PrivilegedAction action, final AccessControlContext context) { AccessControlContext newContext; final SubjectDomainCombiner combiner; if (subject == null) { // performance optimization // if subject is null there is nothing to combine combiner = null; } else { combiner = new SubjectDomainCombiner(subject); } PrivilegedAction dccAction = new PrivilegedAction() { public Object run() { return new AccessControlContext(context, combiner); } }; newContext = (AccessControlContext) AccessController.doPrivileged(dccAction); return AccessController.doPrivileged(action, newContext); } /** * Runs the code defined by {@code action} using the permissions granted to * the subject and to the code itself. * * @param subject * the distinguished {@code Subject}. * @param action * the code to be run. * @return the {@code Object} returned when running the {@code action}. * @throws PrivilegedActionException * if running the {@code action} throws an exception. */ @SuppressWarnings("unchecked") public static Object doAs(Subject subject, PrivilegedExceptionAction action) throws PrivilegedActionException { checkPermission(_AS); return doAs_PrivilegedExceptionAction(subject, action, AccessController.getContext()); } /** * Runs the code defined by {@code action} using the permissions granted to * the subject and to the code itself, additionally providing a more * specific context. * * @param subject * the distinguished {@code Subject}. * @param action * the code to be run. * @param context * the specific context in which the {@code action} is invoked. * if {@code null} a new {@link AccessControlContext} is * instantiated. * @return the {@code Object} returned when running the {@code action}. * @throws PrivilegedActionException * if running the {@code action} throws an exception. */ @SuppressWarnings("unchecked") public static Object doAsPrivileged(Subject subject, PrivilegedExceptionAction action, AccessControlContext context) throws PrivilegedActionException { checkPermission(_AS_PRIVILEGED); if (context == null) { return doAs_PrivilegedExceptionAction(subject, action, new AccessControlContext(new ProtectionDomain[0])); } return doAs_PrivilegedExceptionAction(subject, action, context); } // instantiates a new context and passes it to AccessController @SuppressWarnings("unchecked") private static Object doAs_PrivilegedExceptionAction(Subject subject, PrivilegedExceptionAction action, final AccessControlContext context) throws PrivilegedActionException { AccessControlContext newContext; final SubjectDomainCombiner combiner; if (subject == null) { // performance optimization // if subject is null there is nothing to combine combiner = null; } else { combiner = new SubjectDomainCombiner(subject); } PrivilegedAction dccAction = new PrivilegedAction() { public AccessControlContext run() { return new AccessControlContext(context, combiner); } }; newContext = AccessController.doPrivileged(dccAction); return AccessController.doPrivileged(action, newContext); } /** * Checks two Subjects for equality. More specifically if the principals, * public and private credentials are equal, equality for two {@code * Subjects} is implied. * * @param obj * the {@code Object} checked for equality with this {@code * Subject}. * @return {@code true} if the specified {@code Subject} is equal to this * one. */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || this.getClass() != obj.getClass()) { return false; } Subject that = (Subject) obj; if (principals.equals(that.principals) && publicCredentials.equals(that.publicCredentials) && privateCredentials.equals(that.privateCredentials)) { return true; } return false; } /** * Returns this {@code Subject}'s {@link Principal}. * * @return this {@code Subject}'s {@link Principal}. */ public Set getPrincipals() { return principals; } /** * Returns this {@code Subject}'s {@link Principal} which is a subclass of * the {@code Class} provided. * * @param c * the {@code Class} as a criteria which the {@code Principal} * returned must satisfy. * @return this {@code Subject}'s {@link Principal}. Modifications to the * returned set of {@code Principal}s do not affect this {@code * Subject}'s set. */ public Set getPrincipals(Class c) { return ((SecureSet) principals).get(c); } /** * Returns the private credentials associated with this {@code Subject}. * * @return the private credentials associated with this {@code Subject}. */ public Set getPrivateCredentials() { return privateCredentials; } /** * Returns this {@code Subject}'s private credentials which are a subclass * of the {@code Class} provided. * * @param c * the {@code Class} as a criteria which the private credentials * returned must satisfy. * @return this {@code Subject}'s private credentials. Modifications to the * returned set of credentials do not affect this {@code Subject}'s * credentials. */ public Set getPrivateCredentials(Class c) { return privateCredentials.get(c); } /** * Returns the public credentials associated with this {@code Subject}. * * @return the public credentials associated with this {@code Subject}. */ public Set getPublicCredentials() { return publicCredentials; } /** * Returns this {@code Subject}'s public credentials which are a subclass of * the {@code Class} provided. * * @param c * the {@code Class} as a criteria which the public credentials * returned must satisfy. * @return this {@code Subject}'s public credentials. Modifications to the * returned set of credentials do not affect this {@code Subject}'s * credentials. */ public Set getPublicCredentials(Class c) { return publicCredentials.get(c); } /** * Returns a hash code of this {@code Subject}. * * @return a hash code of this {@code Subject}. */ @Override public int hashCode() { return principals.hashCode() + privateCredentials.hashCode() + publicCredentials.hashCode(); } /** * Prevents from modifications being done to the credentials and {@link * Principal} sets. After setting it to read-only this {@code Subject} can * not be made writable again. The destroy method on the credentials still * works though. */ public void setReadOnly() { checkPermission(_READ_ONLY); readOnly = true; } /** * Returns whether this {@code Subject} is read-only or not. * * @return whether this {@code Subject} is read-only or not. */ public boolean isReadOnly() { return readOnly; } /** * Returns a {@code String} representation of this {@code Subject}. * * @return a {@code String} representation of this {@code Subject}. */ @Override public String toString() { StringBuilder buf = new StringBuilder("Subject:\n"); //$NON-NLS-1$ Iterator it = principals.iterator(); while (it.hasNext()) { buf.append("\tPrincipal: "); //$NON-NLS-1$ buf.append(it.next()); buf.append('\n'); } it = publicCredentials.iterator(); while (it.hasNext()) { buf.append("\tPublic Credential: "); //$NON-NLS-1$ buf.append(it.next()); buf.append('\n'); } int offset = buf.length() - 1; it = privateCredentials.iterator(); try { while (it.hasNext()) { buf.append("\tPrivate Credential: "); //$NON-NLS-1$ buf.append(it.next()); buf.append('\n'); } } catch (SecurityException e) { buf.delete(offset, buf.length()); buf.append("\tPrivate Credentials: no accessible information\n"); //$NON-NLS-1$ } return buf.toString(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } /** * Returns the {@code Subject} that was last associated with the {@code * context} provided as argument. * * @param context * the {@code context} that was associated with the * {@code Subject}. * @return the {@code Subject} that was last associated with the {@code * context} provided as argument. */ public static Subject getSubject(final AccessControlContext context) { checkPermission(_SUBJECT); if (context == null) { throw new NullPointerException("auth.09"); //$NON-NLS-1$ } PrivilegedAction action = new PrivilegedAction() { public DomainCombiner run() { return context.getDomainCombiner(); } }; DomainCombiner combiner = AccessController.doPrivileged(action); if ((combiner == null) || !(combiner instanceof SubjectDomainCombiner)) { return null; } return ((SubjectDomainCombiner) combiner).getSubject(); } // checks passed permission private static void checkPermission(Permission p) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(p); } } // FIXME is used only in two places. remove? private void checkState() { if (readOnly) { throw new IllegalStateException("auth.0A"); //$NON-NLS-1$ } } private final class SecureSet extends AbstractSet implements Serializable { /** * Compatibility issue: see comments for setType variable */ private static final long serialVersionUID = 7911754171111800359L; private LinkedList elements; /* * Is used to define a set type for serialization. * * A type can be principal, priv. or pub. credential set. The spec. * doesn't clearly says that priv. and pub. credential sets can be * serialized and what classes they are. It is only possible to figure * out from writeObject method comments that priv. credential set is * serializable and it is an instance of SecureSet class. So pub. * credential was implemented by analogy * * Compatibility issue: the class follows its specified serial form. * Also according to the serialization spec. adding new field is a * compatible change. So is ok for principal set (because the default * value for integer is zero). But priv. or pub. credential set it is * not compatible because most probably other implementations resolve * this issue in other way */ private int setType; // Defines principal set for serialization. private static final int SET_Principal = 0; // Defines private credential set for serialization. private static final int SET_PrivCred = 1; // Defines public credential set for serialization. private static final int SET_PubCred = 2; // permission required to modify set private transient AuthPermission permission; protected SecureSet(AuthPermission perm) { permission = perm; elements = new LinkedList(); } // creates set from specified collection with specified permission // all collection elements are verified before adding protected SecureSet(AuthPermission perm, Collection s) { this(perm); // Subject's constructor receives a Set, we can trusts if a set is from bootclasspath, // and not to check whether it contains duplicates or not boolean trust = s.getClass().getClassLoader() == null; Iterator it = s.iterator(); while (it.hasNext()) { SST o = it.next(); verifyElement(o); if (trust || !elements.contains(o)) { elements.add(o); } } } // verifies new set element private void verifyElement(Object o) { if (o == null) { throw new NullPointerException(); } if (permission == _PRINCIPALS && !(Principal.class.isAssignableFrom(o.getClass()))) { throw new IllegalArgumentException("auth.0B"); //$NON-NLS-1$ } } /* * verifies specified element, checks set state, and security permission * to modify set before adding new element */ @Override public boolean add(SST o) { verifyElement(o); checkState(); checkPermission(permission); if (!elements.contains(o)) { elements.add(o); return true; } return false; } // returns an instance of SecureIterator @Override public Iterator iterator() { if (permission == _PRIVATE_CREDENTIALS) { /* * private credential set requires iterator with additional * security check (PrivateCredentialPermission) */ return new SecureIterator(elements.iterator()) { /* * checks permission to access next private credential moves * to the next element even SecurityException was thrown */ @Override public SST next() { SST obj = iterator.next(); checkPermission(new PrivateCredentialPermission(obj .getClass().getName(), principals)); return obj; } }; } return new SecureIterator(elements.iterator()); } @Override public boolean retainAll(Collection c) { if (c == null) { throw new NullPointerException(); } return super.retainAll(c); } @Override public int size() { return elements.size(); } /** * return set with elements that are instances or subclasses of the * specified class */ protected final Set get(final Class c) { if (c == null) { throw new NullPointerException(); } AbstractSet s = new AbstractSet() { private LinkedList elements = new LinkedList(); @Override public boolean add(E o) { if (!c.isAssignableFrom(o.getClass())) { throw new IllegalArgumentException( "auth.0C " + c.getName()); //$NON-NLS-1$ } if (elements.contains(o)) { return false; } elements.add(o); return true; } @Override public Iterator iterator() { return elements.iterator(); } @Override public boolean retainAll(Collection c) { if (c == null) { throw new NullPointerException(); } return super.retainAll(c); } @Override public int size() { return elements.size(); } }; // FIXME must have permissions for requested priv. credentials for (Iterator it = iterator(); it.hasNext();) { SST o = it.next(); if (c.isAssignableFrom(o.getClass())) { s.add(c.cast(o)); } } return s; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); switch (setType) { case SET_Principal: permission = _PRINCIPALS; break; case SET_PrivCred: permission = _PRIVATE_CREDENTIALS; break; case SET_PubCred: permission = _PUBLIC_CREDENTIALS; break; default: throw new IllegalArgumentException(); } Iterator it = elements.iterator(); while (it.hasNext()) { verifyElement(it.next()); } } private void writeObject(ObjectOutputStream out) throws IOException { if (permission == _PRIVATE_CREDENTIALS) { // does security check for each private credential for (Iterator it = iterator(); it.hasNext();) { it.next(); } setType = SET_PrivCred; } else if (permission == _PRINCIPALS) { setType = SET_Principal; } else { setType = SET_PubCred; } out.defaultWriteObject(); } /** * Represents iterator for subject's secure set */ private class SecureIterator implements Iterator { protected Iterator iterator; protected SecureIterator(Iterator iterator) { this.iterator = iterator; } public boolean hasNext() { return iterator.hasNext(); } public SST next() { return iterator.next(); } /** * checks set state, and security permission to modify set before * removing current element */ public void remove() { checkState(); checkPermission(permission); iterator.remove(); } } } }