8296244: Alternate implementation of user-based authorization Subject APIs that doesn’t depend on Security Manager APIs

Reviewed-by: mullan
This commit is contained in:
Weijun Wang 2024-03-20 21:25:41 +00:00
parent 000f4d8d15
commit d32746ef4a
17 changed files with 591 additions and 141 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
* 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
@ -35,6 +35,7 @@ import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import jdk.internal.access.SharedSecrets;
import sun.security.util.ResourcesMgr;
/**
@ -90,6 +91,41 @@ import sun.security.util.ResourcesMgr;
* {@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 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
* <a href="../../../java/lang/SecurityManager.html#set-security-manager">allowed or disallowed</a></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 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>
*
* @since 1.4
* @see java.security.Principal
* @see java.security.DomainCombiner
@ -258,7 +294,9 @@ 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.
*
* <p> The {@code AccessControlContext} may contain many
* Subjects (from nested {@code doAs} calls).
@ -273,6 +311,9 @@ public final class Subject implements java.io.Serializable {
* if no {@code Subject} is associated
* with the provided {@code AccessControlContext}.
*
* @throws UnsupportedOperationException if a security manager is
* not allowed
*
* @throws SecurityException if a security manager is installed and the
* caller does not have an
* {@link AuthPermission#AuthPermission(String)
@ -302,36 +343,45 @@ public final class Subject implements java.io.Serializable {
Objects.requireNonNull(acc, ResourcesMgr.getString
("invalid.null.AccessControlContext.provided"));
// return the Subject from the DomainCombiner of the provided context
return AccessController.doPrivileged
(new java.security.PrivilegedAction<>() {
public Subject run() {
DomainCombiner dc = acc.getDomainCombiner();
if (!(dc instanceof SubjectDomainCombiner)) {
return null;
}
SubjectDomainCombiner sdc = (SubjectDomainCombiner)dc;
return sdc.getSubject();
}
});
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
throw new UnsupportedOperationException(
"getSubject is supported only if a security manager is allowed");
} else {
// return the Subject from the DomainCombiner of the provided context
return AccessController.doPrivileged
(new java.security.PrivilegedAction<>() {
public Subject run() {
DomainCombiner dc = acc.getDomainCombiner();
if (!(dc instanceof SubjectDomainCombiner)) {
return null;
}
SubjectDomainCombiner sdc = (SubjectDomainCombiner) dc;
return sdc.getSubject();
}
});
}
}
private static final ScopedValue<Subject> SCOPED_SUBJECT =
ScopedValue.newInstance();
/**
* Returns the current subject.
* <p>
* The current subject is installed by the {@link #callAs} method.
*
* <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()}.
*
* @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.
*
* @return the current subject, or {@code null} if a current subject is
* not installed or the current subject is set to {@code null}.
@ -340,23 +390,32 @@ public final class Subject implements java.io.Serializable {
*/
@SuppressWarnings("removal")
public static Subject current() {
return getSubject(AccessController.getContext());
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
return SCOPED_SUBJECT.orElse(null);
} else {
return getSubject(AccessController.getContext());
}
}
/**
* 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.
*
* @param subject the {@code Subject} that the specified {@code action}
* will run as. This parameter may be {@code null}.
@ -375,22 +434,31 @@ public final class Subject implements java.io.Serializable {
public static <T> T callAs(final Subject subject,
final Callable<T> action) throws CompletionException {
Objects.requireNonNull(action);
try {
PrivilegedExceptionAction<T> pa = () -> action.call();
@SuppressWarnings("removal")
var result = doAs(subject, pa);
return result;
} catch (PrivilegedActionException e) {
throw new CompletionException(e.getCause());
} catch (Exception e) {
throw new CompletionException(e);
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
try {
return ScopedValue.callWhere(SCOPED_SUBJECT, subject, action);
} catch (Exception e) {
throw new CompletionException(e);
}
} else {
try {
PrivilegedExceptionAction<T> pa = () -> action.call();
@SuppressWarnings("removal")
var result = doAs(subject, pa);
return result;
} catch (PrivilegedActionException e) {
throw new CompletionException(e.getCause());
} catch (Exception e) {
throw new CompletionException(e);
}
}
}
/**
* 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},
* and then instantiates a new {@code AccessControlContext}
@ -401,6 +469,10 @@ public final class Subject implements java.io.Serializable {
* 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.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
@ -444,20 +516,36 @@ public final class Subject implements java.io.Serializable {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
// set up the new Subject-based AccessControlContext
// for doPrivileged
final AccessControlContext currentAcc = AccessController.getContext();
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
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);
}
}
} else {
// set up the new Subject-based AccessControlContext
// for doPrivileged
final AccessControlContext currentAcc = AccessController.getContext();
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, currentAcc));
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, currentAcc));
}
}
/**
* 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},
* and then instantiates a new {@code AccessControlContext}
@ -468,6 +556,10 @@ public final class Subject implements java.io.Serializable {
* 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.
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
@ -517,19 +609,37 @@ public final class Subject implements java.io.Serializable {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
// set up the new Subject-based AccessControlContext for doPrivileged
final AccessControlContext currentAcc = AccessController.getContext();
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
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);
}
}
} else {
// set up the new Subject-based AccessControlContext for doPrivileged
final AccessControlContext currentAcc = AccessController.getContext();
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, currentAcc));
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, currentAcc));
}
}
/**
* 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
* {@code AccessControlContext}. If the provided
@ -537,6 +647,10 @@ public final class Subject implements java.io.Serializable {
* 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.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
@ -583,23 +697,39 @@ public final class Subject implements java.io.Serializable {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
// set up the new Subject-based AccessControlContext
// for doPrivileged
final AccessControlContext callerAcc =
(acc == null ?
new AccessControlContext(NULL_PD_ARRAY) :
acc);
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
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);
}
}
} else {
// set up the new Subject-based AccessControlContext
// for doPrivileged
final AccessControlContext callerAcc =
(acc == null ?
new AccessControlContext(NULL_PD_ARRAY) :
acc);
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, callerAcc));
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, callerAcc));
}
}
/**
* 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
* {@code AccessControlContext}. If the provided
@ -607,6 +737,10 @@ public final class Subject implements java.io.Serializable {
* 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.
*
* @param subject the {@code Subject} that the specified
* {@code action} will run as. This parameter
* may be {@code null}.
@ -659,16 +793,33 @@ public final class Subject implements java.io.Serializable {
Objects.requireNonNull(action,
ResourcesMgr.getString("invalid.null.action.provided"));
// set up the new Subject-based AccessControlContext for doPrivileged
final AccessControlContext callerAcc =
(acc == null ?
new AccessControlContext(NULL_PD_ARRAY) :
acc);
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
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);
}
}
} else {
// set up the new Subject-based AccessControlContext for doPrivileged
final AccessControlContext callerAcc =
(acc == null ?
new AccessControlContext(NULL_PD_ARRAY) :
acc);
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, callerAcc));
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, callerAcc));
}
}
@SuppressWarnings("removal")