[ https://issues.apache.org/jira/browse/HADOOP-19212?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18015197#comment-18015197 ]
ASF GitHub Bot commented on HADOOP-19212: ----------------------------------------- LuciferYang commented on code in PR #7886: URL: https://github.com/apache/hadoop/pull/7886#discussion_r2288345234 ########## hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/util/SubjectUtil.java: ########## @@ -0,0 +1,232 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionException; + +import javax.security.auth.Subject; + +import org.apache.hadoop.classification.InterfaceAudience.Private; + +@Private +public class SubjectUtil { + private static MethodHandle CALL_AS; + private static MethodHandle CURRENT; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + try { + // Subject.doAs() is deprecated for removal and replaced by Subject.callAs(). + // Lookup first the new API, since for Java versions where both exist, the + // new API delegates to the old API (e.g. Java 18, 19 and 20). + // Otherwise (e.g. Java 17), lookup the old API. + CALL_AS = lookup.findStatic(Subject.class, "callAs", + MethodType.methodType(Object.class, Subject.class, Callable.class)); + } catch (NoSuchMethodException x) { + try { + // Lookup the old API. + MethodType oldSignature = MethodType.methodType( + Object.class, Subject.class, PrivilegedExceptionAction.class); + MethodHandle doAs = lookup.findStatic(Subject.class, "doAs", oldSignature); + // Convert the Callable used in the new API to the PrivilegedAction used + // in the old API. + MethodType convertSignature = MethodType.methodType( + PrivilegedExceptionAction.class, Callable.class); + MethodHandle converter = lookup.findStatic( + SubjectUtil.class, "callableToPrivilegedExceptionAction", convertSignature); + CALL_AS = MethodHandles.filterArguments(doAs, 1, converter); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + // Subject.getSubject(AccessControlContext) is deprecated for removal and + // replaced by Subject.current(). + // Lookup first the new API, since for Java versions where both exists, the + // new API delegates to the old API (e.g. Java 18, 19 and 20). + // Otherwise (e.g. Java 17), lookup the old API. + CURRENT = lookup.findStatic( + Subject.class, "current", MethodType.methodType(Subject.class)); + } catch (NoSuchMethodException e) { + MethodHandle getContext = lookupGetContext(); + MethodHandle getSubject = lookupGetSubject(); + CURRENT = MethodHandles.filterReturnValue(getContext, getSubject); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + + private static MethodHandle lookupGetSubject() { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + Class<?> contextKlass = ClassLoader.getSystemClassLoader() + .loadClass("java.security.AccessControlContext"); + return lookup.findStatic(Subject.class, + "getSubject", MethodType.methodType(Subject.class, contextKlass)); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + private static MethodHandle lookupGetContext() { + try { + // Use reflection to work with Java versions that have and don't have + // AccessController. + Class<?> controllerKlass = ClassLoader.getSystemClassLoader() + .loadClass("java.security.AccessController"); + Class<?> contextKlass = ClassLoader.getSystemClassLoader() + .loadClass("java.security.AccessControlContext"); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + return lookup.findStatic( + controllerKlass, "getContext", MethodType.methodType(contextKlass)); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + /** + * Maps to Subject.callAs() if available, otherwise maps to Subject.doAs(). + * It also wraps the Callable so that the Subject is propagated to the new thread + * in all Java versions. + * + * @param subject the subject this action runs as + * @param action the action to run + * @return the result of the action + * @param <T> the type of the result + * @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()}. + */ + @SuppressWarnings("unchecked") + public static <T> T callAs(Subject subject, Callable<T> action) throws CompletionException { + try { + return (T) CALL_AS.invoke(subject, action); + } catch (PrivilegedActionException e) { + throw new CompletionException(e.getCause()); + } catch (Throwable t) { + throw sneakyThrow(t); + } + } + + /** + * Maps action to a Callable, and delegates to callAs(). On older JVMs, the + * action may be double wrapped (into Callable, and then back into + * PrivilegedAction). + * + * @param subject the subject this action runs as + * @param action action the action to run + * @return the result of the action + * @param <T> the type of the result + */ + public static <T> T doAs(Subject subject, PrivilegedAction<T> action) { + try { + return callAs(subject, privilegedActionToCallable(action)); + } catch (CompletionException ce) { + Throwable cause = ce.getCause(); + if (cause != null) { + throw sneakyThrow(cause); + } else { + // This should never happen, as CompletionException should always wrap an exception + throw ce; + } + } + } + + /** + * Maps action to a Callable, and delegates to callAs(). On older JVMs, the + * action may be double wrapped (into Callable, and then back into PrivilegedAction). + * + * @param subject the subject this action runs as + * @param action action the action to run + * @return the result of the action + * @param <T> the type of the result + * @throws PrivilegedActionException if {@code action.run()} throws an exception. + * The cause of the {@code PrivilegedActionException} is set to the exception + * thrown by {@code action.run()}. + */ + public static <T> T doAs( + Subject subject, PrivilegedExceptionAction<T> action) throws PrivilegedActionException { + try { + return callAs(subject, privilegedExceptionActionToCallable(action)); + } catch (CompletionException ce) { + try { + Exception cause = (Exception) ce.getCause(); + throw new PrivilegedActionException(cause); + } catch (ClassCastException castException) { + // This should never happen, as PrivilegedExceptionAction should not wrap + // non-checked exceptions + throw new PrivilegedActionException(new UndeclaredThrowableException(ce.getCause())); + } + } + } + + /** + * Maps to Subject.current() if available, otherwise maps to Subject.getSubject(). + * + * @return the current subject + */ + public static Subject current() { + try { + return (Subject) CURRENT.invoke(); + } catch (Throwable t) { + throw sneakyThrow(t); + } + } + + @SuppressWarnings("unused") + private static <T> PrivilegedExceptionAction<T> callableToPrivilegedExceptionAction( + Callable<T> callable) { + return callable::call; + } + + private static <T> Callable<T> privilegedExceptionActionToCallable( + PrivilegedExceptionAction<T> action) { + return action::run; + } + + private static <T> Callable<T> privilegedActionToCallable( + PrivilegedAction<T> action) { + return action::run; + } + + @SuppressWarnings("unchecked") + private static <E extends Throwable> RuntimeException sneakyThrow(Throwable e) throws E { + throw (E) e; Review Comment: Type checking should be performed here. For `RuntimeException` and `Error`, they should be thrown directly, while other exceptions should be wrapped into a `RuntimeException` and then thrown. Additionally, does this method need to specify a return value type? ########## hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/util/SubjectUtil.java: ########## @@ -0,0 +1,232 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.util; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionException; + +import javax.security.auth.Subject; + +import org.apache.hadoop.classification.InterfaceAudience.Private; + +@Private +public class SubjectUtil { + private static MethodHandle CALL_AS; + private static MethodHandle CURRENT; + + static { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + try { + // Subject.doAs() is deprecated for removal and replaced by Subject.callAs(). + // Lookup first the new API, since for Java versions where both exist, the + // new API delegates to the old API (e.g. Java 18, 19 and 20). + // Otherwise (e.g. Java 17), lookup the old API. + CALL_AS = lookup.findStatic(Subject.class, "callAs", + MethodType.methodType(Object.class, Subject.class, Callable.class)); + } catch (NoSuchMethodException x) { + try { + // Lookup the old API. + MethodType oldSignature = MethodType.methodType( + Object.class, Subject.class, PrivilegedExceptionAction.class); + MethodHandle doAs = lookup.findStatic(Subject.class, "doAs", oldSignature); + // Convert the Callable used in the new API to the PrivilegedAction used + // in the old API. + MethodType convertSignature = MethodType.methodType( + PrivilegedExceptionAction.class, Callable.class); + MethodHandle converter = lookup.findStatic( + SubjectUtil.class, "callableToPrivilegedExceptionAction", convertSignature); + CALL_AS = MethodHandles.filterArguments(doAs, 1, converter); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); Review Comment: I suggest throwing `ExceptionInInitializerError` rather than `AssertionError` in the static block. > [JDK23] org.apache.hadoop.security.UserGroupInformation use of Subject needs > to move to replacement APIs > -------------------------------------------------------------------------------------------------------- > > Key: HADOOP-19212 > URL: https://issues.apache.org/jira/browse/HADOOP-19212 > Project: Hadoop Common > Issue Type: Sub-task > Components: security > Affects Versions: 3.5.0 > Reporter: Alan Bateman > Priority: Major > Labels: pull-request-available > > `javax.security.auth.Subject.getSubject` and `Subject.doAs` were deprecated > for removal in JDK 17. The replacement APIs are `Subject.current` and > `callAs`. See [JEP 411]([https://openjdk.org/jeps/411]) for background. > The `Subject.getSubject` API has been "degraded" in JDK 23 to throw > `UnsupportedOperationException` if not running with the option to allow a > SecurityManager. In a future JDK release, the `Subject.getSubject` API will > be degraded further to throw`UnsupportedOperationException` unconditionally. > [renaissance/issues/439]([https://github.com/renaissance-benchmarks/renaissance/issues/439]) > is a failure with a Spark benchmark due to the code in > `org.apache.hadoop.security.UserGroupInformation` using the deprecated > `Subject.getSubject` method. The maintainers of this code need to migrate > this code to the replacement APIs to ensure that this code will continue to > work once the security manager feature is removed. -- This message was sent by Atlassian Jira (v8.20.10#820010) --------------------------------------------------------------------- To unsubscribe, e-mail: common-issues-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-issues-h...@hadoop.apache.org