Summary
-------
Provide a different implementation of the `Subject` API when the Security Manager is not allowed.
Problem
-------
The Security Manager API was deprecated for removal in JDK 17 and JAAS still stores a subject in an `AccessControlContext` object. `AccessControlContext` is one of the Security Manager APIs that is deprecated for removal.
In JDK 18, we added replacement APIs (`Subject.callAs` and `Subject.current`) for `Subject.doAs` and `Subject.getSubject` which had no API dependencies on the deprecated Security Manager APIs. However, the implementations of these replacement APIs are currently wrappers which call the deprecated APIs. It was always our intention that this was a temporary measure and that we needed to provide an alternate mechanism before we further degraded the Security Manager APIs.
Thus, in order for these JAAS APIs to continue working after the Security Manager implementation is removed and the deprecated APIs are degraded, we need to provide a different mechanism to pass the subject to the action. However, it is also important to preserve compatibility with the deprecated APIs _until_ they are degraded in a future release.
Solution
--------
We are proposing a new alternate `Subject` implementation that will store the subject in a scoped value object when `Subject::callAs` is called. When the action, or callable, is executed, the subject can be subsequently retrieved using the `Subject::current` method.
This new implementation will _only_ be used when a Security Manager is not allowed, which means it is not set and not allowed to be set dynamically. Applications using JAAS with a Security Manager can continue to use the existing `Subject` APIs that use `AccessControlContext` and will experience no compatibility issues. An application using JAAS when a Security Manager is allowed, which means it is either already set or allowed to be set dynamically, will by default use the new implementation but can revert to using the existing APIs that use `AccessControlContext` by adding
`-Djava.security.manager=allow` to the command line.
When a Security Manager is not allowed the existing `Subject::doAs` methods will also behave similarly to `callAs` and store the subject in a scoped value object. This ensures that an application calling `doAs` can interoperate correctly with a library calling `current`. However, the existing `Subject::getSubject` method will throw an `UnsupportedOperationException` since it takes an `AccessControlContext` argument.
This solution provides a good balance between compatibility and forward migration. It allows applications to use the new implementation but also allows them to revert to the previous implementation if they encounter some compatibility issues or need more time to migrate to the replacement APIs. It also gives them an advance warning that some of the deprecated APIs (`Subject::getSubject` specifically) will no longer be functional without Security Manager support.
Specification
-------------
Changes are made to the `Subject` class and some of its methods. The methods work differently depending on if a Security Manager is allowed.
The specification does not directly reference the `ScopedValue` type (still in preview) but the description of its thread inheritance behavior matches the feature of `ScopedValue`.
/**
* ....
+ * <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 mostly equivalent to {@code getSubject(AccessController.getContext())}
+ * and {@code callAs} is similar to {@code doAs} except that the
+ * input type and exceptions thrown are slightly different.
+ *
+ * <p><b><a id="sm-allowed">These methods behave differently depending on
+ * whether a security manager is allowed or disallowed</a></b>:
+ * <ul>
+ * <li>If a security manager is allowed, which means it is either already set
+ * or allowed to be set dynamically, a {@code Subject} object is associated
+ * with an {@code AccessControlContext} through a {@code doAs} or
+ * {@code callAs} call, and the subject can then be retrieved using the
+ * {@code getSubject(AccessControlContext)} or {@code current} method.
+ * <li>If a security manager is not allowed, which means it
+ * {@linkplain System#setSecurityManager is not set and not allowed to be set
+ * dynamically}, 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.
+ * </ul>
+ *
* ...
*/
public final class Subject implements java.io.Serializable {
/**
* Get the {@code Subject} associated with the provided
- * {@code AccessControlContext}.
+ * {@code AccessControlContext}. This method is intended to be used with
+ * a security manager. It throws an {@code UnsupportedOperationException}
+ * if a security manager is not allowed.
*
* ...
*
+ * @throws UnsupportedOperationException if a security manager is
+ * not allowed
+ *
* ...
*/
public static Subject getSubject(final AccessControlContext acc) ...
/**
* Returns the current subject.
* ...
*
- * @implNote
- * This method returns the same value as
- * {@code Subject.getSubject(AccessController.getContext())}. This
- * preserves compatibility with code that may still be calling {@code doAs}
- * which installs the subject in an {@code AccessControlContext}. This
- * behavior is subject to change in a future version.
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>, this
+ * method is equivalent to calling {@link #getSubject} with the current
+ * {@code AccessControlContext}.
+ *
+ * <p> If a security manager is not allowed, this method returns the
+ * {@code Subject} bound to the period of the execution of the current
+ * thread.
*
* ...
*/
public static Subject current() ...
/**
* Executes a {@code Callable} with {@code subject} as the
* current subject.
*
- * @implNote
- * This method calls {@link #doAs(Subject, PrivilegedExceptionAction)
- * Subject.doAs(subject, altAction)} which stores the subject in
- * a new {@code AccessControlContext}, where {@code altAction.run()}
- * is equivalent to {@code action.call()} and the exception thrown is
- * modified to match the specification of this method. This preserves
- * compatibility with code that may still be calling
- * {@code getSubject(AccessControlContext)} which retrieves the subject
- * from an {@code AccessControlContext}. This behavior is subject
- * to change in a future version.
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>,
+ * this method first retrieves the current Thread's
+ * {@code AccessControlContext} via
+ * {@code AccessController.getContext},
+ * and then instantiates a new {@code AccessControlContext}
+ * using the retrieved context along with a new
+ * {@code SubjectDomainCombiner} (constructed using
+ * the provided {@code Subject}).
+ * Finally, this method invokes {@code AccessController.doPrivileged},
+ * passing it the provided {@code PrivilegedAction},
+ * as well as the newly constructed {@code AccessControlContext}.
+ *
+ * <p> If a security manager is not allowed,
+ * this method launches {@code action} and binds {@code subject} to the
+ * period of its execution.
*...
*/
public static <T> T callAs(final Subject subject,
final Callable<T> action) throws CompletionException ...
/**
* Perform work as a particular {@code Subject}.
*
- * <p> This method first retrieves the current Thread's
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>,
+ * this method first retrieves the current Thread's
* {@code AccessControlContext} via
* {@code AccessController.getContext},
* ...
* passing it the provided {@code PrivilegedAction},
* as well as the newly constructed {@code AccessControlContext}.
*
+ * <p> If a security manager is not allowed,
+ * this method launches {@code action} and binds {@code subject} to the
+ * period of its execution.
+ *
*...
*/
public static <T> T doAs(final Subject subject,
final java.security.PrivilegedAction<T> action) ...
/**
* Perform work as a particular {@code Subject}.
*
- * <p> This method first retrieves the current Thread's
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>,
+ * this method first retrieves the current Thread's
* {@code AccessControlContext} via
* {@code AccessController.getContext},
* ...
* passing it the provided {@code PrivilegedExceptionAction},
* as well as the newly constructed {@code AccessControlContext}.
*
+ * <p> If a security manager is not allowed,
+ * this method launches {@code action} and binds {@code subject} to the
+ * period of its execution.
+
*...
*/
public static <T> T doAs(final Subject subject,
final java.security.PrivilegedExceptionAction<T> action)
throws java.security.PrivilegedActionException ...
/**
* Perform privileged work as a particular {@code Subject}.
*
- * <p> This method behaves exactly as {@code Subject.doAs},
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>,
+ * this method behaves exactly as {@code Subject.doAs},
* except that instead of retrieving the current Thread's
* {@code AccessControlContext}, it uses the provided
* ...
* this method instantiates a new {@code AccessControlContext}
* with an empty collection of ProtectionDomains.
*
+ * <p> If a security manager is not allowed,
+ * this method ignores the {@code acc} argument, launches {@code action},
+ * and binds {@code subject} to the period of its execution.
+ *
* ...
*/
public static <T> T doAsPrivileged(final Subject subject,
final java.security.PrivilegedAction<T> action,
final java.security.AccessControlContext acc) ...
/**
* Perform privileged work as a particular {@code Subject}.
*
- * <p> This method behaves exactly as {@code Subject.doAs},
+ * <p> If a security manager is <a href=#sm-allowed>allowed</a>,
+ * this method behaves exactly as {@code Subject.doAs},
* except that instead of retrieving the current Thread's
* {@code AccessControlContext}, it uses the provided
* ...
* this method instantiates a new {@code AccessControlContext}
* with an empty collection of ProtectionDomains.
*
+ * <p> If a security manager is not allowed,
+ * this method ignores the {@code acc} argument, launches {@code action},
+ * and binds {@code subject} to the period of its execution.
+ *
* ...
*/
public static <T> T doAsPrivileged(final Subject subject,
final java.security.PrivilegedExceptionAction<T> action,
final java.security.AccessControlContext acc)
throws java.security.PrivilegedActionException ...