diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java index 5d8f1ecfb0d..370f92ff4d6 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/UserGroupInformation.java @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -625,7 +626,33 @@ public class UserGroupInformation { this.isKeytab = KerberosUtil.hasKerberosKeyTab(subject); this.isKrbTkt = KerberosUtil.hasKerberosTicket(subject); } - + + /** + * Copies the Subject of this UGI and creates a new UGI with the new subject. + * This can be used to add credentials (e.g. tokens) to different copies of + * the same UGI, allowing multiple users with different tokens to reuse the + * UGI without re-authenticating with Kerberos. + * @return clone of the UGI with a new subject. + */ + @InterfaceAudience.Public + @InterfaceStability.Evolving + public UserGroupInformation copySubjectAndUgi() { + Subject subj = getSubject(); + // The ctor will set other fields automatically from the principals. + return new UserGroupInformation(new Subject(false, subj.getPrincipals(), + cloneCredentials(subj.getPublicCredentials()), + cloneCredentials(subj.getPrivateCredentials()))); + } + + private static Set cloneCredentials(Set old) { + Set set = new HashSet<>(); + // Make sure Hadoop credentials objects do not reuse the maps. + for (Object o : old) { + set.add(o instanceof Credentials ? new Credentials((Credentials)o) : o); + } + return set; + } + /** * checks if logged in using kerberos * @return true if the subject logged via keytab or has a Kerberos TGT diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java index ed407ad8f51..838d4311df6 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestUserGroupInformation.java @@ -43,6 +43,7 @@ import java.security.PrivilegedExceptionAction; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS; @@ -841,6 +842,32 @@ public class TestUserGroupInformation { assertEquals(1, tokens.size()); } + @Test(timeout = 30000) + public void testCopySubjectAndUgi() throws IOException { + SecurityUtil.setAuthenticationMethod(AuthenticationMethod.SIMPLE, conf); + UserGroupInformation.setConfiguration(conf); + UserGroupInformation u1 = UserGroupInformation.getLoginUser(); + assertNotNull(u1); + @SuppressWarnings("unchecked") + Token tmpToken = mock(Token.class); + u1.addToken(tmpToken); + + UserGroupInformation u2 = u1.copySubjectAndUgi(); + assertEquals(u1.getAuthenticationMethod(), u2.getAuthenticationMethod()); + assertNotSame(u1.getSubject(), u2.getSubject()); + Credentials c1 = u1.getCredentials(), c2 = u2.getCredentials(); + List sc1 = c1.getAllSecretKeys(), sc2 = c2.getAllSecretKeys(); + assertArrayEquals(sc1.toArray(new Text[0]), sc2.toArray(new Text[0])); + Collection> ts1 = c1.getAllTokens(), + ts2 = c2.getAllTokens(); + assertArrayEquals(ts1.toArray(new Token[0]), ts2.toArray(new Token[0])); + @SuppressWarnings("unchecked") + Token token = mock(Token.class); + u2.addToken(token); + assertTrue(u2.getCredentials().getAllTokens().contains(token)); + assertFalse(u1.getCredentials().getAllTokens().contains(token)); + } + /** * This test checks a race condition between getting and adding tokens for * the current user. Calling UserGroupInformation.getCurrentUser() returns