blob: dd1e2333005a99622ba46e53ea84ababd89f758f [file]
/*
* Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.security.auth;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.security.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import sun.security.util.ResourcesMgr;
/**
* <p> A {@code Subject} represents a grouping of related information
* for a single entity, such as a person.
* Such information includes the Subject's identities as well as
* its security-related attributes
* (passwords and cryptographic keys, for example).
*
* <p> Subjects may potentially have multiple identities.
* Each identity is represented as a {@code Principal}
* within the {@code Subject}. Principals simply bind names to a
* {@code Subject}. For example, a {@code Subject} that happens
* to be a person, Alice, might have two Principals:
* one which binds "Alice Bar", the name on her driver license,
* to the {@code Subject}, and another which binds,
* "999-99-9999", the number on her student identification card,
* to the {@code Subject}. Both Principals refer to the same
* {@code Subject} even though each has a different name.
*
* <p> A {@code Subject} may also own security-related attributes,
* which are referred to as credentials.
* Sensitive credentials that require special protection, such as
* private cryptographic keys, are stored within a private credential
* {@code Set}. Credentials intended to be shared, such as
* public key certificates or Kerberos server tickets are stored
* within a public credential {@code Set}.
*
* <p> To retrieve all the Principals associated with a {@code Subject},
* invoke the {@code getPrincipals} method. To retrieve
* all the public or private credentials belonging to a {@code Subject},
* invoke the {@code getPublicCredentials} method or
* {@code getPrivateCredentials} method, respectively.
* To modify the returned {@code Set} of Principals and credentials,
* use the methods defined in the {@code Set} class.
* For example:
* <pre>
* Subject subject;
* Principal principal;
* Object credential;
*
* // add a Principal and credential to the Subject
* subject.getPrincipals().add(principal);
* subject.getPublicCredentials().add(credential);
* </pre>
*
* <p> This {@code Subject} class implements {@code Serializable}.
* While the Principals associated with the {@code Subject} are serialized,
* the credentials associated with the {@code Subject} are not.
* Note that the {@code java.security.Principal} class
* does not implement {@code Serializable}. Therefore, all concrete
* {@code Principal} implementations associated with Subjects
* must implement {@code Serializable}.
*
* <h2>Deprecated Methods and Replacements</h2>
*
* <p> The following methods in this class for user-based authorization
* that are dependent on Security Manager APIs are deprecated for removal:
* <ul>
* <li>{@link #getSubject(AccessControlContext)}
* <li>{@link #doAs(Subject, PrivilegedAction)}
* <li>{@link #doAs(Subject, PrivilegedExceptionAction)}
* <li>{@link #doAsPrivileged(Subject, PrivilegedAction, AccessControlContext)}
* <li>{@link #doAsPrivileged(Subject, PrivilegedExceptionAction, AccessControlContext)}
* </ul>
* Methods {@link #current()} and {@link #callAs(Subject, Callable)}
* are replacements for these methods, where {@code current} is equivalent to
* {@code getSubject(AccessController.getContext())} (as originally specified)
* and {@code callAs} is similar to {@code doAs} except that the
* input type and exceptions thrown are slightly different.
*
* <p> A {@code doAs} or {@code callAs} call
* binds a {@code Subject} object to the period of execution of an action,
* and the subject can be retrieved using the {@code current} method inside
* the action. This subject can be inherited by child threads if they are
* started and terminate within the execution of its parent thread using
* structured concurrency.
*
* @since 1.4
* @see java.security.Principal
* @see java.security.DomainCombiner
*/
public final class Subject implements java.io.Serializable {
@java.io.Serial
private static final long serialVersionUID = -8308522755600156056L;
/**
* A {@code Set} that provides a view of all of this
* Subject's Principals
*
* @serial Each element in this set is a
* {@code java.security.Principal}.
* The set is a {@code Subject.SecureSet}.
*/
@SuppressWarnings("serial") // Not statically typed as Serializable
Set<Principal> principals;
/**
* Sets that provide a view of all of this
* Subject's Credentials
*/
transient Set<Object> pubCredentials;
transient Set<Object> privCredentials;
/**
* Whether this Subject is read-only
*
* @serial
*/
private volatile boolean readOnly;
private static final int PRINCIPAL_SET = 1;
private static final int PUB_CREDENTIAL_SET = 2;
private static final int PRIV_CREDENTIAL_SET = 3;
private static final ProtectionDomain[] NULL_PD_ARRAY
= new ProtectionDomain[0];
/**
* Create an instance of a {@code Subject}
* with an empty {@code Set} of Principals and empty
* Sets of public and private credentials.
*
* <p> The newly constructed Sets check whether this {@code Subject}
* has been set read-only before permitting subsequent modifications.
* These Sets also prohibit null elements, and attempts to add, query,
* or remove a null element will result in a {@code NullPointerException}.
*/
public Subject() {
this.principals = Collections.synchronizedSet
(new SecureSet<>(this, PRINCIPAL_SET));
this.pubCredentials = Collections.synchronizedSet
(new SecureSet<>(this, PUB_CREDENTIAL_SET));
this.privCredentials = Collections.synchronizedSet
(new SecureSet<>(this, PRIV_CREDENTIAL_SET));
}
/**
* Create an instance of a {@code Subject} with
* Principals and credentials.
*
* <p> The Principals and credentials from the specified Sets
* are copied into newly constructed Sets.
* These newly created Sets check whether this {@code Subject}
* has been set read-only before permitting subsequent modifications.
* These Sets also prohibit null elements, and attempts to add, query,
* or remove a null element will result in a {@code NullPointerException}.
*
* @param readOnly true if the {@code Subject} is to be read-only,
* and false otherwise.
*
* @param principals the {@code Set} of Principals
* to be associated with this {@code Subject}.
*
* @param pubCredentials the {@code Set} of public credentials
* to be associated with this {@code Subject}.
*
* @param privCredentials the {@code Set} of private credentials
* to be associated with this {@code Subject}.
*
* @throws NullPointerException if the specified
* {@code principals}, {@code pubCredentials},
* or {@code privCredentials} are {@code null},
* or a null value exists within any of these three
* Sets.
*/
public Subject(boolean readOnly, Set<? extends Principal> principals,
Set<?> pubCredentials, Set<?> privCredentials) {
LinkedList<Principal> principalList
= collectionNullClean(principals);
LinkedList<Object> pubCredsList
= collectionNullClean(pubCredentials);
LinkedList<Object> privCredsList
= collectionNullClean(privCredentials);
this.principals = Collections.synchronizedSet(
new SecureSet<>(this, PRINCIPAL_SET, principalList));
this.pubCredentials = Collections.synchronizedSet(
new SecureSet<>(this, PUB_CREDENTIAL_SET, pubCredsList));
this.privCredentials = Collections.synchronizedSet(
new SecureSet<>(this, PRIV_CREDENTIAL_SET, privCredsList));
this.readOnly = readOnly;
}
/**
* Set this {@code Subject} to be read-only.
*
* <p> Modifications (additions and removals) to this Subject's
* {@code Principal} {@code Set} and
* credential Sets will be disallowed.
* The {@code destroy} operation on this Subject's credentials will
* still be permitted.
*
* <p> Subsequent attempts to modify the Subject's {@code Principal}
* and credential Sets will result in an
* {@code IllegalStateException} being thrown.
* Also, once a {@code Subject} is read-only,
* it can not be reset to being writable again.
*/
public void setReadOnly() {
this.readOnly = true;
}
/**
* Query whether this {@code Subject} is read-only.
*
* @return true if this {@code Subject} is read-only, false otherwise.
*/
public boolean isReadOnly() {
return this.readOnly;
}
/**
* Throws {@code UnsupportedOperationException}. A replacement API
* named {@link #current()} has been added which can be used to obtain
* the current subject.
*
* @param acc ignored
*
* @return n/a
*
* @throws UnsupportedOperationException always
*
* @deprecated This method used to get the subject associated with the
* provided {@link AccessControlContext}, which was only useful in
* conjunction with {@linkplain SecurityManager the Security Manager},
* which is no longer supported. This method has been changed to
* always throw {@code UnsupportedOperationException}. A replacement
* API named {@link #current()} has been added which can be used to
* obtain the current subject. There is no replacement for the
* Security Manager.
*
* @see #current()
*/
@SuppressWarnings("removal")
@Deprecated(since="17", forRemoval=true)
public static Subject getSubject(final AccessControlContext acc) {
throw new UnsupportedOperationException("getSubject is not supported");
}
private static final ScopedValue<Subject> SCOPED_SUBJECT =
ScopedValue.newInstance();
/**
* Returns the current subject.
*
* <p> The current subject is installed by the {@link #callAs} method.
* When {@code callAs(subject, action)} is called, {@code action} is
* executed with {@code subject} as its current subject which can be
* retrieved by this method. After {@code action} is finished, the current
* subject is reset to its previous value. The current
* subject is {@code null} before the first call of {@code callAs()}.
*
* <p> This method returns the
* {@code Subject} bound to the period of the execution of the current
* thread.
*
* @return the current subject, or {@code null} if a current subject is
* not installed or the current subject is set to {@code null}.
* @see #callAs(Subject, Callable)
* @since 18
*/
public static Subject current() {
return SCOPED_SUBJECT.isBound() ? SCOPED_SUBJECT.get() : null;
}
/**
* Executes a {@code Callable} with {@code subject} as the
* current subject.
*
* <p> This method launches {@code action} and binds {@code subject} to the
* period of its execution.
*
* @param subject the {@code Subject} that the specified {@code action}
* will run as. This parameter may be {@code null}.
* @param action the code to be run with {@code subject} as its current
* subject. Must not be {@code null}.
* @param <T> the type of value returned by the {@code call} method
* of {@code action}
* @return the value returned by the {@code call} method of {@code action}
* @throws NullPointerException if {@code action} is {@code null}
* @throws CompletionException if {@code action.call()} throws an exception.
* The cause of the {@code CompletionException} is set to the exception
* thrown by {@code action.call()}.
* @see #current()
* @since 18
*/
public static <T> T callAs(final Subject subject,
final Callable<T> action) throws CompletionException {
Objects.requireNonNull(action);
try {
return ScopedValue.where(SCOPED_SUBJECT, subject).call(action::call);
} catch (Exception e) {
throw new CompletionException(e);
}
}
/**
* Perform work as a particular {@code Subject}.
*
* <p> This method launches {@code action} and binds {@code subject} to the
* period of its execution.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
*
* @param <T> the type of the value returned by the PrivilegedAction's
* {@code run} method.
*
* @param action the code to be run as the specified
* {@code Subject}.
*
* @return the value returned by the PrivilegedAction's
* {@code run} method.
*
* @throws NullPointerException if the {@code PrivilegedAction}
* is {@code null}.
*
* @deprecated This method originally performed the specified
* {@code PrivilegedAction} with privileges enabled. Running the
* action with privileges enabled was only useful in conjunction
* with {@linkplain SecurityManager the Security Manager}, which is
* no longer supported. This method has been changed to launch the
* action as is and bind the subject to the period of its execution.
* A replacement API named {@link #callAs} has been added which can
* be used to perform the same work. There is no replacement for the
* Security Manager.
*
* @see #callAs(Subject, Callable)
*/
@Deprecated(since="18", forRemoval=true)
public static <T> T doAs(final Subject subject,
final java.security.PrivilegedAction<T> action) {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
try {
return callAs(subject, action::run);
} catch (CompletionException ce) {
var cause = ce.getCause();
if (cause instanceof RuntimeException re) {
throw re;
} else if (cause instanceof Error er) {
throw er;
} else {
throw new AssertionError(ce);
}
}
}
/**
* Perform work as a particular {@code Subject}.
*
* <p> This method launches {@code action} and binds {@code subject} to the
* period of its execution.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
*
* @param <T> the type of the value returned by the
* PrivilegedExceptionAction's {@code run} method.
*
* @param action the code to be run as the specified
* {@code Subject}.
*
* @return the value returned by the
* PrivilegedExceptionAction's {@code run} method.
*
* @throws PrivilegedActionException if the
* {@code PrivilegedExceptionAction.run}
* method throws a checked exception.
*
* @throws NullPointerException if the specified
* {@code PrivilegedExceptionAction} is
* {@code null}.
*
* @deprecated This method originally performed the specified
* {@code PrivilegedExceptionAction} with privileges enabled.
* Running the action with privileges enabled was only useful in
* conjunction with {@linkplain SecurityManager the Security Manager},
* which is no longer supported. This method has been changed to
* launch the action as is and bind the subject to the period of its
* execution. A replacement API named {@link #callAs} has been added
* which can be used to perform the same work. There is no
* replacement for the Security Manager.
*
* @see #callAs(Subject, Callable)
*/
@Deprecated(since="18", forRemoval=true)
public static <T> T doAs(final Subject subject,
final java.security.PrivilegedExceptionAction<T> action)
throws java.security.PrivilegedActionException {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
try {
return callAs(subject, action::run);
} catch (CompletionException ce) {
var cause = ce.getCause();
if (cause instanceof RuntimeException re) {
throw re;
} else if (cause instanceof Error er) {
throw er;
} else if (cause instanceof Exception e) {
throw new PrivilegedActionException(e);
} else {
throw new PrivilegedActionException(ce);
}
}
}
/**
* Perform work as a particular {@code Subject}.
*
* <p> This method launches {@code action} and binds {@code subject} to
* the period of its execution.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
*
* @param <T> the type of the value returned by the PrivilegedAction's
* {@code run} method.
*
* @param action the code to be run as the specified
* {@code Subject}.
*
* @param acc ignored
*
* @return the value returned by the PrivilegedAction's
* {@code run} method.
*
* @throws NullPointerException if the {@code PrivilegedAction}
* is {@code null}.
*
* @deprecated This method originally performed the specified
* {@code PrivilegedAction} with privileges enabled and restricted
* by the specified {@code AccessControlContext}. Running the
* action with privileges enabled was only useful in conjunction
* with {@linkplain SecurityManager the Security Manager}, which is
* no longer supported. This method has been changed to ignore the
* {@code AccessControlContext} and launch the action as is and bind
* the subject to the period of its execution. A replacement API
* named {@link #callAs} has been added which can be used to perform
* the same work. There is no replacement for the Security Manager.
*
* @see #callAs(Subject, Callable)
*/
@SuppressWarnings("removal")
@Deprecated(since="17", forRemoval=true)
public static <T> T doAsPrivileged(final Subject subject,
final java.security.PrivilegedAction<T> action,
final java.security.AccessControlContext acc) {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
try {
return callAs(subject, action::run);
} catch (CompletionException ce) {
var cause = ce.getCause();
if (cause instanceof RuntimeException re) {
throw re;
} else if (cause instanceof Error er) {
throw er;
} else {
throw new AssertionError(ce);
}
}
}
/**
* Perform work as a particular {@code Subject}.
*
* <p> This method launches {@code action} and binds {@code subject} to
* the period of its execution.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
*
* @param <T> the type of the value returned by the
* PrivilegedExceptionAction's {@code run} method.
*
* @param action the code to be run as the specified
* {@code Subject}.
*
* @param acc ignored
*
* @return the value returned by the
* PrivilegedExceptionAction's {@code run} method.
*
* @throws PrivilegedActionException if the
* {@code PrivilegedExceptionAction.run}
* method throws a checked exception.
*
* @throws NullPointerException if the specified
* {@code PrivilegedExceptionAction} is
* {@code null}.
*
* @deprecated This method originally performed the specified
* {@code PrivilegedExceptionAction} with privileges enabled and
* restricted by the specified {@code AccessControlContext}. Running
* the action with privileges enabled was only useful in conjunction
* with {@linkplain SecurityManager the Security Manager}, which is
* no longer supported. This method has been changed to ignore the
* {@code AccessControlContext} and launch the action as is and bind
* the subject to the period of its execution. A replacement API
* named {@link #callAs} has been added which can be used to perform
* the same work. There is no replacement for the Security Manager.
*
* @see #callAs(Subject, Callable)
*/
@SuppressWarnings("removal")
@Deprecated(since="17", forRemoval=true)
public static <T> T doAsPrivileged(final Subject subject,
final java.security.PrivilegedExceptionAction<T> action,
final java.security.AccessControlContext acc)
throws java.security.PrivilegedActionException {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
try {
return callAs(subject, action::run);
} catch (CompletionException ce) {
var cause = ce.getCause();
if (cause instanceof RuntimeException re) {
throw re;
} else if (cause instanceof Error er) {
throw er;
} else if (cause instanceof Exception e) {
throw new PrivilegedActionException(e);
} else {
throw new PrivilegedActionException(ce);
}
}
}
/**
* Return the {@code Set} of Principals associated with this
* {@code Subject}. Each {@code Principal} represents
* an identity for this {@code Subject}.
*
* <p> The returned {@code Set} is backed by this Subject's
* internal {@code Principal} {@code Set}. Any modification
* to the returned {@code Set} affects the internal
* {@code Principal} {@code Set} as well.
*
* @return the {@code Set} of Principals associated with this
* {@code Subject}.
*/
public Set<Principal> getPrincipals() {
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return principals;
}
/**
* Return a {@code Set} of Principals associated with this
* {@code Subject} that are instances or subclasses of the specified
* {@code Class}.
*
* <p> The returned {@code Set} is not backed by this Subject's
* internal {@code Principal} {@code Set}. A new
* {@code Set} is created and returned for each method invocation.
* Modifications to the returned {@code Set}
* will not affect the internal {@code Principal} {@code Set}.
*
* @param <T> the type of the class modeled by {@code c}
*
* @param c the returned {@code Set} of Principals will all be
* instances of this class.
*
* @return a {@code Set} of Principals that are instances of the
* specified {@code Class}.
*
* @throws NullPointerException if the specified {@code Class}
* is {@code null}.
*/
public <T extends Principal> Set<T> getPrincipals(Class<T> c) {
Objects.requireNonNull(c,
ResourcesMgr.getString("invalid.null.Class.provided"));
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return new ClassSet<>(PRINCIPAL_SET, c);
}
/**
* Return the {@code Set} of public credentials held by this
* {@code Subject}.
*
* <p> The returned {@code Set} is backed by this Subject's
* internal public Credential {@code Set}. Any modification
* to the returned {@code Set} affects the internal public
* Credential {@code Set} as well.
*
* @return a {@code Set} of public credentials held by this
* {@code Subject}.
*/
public Set<Object> getPublicCredentials() {
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return pubCredentials;
}
/**
* Return the {@code Set} of private credentials held by this
* {@code Subject}.
*
* <p> The returned {@code Set} is backed by this Subject's
* internal private Credential {@code Set}. Any modification
* to the returned {@code Set} affects the internal private
* Credential {@code Set} as well.
*
* @return a {@code Set} of private credentials held by this
* {@code Subject}.
*/
public Set<Object> getPrivateCredentials() {
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return privCredentials;
}
/**
* Return a {@code Set} of public credentials associated with this
* {@code Subject} that are instances or subclasses of the specified
* {@code Class}.
*
* <p> The returned {@code Set} is not backed by this Subject's
* internal public Credential {@code Set}. A new
* {@code Set} is created and returned for each method invocation.
* Modifications to the returned {@code Set}
* will not affect the internal public Credential {@code Set}.
*
* @param <T> the type of the class modeled by {@code c}
*
* @param c the returned {@code Set} of public credentials will all be
* instances of this class.
*
* @return a {@code Set} of public credentials that are instances
* of the specified {@code Class}.
*
* @throws NullPointerException if the specified {@code Class}
* is {@code null}.
*/
public <T> Set<T> getPublicCredentials(Class<T> c) {
Objects.requireNonNull(c,
ResourcesMgr.getString("invalid.null.Class.provided"));
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return new ClassSet<>(PUB_CREDENTIAL_SET, c);
}
/**
* Return a {@code Set} of private credentials associated with this
* {@code Subject} that are instances or subclasses of the specified
* {@code Class}.
*
* <p> The returned {@code Set} is not backed by this Subject's
* internal private Credential {@code Set}. A new
* {@code Set} is created and returned for each method invocation.
* Modifications to the returned {@code Set}
* will not affect the internal private Credential {@code Set}.
*
* @param <T> the type of the class modeled by {@code c}
*
* @param c the returned {@code Set} of private credentials will all be
* instances of this class.
*
* @return a {@code Set} of private credentials that are instances
* of the specified {@code Class}.
*
* @throws NullPointerException if the specified {@code Class}
* is {@code null}.
*/
public <T> Set<T> getPrivateCredentials(Class<T> c) {
Objects.requireNonNull(c,
ResourcesMgr.getString("invalid.null.Class.provided"));
// always return an empty Set instead of null
// so LoginModules can add to the Set if necessary
return new ClassSet<>(PRIV_CREDENTIAL_SET, c);
}
/**
* Compares the specified Object with this {@code Subject}
* for equality. Returns true if the given object is also a Subject
* and the two {@code Subject} instances are equivalent.
* More formally, two {@code Subject} instances are
* equal if their {@code Principal} and {@code Credential}
* Sets are equal.
*
* @param o Object to be compared for equality with this
* {@code Subject}.
*
* @return true if the specified Object is equal to this
* {@code Subject}.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof final Subject that) {
// check the principal and credential sets
Set<Principal> thatPrincipals;
synchronized(that.principals) {
// avoid deadlock from dual locks
thatPrincipals = new HashSet<>(that.principals);
}
if (!principals.equals(thatPrincipals)) {
return false;
}
Set<Object> thatPubCredentials;
synchronized(that.pubCredentials) {
// avoid deadlock from dual locks
thatPubCredentials = new HashSet<>(that.pubCredentials);
}
if (!pubCredentials.equals(thatPubCredentials)) {
return false;
}
Set<Object> thatPrivCredentials;
synchronized(that.privCredentials) {
// avoid deadlock from dual locks
thatPrivCredentials = new HashSet<>(that.privCredentials);
}
return privCredentials.equals(thatPrivCredentials);
}
return false;
}
/**
* Return the String representation of this {@code Subject}.
*
* @return the String representation of this {@code Subject}.
*/
@Override
public String toString() {
String s = ResourcesMgr.getString("Subject.");
String suffix = "";
synchronized(principals) {
for (Principal p : principals) {
suffix = suffix + ResourcesMgr.getString(".Principal.") +
p.toString() + ResourcesMgr.getString("NEWLINE");
}
}
synchronized(pubCredentials) {
for (Object o : pubCredentials) {
suffix = suffix +
ResourcesMgr.getString(".Public.Credential.") +
o.toString() + ResourcesMgr.getString("NEWLINE");
}
}
synchronized(privCredentials) {
Iterator<Object> pI = privCredentials.iterator();
while (pI.hasNext()) {
try {
Object o = pI.next();
suffix += ResourcesMgr.getString
(".Private.Credential.") +
o.toString() +
ResourcesMgr.getString("NEWLINE");
} catch (SecurityException se) {
suffix += ResourcesMgr.getString
(".Private.Credential.inaccessible.");
break;
}
}
}
return s + suffix;
}
/**
* {@return a hashcode for this {@code Subject}}
*/
@Override
public int hashCode() {
/*
* The hashcode is derived exclusive or-ing the
* hashcodes of this Subject's Principals and credentials.
*
* If a particular credential was destroyed
* ({@code credential.hashCode()} throws an
* {@code IllegalStateException}),
* the hashcode for that credential is derived via:
* {@code credential.getClass().toString().hashCode()}.
*/
int hashCode = 0;
synchronized(principals) {
for (Principal p : principals) {
hashCode ^= p.hashCode();
}
}
synchronized(pubCredentials) {
for (Object pubCredential : pubCredentials) {
hashCode ^= getCredHashCode(pubCredential);
}
}
return hashCode;
}
/**
* get a credential's hashcode
*/
private int getCredHashCode(Object o) {
try {
return o.hashCode();
} catch (IllegalStateException ise) {
return o.getClass().toString().hashCode();
}
}
/**
* Writes this object out to a stream (i.e., serializes it).
*
* @param oos the {@code ObjectOutputStream} to which data is written
* @throws IOException if an I/O error occurs
*/
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream oos)
throws java.io.IOException {
synchronized(principals) {
oos.defaultWriteObject();
}
}
/**
* Reads this object from a stream (i.e., deserializes it)
*
* @param s the {@code ObjectInputStream} from which data is read
* @throws IOException if an I/O error occurs
* @throws ClassNotFoundException if a serialized class cannot be loaded
*/
@SuppressWarnings("unchecked")
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = s.readFields();
readOnly = gf.get("readOnly", false);
Set<Principal> inputPrincs = (Set<Principal>)gf.get("principals", null);
Objects.requireNonNull(inputPrincs,
ResourcesMgr.getString("invalid.null.input.s."));
// Rewrap the principals into a SecureSet
try {
LinkedList<Principal> principalList = collectionNullClean(inputPrincs);
principals = Collections.synchronizedSet(new SecureSet<>
(this, PRINCIPAL_SET, principalList));
} catch (NullPointerException npe) {
// Sometimes people deserialize the principals set only.
// Subject is not accessible, so just don't fail.
principals = Collections.synchronizedSet
(new SecureSet<>(this, PRINCIPAL_SET));
}
// The Credential {@code Set} is not serialized, but we do not
// want the default deserialization routine to set it to null.
this.pubCredentials = Collections.synchronizedSet
(new SecureSet<>(this, PUB_CREDENTIAL_SET));
this.privCredentials = Collections.synchronizedSet
(new SecureSet<>(this, PRIV_CREDENTIAL_SET));
}
/**
* Tests for null-clean collections (both non-null reference and
* no null elements)
*
* @param coll A {@code Collection} to be tested for null references
*
* @throws NullPointerException if the specified collection is either
* {@code null} or contains a {@code null} element
*/
private static <E> LinkedList<E> collectionNullClean(
Collection<? extends E> coll) {
Objects.requireNonNull(coll,
ResourcesMgr.getString("invalid.null.input.s."));
LinkedList<E> output = new LinkedList<>();
for (E e : coll) {
output.add(Objects.requireNonNull(e,
ResourcesMgr.getString("invalid.null.input.s.")));
}
return output;
}
/**
* Prevent modifications unless caller has permission.
*
* @serial include
*/
private static class SecureSet<E>
implements Set<E>, java.io.Serializable {
@java.io.Serial
private static final long serialVersionUID = 7911754171111800359L;
/**
* @serialField this$0 Subject The outer Subject instance.
* @serialField elements LinkedList The elements in this set.
*/
@java.io.Serial
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("this$0", Subject.class),
new ObjectStreamField("elements", LinkedList.class),
new ObjectStreamField("which", int.class)
};
Subject subject;
LinkedList<E> elements;
/**
* @serial An integer identifying the type of objects contained
* in this set. If {@code which == 1},
* this is a Principal set and all the elements are
* of type {@code java.security.Principal}.
* If {@code which == 2}, this is a public credential
* set and all the elements are of type {@code Object}.
* If {@code which == 3}, this is a private credential
* set and all the elements are of type {@code Object}.
*/
private int which;
SecureSet(Subject subject, int which) {
this.subject = subject;
this.which = which;
this.elements = new LinkedList<>();
}
SecureSet(Subject subject, int which, LinkedList<E> list) {
this.subject = subject;
this.which = which;
this.elements = list;
}
public int size() {
return elements.size();
}
public Iterator<E> iterator() {
final LinkedList<E> list = elements;
return new Iterator<>() {
final ListIterator<E> i = list.listIterator(0);
public boolean hasNext() {
return i.hasNext();
}
public E next() {
return i.next();
}
public void remove() {
if (subject.isReadOnly()) {
throw new IllegalStateException(ResourcesMgr.getString
("Subject.is.read.only"));
}
i.remove();
}
};
}
public boolean add(E o) {
Objects.requireNonNull(o,
ResourcesMgr.getString("invalid.null.input.s."));
if (subject.isReadOnly()) {
throw new IllegalStateException
(ResourcesMgr.getString("Subject.is.read.only"));
}
switch (which) {
case Subject.PRINCIPAL_SET:
if (!(o instanceof Principal)) {
throw new SecurityException(ResourcesMgr.getString
("attempting.to.add.an.object.which.is.not.an.instance.of.java.security.Principal.to.a.Subject.s.Principal.Set"));
}
break;
default:
// ok to add Objects of any kind to credential sets
break;
}
// check for duplicates
if (!elements.contains(o))
return elements.add(o);
else {
return false;
}
}
public boolean remove(Object o) {
Objects.requireNonNull(o,
ResourcesMgr.getString("invalid.null.input.s."));
final Iterator<E> e = iterator();
while (e.hasNext()) {
E next = e.next();
if (next.equals(o)) {
e.remove();
return true;
}
}
return false;
}
public boolean contains(Object o) {
Objects.requireNonNull(o,
ResourcesMgr.getString("invalid.null.input.s."));
final Iterator<E> e = iterator();
while (e.hasNext()) {
E next = e.next();
if (next.equals(o)) {
return true;
}
}
return false;
}
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
c = collectionNullClean(c);
for (E item : c) {
result |= this.add(item);
}
return result;
}
public boolean removeAll(Collection<?> c) {
c = collectionNullClean(c);
boolean modified = false;
final Iterator<E> e = iterator();
while (e.hasNext()) {
E next = e.next();
for (Object o : c) {
if (next.equals(o)) {
e.remove();
modified = true;
break;
}
}
}
return modified;
}
public boolean containsAll(Collection<?> c) {
c = collectionNullClean(c);
for (Object item : c) {
if (!this.contains(item)) {
return false;
}
}
return true;
}
public boolean retainAll(Collection<?> c) {
c = collectionNullClean(c);
boolean modified = false;
final Iterator<E> e = iterator();
while (e.hasNext()) {
E next = e.next();
if (c.contains(next) == false) {
e.remove();
modified = true;
}
}
return modified;
}
public void clear() {
final Iterator<E> e = iterator();
while (e.hasNext()) {
E next = e.next();
e.remove();
}
}
public boolean isEmpty() {
return elements.isEmpty();
}
public Object[] toArray() {
return elements.toArray();
}
public <T> T[] toArray(T[] a) {
return elements.toArray(a);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Set)) {
return false;
}
Collection<?> c = (Collection<?>) o;
if (c.size() != size()) {
return false;
}
try {
return containsAll(c);
} catch (ClassCastException | NullPointerException unused) {
return false;
}
}
@Override
public int hashCode() {
int h = 0;
for (E obj : this) {
h += Objects.hashCode(obj);
}
return h;
}
/**
* Writes this object out to a stream (i.e., serializes it).
*
* @param oos the {@code ObjectOutputStream} to which data is written
* @throws IOException if an I/O error occurs
*/
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream oos)
throws java.io.IOException {
ObjectOutputStream.PutField fields = oos.putFields();
fields.put("this$0", subject);
fields.put("elements", elements);
fields.put("which", which);
oos.writeFields();
}
/**
* Restores the state of this object from the stream.
*
* @param ois the {@code ObjectInputStream} from which data is read
* @throws IOException if an I/O error occurs
* @throws ClassNotFoundException if a serialized class cannot be loaded
*/
@SuppressWarnings("unchecked")
@java.io.Serial
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException
{
ObjectInputStream.GetField fields = ois.readFields();
subject = (Subject) fields.get("this$0", null);
which = fields.get("which", 0);
LinkedList<E> tmp = (LinkedList<E>) fields.get("elements", null);
elements = Subject.collectionNullClean(tmp);
}
}
/**
* This class implements a {@code Set} which returns only
* members that are an instance of a specified Class.
*/
private class ClassSet<T> extends AbstractSet<T> {
private final int which;
private final Class<T> c;
private final Set<T> set;
ClassSet(int which, Class<T> c) {
this.which = which;
this.c = c;
set = new HashSet<>();
switch (which) {
case Subject.PRINCIPAL_SET:
synchronized(principals) { populateSet(); }
break;
case Subject.PUB_CREDENTIAL_SET:
synchronized(pubCredentials) { populateSet(); }
break;
default:
synchronized(privCredentials) { populateSet(); }
break;
}
}
@SuppressWarnings("unchecked")
private void populateSet() {
final Iterator<?> iterator;
switch(which) {
case Subject.PRINCIPAL_SET:
iterator = Subject.this.principals.iterator();
break;
case Subject.PUB_CREDENTIAL_SET:
iterator = Subject.this.pubCredentials.iterator();
break;
default:
iterator = Subject.this.privCredentials.iterator();
break;
}
while (iterator.hasNext()) {
Object next = iterator.next();
if (c.isAssignableFrom(next.getClass())) {
set.add((T)next);
}
}
}
@Override
public int size() {
return set.size();
}
@Override
public Iterator<T> iterator() {
return set.iterator();
}
@Override
public boolean add(T o) {
if (!c.isAssignableFrom(o.getClass())) {
MessageFormat form = new MessageFormat(ResourcesMgr.getString
("attempting.to.add.an.object.which.is.not.an.instance.of.class"));
Object[] source = {c.toString()};
throw new SecurityException(form.format(source));
}
return set.add(o);
}
}
}