diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 39ecb2a6436ca..5544165588aa2 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2687,6 +2687,11 @@ public void copyToSegmentRaw(String string, MemorySegment segment, long offset) public boolean bytesCompatible(String string, Charset charset) { return string.bytesCompatible(charset); } + + @Override + public boolean allowSecurityManager() { + return System.allowSecurityManager(); + } }); } } diff --git a/src/java.base/share/classes/javax/security/auth/Subject.java b/src/java.base/share/classes/javax/security/auth/Subject.java index 60e6d5fe30ea2..a16cb2119d5bd 100644 --- a/src/java.base/share/classes/javax/security/auth/Subject.java +++ b/src/java.base/share/classes/javax/security/auth/Subject.java @@ -35,6 +35,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletionException; +import jdk.internal.access.SharedSecrets; import sun.security.util.ResourcesMgr; /** @@ -90,6 +91,40 @@ * {@code Principal} implementations associated with Subjects * must implement {@code Serializable}. * + *

Deprecated Methods and Replacements

+ * + *

The following methods in this class for user-based authorization + * that are dependent on Security Manager APIs are deprecated for removal: + *

+ * 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. + * + *

These methods behave differently depending on + * whether a security manager is allowed or disallowed: + *

+ * * @since 1.4 * @see java.security.Principal * @see java.security.DomainCombiner @@ -258,7 +293,9 @@ public boolean isReadOnly() { /** * Get the {@code Subject} associated with the provided - * {@code AccessControlContext}. + * {@code AccessControlContext}. When a security manager is + * not allowed, this method is not supported + * and throws an {@code UnsupportedOperationException}. * *

The {@code AccessControlContext} may contain many * Subjects (from nested {@code doAs} calls). @@ -273,6 +310,9 @@ public boolean isReadOnly() { * 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,20 +342,27 @@ public static Subject getSubject(final AccessControlContext acc) { 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 SCOPED_SUBJECT = + ScopedValue.newInstance(); + /** * Returns the current subject. *

@@ -325,13 +372,11 @@ public Subject run() { * 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. + *

+ * If a security manager is not allowed, the + * current subject binds to the period of the execution of the current + * thread. Otherwise, it is associated with the current + * {@code AccessControlContext}. * * @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 +385,30 @@ public Subject run() { */ @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. + *

If a security manager is not allowed, + * this method launches {@code action} and binds {@code subject} to the + * period of its execution. + *

Otherwise, 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}. * * @param subject the {@code Subject} that the specified {@code action} * will run as. This parameter may be {@code null}. @@ -375,22 +427,33 @@ public static Subject current() { public static T callAs(final Subject subject, final Callable action) throws CompletionException { Objects.requireNonNull(action); - try { - PrivilegedExceptionAction 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 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}. * - *

This method first retrieves the current Thread's + *

If a security manager is not allowed, + * this method launches {@code action} and binds {@code subject} to the + * period of its execution. + *

Otherwise, this method first retrieves the current Thread's * {@code AccessControlContext} via * {@code AccessController.getContext}, * and then instantiates a new {@code AccessControlContext} @@ -444,20 +507,38 @@ public static T doAs(final Subject subject, Objects.requireNonNull(action, ResourcesMgr.getString("invalid.null.action.provided")); - // 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)); + 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)); + } } /** * Perform work as a particular {@code Subject}. * - *

This method first retrieves the current Thread's + *

If a security manager is not allowed, + * this method launches {@code action} and binds {@code subject} to the + * period of its execution. + *

Otherwise, this method first retrieves the current Thread's * {@code AccessControlContext} via * {@code AccessController.getContext}, * and then instantiates a new {@code AccessControlContext} @@ -517,19 +598,39 @@ public static T doAs(final Subject subject, Objects.requireNonNull(action, ResourcesMgr.getString("invalid.null.action.provided")); - // 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)); + 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)); + } } /** * Perform privileged work as a particular {@code Subject}. * - *

This method behaves exactly as {@code Subject.doAs}, + *

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. + *

Otherwise, 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 @@ -583,23 +684,41 @@ public static T doAsPrivileged(final Subject subject, 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); - - // call doPrivileged and push this new context on the stack - return java.security.AccessController.doPrivileged - (action, - createContext(subject, callerAcc)); + 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)); + } } /** * Perform privileged work as a particular {@code Subject}. * - *

This method behaves exactly as {@code Subject.doAs}, + *

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. + *

Otherwise, 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 @@ -659,16 +778,33 @@ public static T doAsPrivileged(final Subject subject, 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); - - // call doPrivileged and push this new context on the stack - return java.security.AccessController.doPrivileged - (action, - createContext(subject, callerAcc)); + 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)); + } } @SuppressWarnings("removal") diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 93815009a543f..669db761497b4 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -586,4 +586,10 @@ StackWalker newStackWalkerInstance(Set options, * Are the string bytes compatible with the given charset? */ boolean bytesCompatible(String string, Charset charset); + + /** + * Is a security manager already set or allowed to be set + * (using -Djava.security.manager=allow)? + */ + boolean allowSecurityManager(); } diff --git a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java index e04a375c998b0..8a310725abde7 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java @@ -346,7 +346,7 @@ public void terminate() { @SuppressWarnings("removal") private Subject getSubject() { - return Subject.getSubject(AccessController.getContext()); + return Subject.current(); } private void checkState() throws IOException { diff --git a/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java b/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java index d694d9cce31dd..33dcd75e53006 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java @@ -27,7 +27,6 @@ import java.io.FileInputStream; import java.io.IOException; -import java.security.AccessControlContext; import java.security.AccessController; import java.security.Principal; import java.security.PrivilegedAction; @@ -301,13 +300,11 @@ private static Properties propertiesFromFile(String fname) } private synchronized void checkAccess(AccessType requiredAccess, String arg) { - @SuppressWarnings("removal") - final AccessControlContext acc = AccessController.getContext(); @SuppressWarnings("removal") final Subject s = AccessController.doPrivileged(new PrivilegedAction<>() { public Subject run() { - return Subject.getSubject(acc); + return Subject.current(); } }); if (s == null) return; /* security has not been enabled */ diff --git a/test/jdk/javax/management/monitor/ThreadPoolAccTest.java b/test/jdk/javax/management/monitor/ThreadPoolAccTest.java index 8218a58b4109e..4d6686f681ac8 100644 --- a/test/jdk/javax/management/monitor/ThreadPoolAccTest.java +++ b/test/jdk/javax/management/monitor/ThreadPoolAccTest.java @@ -33,7 +33,6 @@ * @run main ThreadPoolAccTest */ -import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Date; import java.util.Set; @@ -67,7 +66,7 @@ public String getString() { return ""; } private void setPrincipal() { - Subject subject = Subject.getSubject(AccessController.getContext()); + Subject subject = Subject.current(); Set principals = subject.getPrincipals(JMXPrincipal.class); principal = principals.iterator().next().getName(); } diff --git a/test/jdk/javax/management/remote/mandatory/passwordAuthenticator/SimpleStandard.java b/test/jdk/javax/management/remote/mandatory/passwordAuthenticator/SimpleStandard.java index bc503b3e29169..7b7376a12ca04 100644 --- a/test/jdk/javax/management/remote/mandatory/passwordAuthenticator/SimpleStandard.java +++ b/test/jdk/javax/management/remote/mandatory/passwordAuthenticator/SimpleStandard.java @@ -40,8 +40,6 @@ * - the "getNbResets()" method. */ -import java.security.AccessControlContext; -import java.security.AccessController; import java.security.Principal; import java.util.Set; import javax.management.AttributeChangeNotification; @@ -152,8 +150,7 @@ public int getNbResets() { * type JMXPrincipal and refers to the "monitorRole" identity. */ private void checkSubject() { - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); Set principals = subject.getPrincipals(); Principal principal = (Principal) principals.iterator().next(); if (!(principal instanceof JMXPrincipal)) diff --git a/test/jdk/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java b/test/jdk/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java index 72415a33cc51b..6b77abc248728 100644 --- a/test/jdk/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java +++ b/test/jdk/javax/management/remote/mandatory/subjectDelegation/SimpleStandard.java @@ -40,8 +40,6 @@ * - the "getNbResets()" method. */ -import java.security.AccessControlContext; -import java.security.AccessController; import java.security.Principal; import java.util.Set; import javax.management.AttributeChangeNotification; @@ -150,8 +148,7 @@ public int getNbResets() { * type JMXPrincipal and refers to the principalName identity. */ private void checkSubject(String op) { - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); Set principals = subject.getPrincipals(); Principal principal = (Principal) principals.iterator().next(); if (!(principal instanceof JMXPrincipal)) diff --git a/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java new file mode 100755 index 0000000000000..6681bb8481f89 --- /dev/null +++ b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 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. + * + * 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. + */ + +/* + * @test + * @bug 8296244 + * @enablePreview + * @summary Implement Subject.current and Subject.callAs using scoped values + * @run main/othervm -Djava.security.manager=allow CallAsWithScopedValue false + * @run main/othervm -Djava.security.manager=disallow CallAsWithScopedValue true + */ +import com.sun.security.auth.UserPrincipal; + +import javax.security.auth.Subject; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.StructuredTaskScope; + +public class CallAsWithScopedValue { + + private static Map results = new ConcurrentHashMap(); + + public static void main(String[] args) throws Exception { + + boolean usv = Boolean.parseBoolean(args[0]); + + Subject subject = new Subject(); + subject.getPrincipals().add(new UserPrincipal("Duke")); + + // Always observable in the same thread + Subject.callAs(subject, () -> check(0, Subject.current(), "Duke")); + + // Observable in the same thread in ACC mode, but not in the SV mode + Subject.callAs(subject, () -> { + Thread.ofPlatform().start(() -> check(1, Subject.current(), usv ? null : "Duke")).join(); + return null; + }); + + // Never observable in a new virtual thread + Subject.callAs(subject, () -> { + Thread.ofVirtual().start(() -> check(2, Subject.current(), null)).join(); + return null; + }); + + // Observable in structured concurrency in SV mode, but not in ACC mode + Subject.callAs(subject, () -> { + try (var scope = new StructuredTaskScope<>()) { + scope.fork(() -> check(3, Subject.current(), usv ? "Duke" : null)); + scope.join(); + } + return null; + }); + + // Suggested way to pass the current subject into arbitrary + // threads. Grab one using current() and explicitly pass it + // into the new thread. + Subject.callAs(subject, () -> { + Subject current = Subject.current(); + Thread.ofPlatform().start(() -> { + Subject.callAs(current, () -> check(4, Subject.current(), "Duke")); + }).join(); + return null; + }); + + if (results.size() != 5 || results.containsValue(false)) { + System.out.println(results); + throw new RuntimeException("Failed"); + } + } + + static String check(int type, Subject current, String expected) { + String actual; + if (current == null) { + actual = null; + } else { + var set = current.getPrincipals(UserPrincipal.class); + actual = set.isEmpty() + ? null + : set.iterator().next().getName(); + } + results.put(type, Objects.equals(actual, expected)); + return actual; + } +} diff --git a/test/jdk/javax/security/auth/Subject/Compat.java b/test/jdk/javax/security/auth/Subject/Compat.java new file mode 100755 index 0000000000000..ccd203fd2dfc3 --- /dev/null +++ b/test/jdk/javax/security/auth/Subject/Compat.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2021, 2022, 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. + * + * 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. + */ +import com.sun.security.auth.UserPrincipal; + +import javax.security.auth.Subject; +import javax.security.auth.SubjectDomainCombiner; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.Objects; + +/* + * @test + * @run main/othervm -Djava.security.manager=allow Compat + */ +public class Compat { + +// static PrivilegedAction action +// = () -> AccessController.getContext(); + + static PrivilegedExceptionAction action + = () -> AccessController.getContext(); + + static boolean failed = false; + + public static void main(String[] args) throws Exception { + main0(null); + var t = new Thread(() -> { + try { + main0(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + t.start(); + t.join(); + } + public static void main0(String[] args) throws Exception { + System.out.println(">>> bare run"); + run(null); + System.out.println(">>> run inside"); + Subject subject = makeSubject("three"); + Subject.doAs(subject, (PrivilegedExceptionAction) + () -> run("three")); + if (failed) { + throw new RuntimeException(); + } + } + + public static Void run(String from) throws Exception { + Subject subject = makeSubject("one"); + var a1 = Subject.doAs(subject, action); + Subject subject2 = makeSubject("two"); + var a2 = Subject.doAs(subject2, action); + + test("from ether", AccessController.getContext(), from); + test("from a1", a1, "one"); + test("from a2", a2, "two"); + + var a3 = Subject.doAsPrivileged(subject, action, a1); + test("doAsPriv with one and a1", a3, "one"); + + var a4 = Subject.doAsPrivileged(subject, action, a2); + test("doAsPriv with one and a2", a4, "one"); + + var a5 = Subject.doAsPrivileged(null, action, a2); + test("doAsPriv with null and a2", a5, null); + + var a6 = Subject.doAs(null, action); + test("doAsPriv with null and this", a6, null); + + var ax = new AccessControlContext(a2, new SubjectDomainCombiner(subject)); + test("a2 plus subject", ax, "one"); + + ax = AccessController.doPrivileged(action, a2); + test("doPriv on a2", ax, "two"); + + ax = AccessController.doPrivilegedWithCombiner(action); + test("doPrivWC", ax, from == null ? null : from); + + ax = AccessController.doPrivilegedWithCombiner(action, a2); + test("doPrivWC on a2", ax, from == null ? "two" : from); + return null; + } + + static Subject makeSubject(String name) { + Subject subject = new Subject(); + subject.getPrincipals().add(new UserPrincipal(name)); + return subject; + } + + static String getSubject(AccessControlContext acc) { + var subj = Subject.getSubject(acc); + if (subj == null) return null; + var princ = subj.getPrincipals(UserPrincipal.class); + return (princ == null || princ.isEmpty()) + ? null + : princ.iterator().next().getName(); + } + + static void test(String label, AccessControlContext acc, String expected) { + var actual = getSubject(acc); + System.out.println(label + ": " + actual); + if (!Objects.equals(actual, expected)) { + System.out.println(" Expect " + expected + ", but see " + actual); + failed = true; + } + } +} diff --git a/test/jdk/javax/security/auth/Subject/CurrentSubject.java b/test/jdk/javax/security/auth/Subject/CurrentSubject.java index 67e19012d1f27..c1b0b4ffd301b 100644 --- a/test/jdk/javax/security/auth/Subject/CurrentSubject.java +++ b/test/jdk/javax/security/auth/Subject/CurrentSubject.java @@ -22,28 +22,24 @@ */ import javax.security.auth.Subject; -import java.security.AccessController; import java.security.Principal; import java.security.PrivilegedAction; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; +import java.security.PrivilegedExceptionAction; /* * @test * @bug 8267108 * @summary confirm current subject specification - * @run main/othervm CurrentSubject + * @run main/othervm -Djava.security.manager=allow CurrentSubject + * @run main/othervm -Djava.security.manager=disallow CurrentSubject */ public class CurrentSubject { - static transient boolean failed = false; - static CountDownLatch cl = new CountDownLatch(1); - static AtomicInteger count = new AtomicInteger(); + static boolean failed = false; public static void main(String[] args) throws Exception { // At the beginning, current subject is null test("", null); - cl.await(); if (failed) { throw new Exception("Failed"); } @@ -57,12 +53,6 @@ public static void main(String[] args) throws Exception { */ synchronized static void check(String label, Subject expected) { Subject cas = Subject.current(); - Subject accs = Subject.getSubject(AccessController.getContext()); - if (cas != accs) { - failed = true; - System.out.println(label + ": current " + s2s(cas) - + " but getSubject is " + s2s(accs)); - } Subject interested = cas; if (interested != expected) { failed = true; @@ -89,31 +79,23 @@ static Void test(String name, Subject expected) { // run with a new subject, inside current subject will be the new subject Subject.callAs(another, () -> test(name + 'c', another)); Subject.doAs(another, (PrivilegedAction) () -> test(name + 'd', another)); + Subject.doAsPrivileged(another, (PrivilegedAction) () -> test(name + 'e', another), null); + try { + Subject.doAs(another, (PrivilegedExceptionAction) () -> test(name + 'f', another)); + Subject.doAsPrivileged(another, (PrivilegedExceptionAction) () -> test(name + 'g', another), null); + } catch (Exception e) { + throw new RuntimeException(e); + } // run with null, inside current subject will be null Subject.callAs(null, () -> test(name + 'C', null)); Subject.doAs(null, (PrivilegedAction) () -> test(name + 'D', null)); - // new thread, inside current subject is unchanged - count.incrementAndGet(); - new Thread(() -> { - try { - test(name + 't', expected); - try { - Thread.sleep(500); - } catch (Exception e) { - throw new AssertionError(e); - } - // by this time, parent thread should have exited the - // action and current subject reset, but here - // current subject unchanged. - test(name + 'T', expected); - } finally { - var n = count.decrementAndGet(); - if (n == 0) { - cl.countDown(); - } - assert n >= 0; - } - }).start(); + Subject.doAsPrivileged(null, (PrivilegedAction) () -> test(name + 'E', null), null); + try { + Subject.doAs(null, (PrivilegedExceptionAction) () -> test(name + 'F', null)); + Subject.doAsPrivileged(null, (PrivilegedExceptionAction) () -> test(name + 'G', null), null); + } catch (Exception e) { + throw new RuntimeException(e); + } } // Now it's reset to original check(" ".repeat(name.length()) + "<- " + name, expected); diff --git a/test/jdk/javax/security/auth/Subject/FromACC.java b/test/jdk/javax/security/auth/Subject/FromACC.java index 07216cb228b70..c954d86202c7f 100644 --- a/test/jdk/javax/security/auth/Subject/FromACC.java +++ b/test/jdk/javax/security/auth/Subject/FromACC.java @@ -32,7 +32,6 @@ * @bug 8267108 * @summary confirm current installed subject specification * @run main/othervm -Djava.security.manager=allow FromACC - * @run main/othervm -Djava.security.manager=disallow FromACC */ public class FromACC { public static void main(String[] args) throws Exception { diff --git a/test/jdk/javax/security/auth/Subject/UnsupportedSV.java b/test/jdk/javax/security/auth/Subject/UnsupportedSV.java new file mode 100644 index 0000000000000..ad85b54be2b50 --- /dev/null +++ b/test/jdk/javax/security/auth/Subject/UnsupportedSV.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, 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. + * + * 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. + */ + +/* + * @test + * @bug 8296244 + * @library /test/lib + * @summary Implement Subject.current and Subject.callAs using scoped values + * @run main/othervm -Djava.security.manager=disallow UnsupportedSV t1 + * @run main/othervm -Djava.security.manager=allow UnsupportedSV t2 + */ +import com.sun.security.auth.UserPrincipal; +import jdk.test.lib.Utils; + +import javax.security.auth.Subject; +import javax.security.auth.SubjectDomainCombiner; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; + +public class UnsupportedSV { + public static void main(String[] args) throws Exception { + switch (args[0]) { + case "t1" -> t1(); + case "t2" -> t2(); + } + } + + // ScopedValue-based implementation is used + static void t1() throws Exception { + AccessControlContext acc = AccessController.getContext(); + Utils.runAndCheckException(() -> Subject.getSubject(acc), + UnsupportedOperationException.class); + + Subject s = new Subject(); + s.getPrincipals().add(new UserPrincipal("Duke")); + + // TODO: Still has no way to reject the following code. + // Here, AccessController::getContext returns a plan ACC without + // the subject inside. + AccessControlContext acc2 = Subject.callAs(s, AccessController::getContext); + Subject ns = AccessController.doPrivileged( + (PrivilegedAction) Subject::current, acc2); + System.out.println(ns); + } + + // When a security manager is set, ScopedValue-based implementation + // will not be used + static void t2() { + AccessControlContext acc = AccessController.getContext(); + Subject.getSubject(acc); + } +} diff --git a/test/jdk/javax/security/auth/Subject/doAs/NestedActions.java b/test/jdk/javax/security/auth/Subject/doAs/NestedActions.java index e3a9abb45815f..2730d0bbad2fe 100644 --- a/test/jdk/javax/security/auth/Subject/doAs/NestedActions.java +++ b/test/jdk/javax/security/auth/Subject/doAs/NestedActions.java @@ -26,9 +26,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.security.AccessControlContext; import java.security.AccessControlException; -import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -282,8 +280,7 @@ class Utils { static void readFile(String filename) { System.out.println("ReadFromFileAction: try to read " + filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); System.out.println("principals = " + subject.getPrincipals()); try (FileInputStream fis = new FileInputStream(filename)) { // do nothing @@ -294,8 +291,7 @@ static void readFile(String filename) { static void writeFile(String filename) { System.out.println("WriteToFileAction: try to write to " + filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); System.out.println("principals = " + subject.getPrincipals()); try (BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(filename))) { @@ -325,8 +321,7 @@ class WriteToFileAction implements PrivilegedAction { @Override public Object run() { Utils.writeFile(filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); return Subject.doAs(subject, nextAction); } @@ -350,8 +345,7 @@ class ReadFromFileAction implements PrivilegedAction { public Object run() { Utils.readFile(filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); ReadPropertyAction readProperty = new ReadPropertyAction(); if (anotherSubject != null) { return Subject.doAs(anotherSubject, readProperty); @@ -369,8 +363,7 @@ public java.lang.Object run() { System.out.println("ReadPropertyAction: " + "try to read 'java.class.path' property"); - AccessControlContext acc = AccessController.getContext(); - Subject s = Subject.getSubject(acc); + Subject s = Subject.current(); System.out.println("principals = " + s.getPrincipals()); System.out.println("java.class.path = " + System.getProperty("java.class.path")); @@ -390,8 +383,7 @@ public WriteToFileNegativeAction(String filename) { @Override public Object run() { - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); System.out.println("principals = " + subject.getPrincipals()); try { @@ -422,8 +414,7 @@ public ReadFromFileNegativeAction(String filename) { @Override public Object run() { - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); System.out.println("principals = " + subject.getPrincipals()); try { @@ -449,8 +440,7 @@ class ReadPropertyNegativeAction implements PrivilegedAction { public java.lang.Object run() { System.out.println("Try to read 'java.class.path' property"); - AccessControlContext acc = AccessController.getContext(); - Subject s = Subject.getSubject(acc); + Subject s = Subject.current(); System.out.println("principals = " + s.getPrincipals()); try { @@ -480,8 +470,7 @@ class WriteToFileExceptionAction implements PrivilegedExceptionAction { @Override public Object run() throws Exception { Utils.writeFile(filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); ReadFromFileExceptionAction readFromFile = new ReadFromFileExceptionAction(filename); return Subject.doAs(subject, readFromFile); @@ -500,8 +489,7 @@ class ReadFromFileExceptionAction implements PrivilegedExceptionAction { @Override public Object run() throws Exception { Utils.readFile(filename); - AccessControlContext acc = AccessController.getContext(); - Subject subject = Subject.getSubject(acc); + Subject subject = Subject.current(); ReadPropertyExceptionAction readProperty = new ReadPropertyExceptionAction(); return Subject.doAs(subject, readProperty); @@ -515,8 +503,7 @@ class ReadPropertyExceptionAction implements PrivilegedExceptionAction { public java.lang.Object run() throws Exception { System.out.println("Try to read 'java.class.path' property"); - AccessControlContext acc = AccessController.getContext(); - Subject s = Subject.getSubject(acc); + Subject s = Subject.current(); System.out.println("principals = " + s.getPrincipals()); try {