001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.security;
019
020import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS;
021
022import com.sun.security.auth.login.ConfigFile;
023import java.io.File;
024import java.io.IOException;
025import java.lang.reflect.UndeclaredThrowableException;
026import java.security.AccessControlContext;
027import java.security.AccessController;
028import java.security.Principal;
029import java.security.PrivilegedAction;
030import java.security.PrivilegedActionException;
031import java.security.PrivilegedExceptionAction;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Collection;
035import java.util.Collections;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.LinkedHashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043import javax.security.auth.Subject;
044import javax.security.auth.kerberos.KerberosKey;
045import javax.security.auth.kerberos.KerberosPrincipal;
046import javax.security.auth.kerberos.KerberosTicket;
047import javax.security.auth.login.AppConfigurationEntry;
048import javax.security.auth.login.LoginContext;
049import javax.security.auth.login.LoginException;
050
051import org.apache.commons.logging.Log;
052import org.apache.commons.logging.LogFactory;
053import org.apache.hadoop.classification.InterfaceAudience;
054import org.apache.hadoop.classification.InterfaceStability;
055
056import org.apache.hadoop.classification.MapRModified;
057import org.apache.hadoop.conf.Configuration;
058import org.apache.hadoop.io.Text;
059import org.apache.hadoop.metrics2.annotation.Metric;
060import org.apache.hadoop.metrics2.annotation.Metrics;
061import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
062import org.apache.hadoop.metrics2.lib.MetricsRegistry;
063import org.apache.hadoop.metrics2.lib.MutableQuantiles;
064import org.apache.hadoop.metrics2.lib.MutableRate;
065import org.apache.hadoop.security.SaslRpcServer.AuthMethod;
066import org.apache.hadoop.security.login.HadoopLoginModule;
067import org.apache.hadoop.security.rpcauth.RpcAuthMethod;
068import org.apache.hadoop.security.rpcauth.RpcAuthRegistry;
069import org.apache.hadoop.security.token.Token;
070import org.apache.hadoop.security.token.TokenIdentifier;
071import org.apache.hadoop.util.Shell;
072import org.apache.hadoop.util.Time;
073
074import static org.apache.hadoop.util.PlatformName.IBM_JAVA;
075
076import com.google.common.annotations.VisibleForTesting;
077
078/**
079 * User and group information for Hadoop.
080 * This class wraps around a JAAS Subject and provides methods to determine the
081 * user's username and groups. It supports both the Windows, Unix and Kerberos 
082 * login modules.
083 */
084@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce", "HBase", "Hive", "Oozie"})
085@InterfaceStability.Evolving
086@MapRModified
087public class UserGroupInformation {
088  private static final Log LOG =  LogFactory.getLog(UserGroupInformation.class);
089  /**
090   * Percentage of the ticket window to use before we renew ticket.
091   */
092  private static final float TICKET_RENEW_WINDOW = 0.80f;
093  static final String HADOOP_USER_NAME = "HADOOP_USER_NAME";
094  static final String HADOOP_PROXY_USER = "HADOOP_PROXY_USER";
095
096  // Begin MapR section
097  private static final String HADOOP_SECURITY_SPOOF_USER = "hadoop.spoof.user";
098  private static final String HADOOP_SECURITY_SPOOFED_USER = "hadoop.spoofed.user.username";
099  private static boolean spoofUser = false;
100  private static String spoofedUser;
101
102  private static void checkSpoofing(Configuration conf) {
103    // spoof users by default on Windows
104    spoofUser = conf.getBoolean(HADOOP_SECURITY_SPOOF_USER,
105        windows ? true : false);
106
107    if (!spoofUser) {
108      return;
109    }
110
111    spoofedUser = conf.get(HADOOP_SECURITY_SPOOFED_USER, "root");
112  }
113  // End MapR section
114
115  /** 
116   * UgiMetrics maintains UGI activity statistics
117   * and publishes them through the metrics interfaces.
118   */
119  @Metrics(about="User and group related metrics", context="ugi")
120  static class UgiMetrics {
121    final MetricsRegistry registry = new MetricsRegistry("UgiMetrics");
122
123    @Metric("Rate of successful kerberos logins and latency (milliseconds)")
124    MutableRate loginSuccess;
125    @Metric("Rate of failed kerberos logins and latency (milliseconds)")
126    MutableRate loginFailure;
127    @Metric("GetGroups") MutableRate getGroups;
128    MutableQuantiles[] getGroupsQuantiles;
129
130    static UgiMetrics create() {
131      return DefaultMetricsSystem.instance().register(new UgiMetrics());
132    }
133
134    void addGetGroups(long latency) {
135      getGroups.add(latency);
136      if (getGroupsQuantiles != null) {
137        for (MutableQuantiles q : getGroupsQuantiles) {
138          q.add(latency);
139        }
140      }
141    }
142  }
143  
144  /** Metrics to track UGI activity */
145  static UgiMetrics metrics = UgiMetrics.create();
146  /** The auth method to use */
147  private static AuthenticationMethod authenticationMethod;
148  /** Server-side groups fetching service */
149  private static Groups groups;
150  /** The configuration to use */
151  private static Configuration conf;
152
153  /** JAAS configuration name to be used for user login */
154  private static String userJAASConfName;
155
156  /** JAAS configuration name to be used for service login */
157  private static String serviceJAASConfName;
158  
159  /** Leave 10 minutes between relogin attempts. */
160  private static final long MIN_TIME_BEFORE_RELOGIN = 10 * 60 * 1000L;
161  
162  /**Environment variable pointing to the token cache file*/
163  public static final String HADOOP_TOKEN_FILE_LOCATION = 
164    "HADOOP_TOKEN_FILE_LOCATION";
165
166  private static Class<? extends Principal> customAuthPrincipalClass;
167
168  private static Class<? extends RpcAuthMethod> customRpcAuthMethodClass;
169
170  // mapr_extensibility
171  public static final String USER_TICKET_FILE_LOCATION = "MAPR_TICKETFILE_LOCATION";
172
173  private static final String JAVA_SECURITY_AUTH_LOGIN_CONFIG = "java.security.auth.login.config";
174
175  /** 
176   * A method to initialize the fields that depend on a configuration.
177   * Must be called before useKerberos or groups is used.
178   */
179  private static void ensureInitialized() {
180    if (conf == null) {
181      synchronized(UserGroupInformation.class) {
182        if (conf == null) { // someone might have beat us
183          initialize(new Configuration(), false);
184        }
185      }
186    }
187  }
188
189  /**
190   * Initialize UGI and related classes.
191   * @param conf the configuration to use
192   */
193  private static synchronized void initialize(Configuration conf,
194                                              boolean overrideNameRules) {
195    authenticationMethod = SecurityUtil.getAuthenticationMethod(conf);
196    if (LOG.isDebugEnabled())
197      LOG.debug("HADOOP_SECURITY_AUTHENTICATION is set to: " + authenticationMethod);
198
199    // spoofing is only allowed when insecure
200    if (authenticationMethod == null || authenticationMethod.equals(AuthenticationMethod.SIMPLE)) {
201      checkSpoofing(conf);
202    } else if (authenticationMethod.equals(AuthenticationMethod.CUSTOM)) {
203      customAuthPrincipalClass = SecurityUtil.getCustomAuthPrincipal(conf);
204      customRpcAuthMethodClass = SecurityUtil.getCustomRpcAuthMethod(conf);
205
206      if (customAuthPrincipalClass == null || customRpcAuthMethodClass == null) {
207        throw new RuntimeException("Either principal class or rpc auth method class is null for custom auth method.");
208      }
209    }
210
211    String jaasConfName = null;
212    if (authenticationMethod == AuthenticationMethod.SIMPLE) {
213      jaasConfName = "simple";
214    } else {
215      LOG.debug("Security is enabled.");
216      // ignore what is passed in and instead honor extra configuration
217      // or JVM setting if neither set, use default.
218      jaasConfName = System.getProperty("hadoop.login");
219      if (jaasConfName == null) {
220        jaasConfName = conf.get("hadoop.login", "default");
221      }
222    }
223
224    userJAASConfName = jaasConfName.startsWith("hadoop_")
225        ? jaasConfName : "hadoop_" + jaasConfName;
226    serviceJAASConfName = userJAASConfName.endsWith("_keytab")
227        ? userJAASConfName : userJAASConfName + "_keytab";
228
229    if (LOG.isDebugEnabled())
230      LOG.debug("Login configuration entry is " + userJAASConfName);
231
232    if (System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG) == null) {
233      String loginConfigPath = conf.get(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
234      if (loginConfigPath != null) {
235        System.setProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG, loginConfigPath);
236        LOG.info("Java System property 'java.security.auth.login.config' not"
237            + " set, unilaterally setting to " + loginConfigPath);
238      } else {
239        LOG.warn("'java.security.auth.login.config' is not"
240            + " configured either in Hadoop configuration or"
241            + " via Java property, may cause login failure");
242      }
243    } else {
244      String loginConfPath = System.getProperty(JAVA_SECURITY_AUTH_LOGIN_CONFIG);
245      ConfigFile loginConfig = new ConfigFile(new File(loginConfPath).toURI());
246      javax.security.auth.login.Configuration.setConfiguration(loginConfig);
247    }
248
249    if (overrideNameRules || !HadoopKerberosName.hasRulesBeenSet()) {
250      try {
251        HadoopKerberosName.setConfiguration(conf);
252      } catch (IOException ioe) {
253        throw new RuntimeException(
254            "Problem with Kerberos auth_to_local name configuration", ioe);
255      }
256    }
257    // If we haven't set up testing groups, use the configuration to find it
258    if (!(groups instanceof TestingGroups)) {
259      groups = Groups.getUserToGroupsMappingService(conf);
260    }
261    UserGroupInformation.conf = conf;
262
263    if (metrics.getGroupsQuantiles == null) {
264      int[] intervals = conf.getInts(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS);
265      if (intervals != null && intervals.length > 0) {
266        final int length = intervals.length;
267        MutableQuantiles[] getGroupsQuantiles = new MutableQuantiles[length];
268        for (int i = 0; i < length; i++) {
269          getGroupsQuantiles[i] = metrics.registry.newQuantiles(
270            "getGroups" + intervals[i] + "s",
271            "Get groups", "ops", "latency", intervals[i]);
272        }
273        metrics.getGroupsQuantiles = getGroupsQuantiles;
274      }
275    }
276  }
277
278  /**
279   * Set the static configuration for UGI.
280   * In particular, set the security authentication mechanism and the
281   * group look up service.
282   * @param conf the configuration to use
283   */
284  @InterfaceAudience.Public
285  @InterfaceStability.Evolving
286  public static void setConfiguration(Configuration conf) {
287    initialize(conf, true);
288  }
289  
290  @InterfaceAudience.Private
291  @VisibleForTesting
292  static void reset() {
293    authenticationMethod = null;
294    conf = null;
295    groups = null;
296    setLoginUser(null);
297    HadoopKerberosName.setRules(null);
298  }
299  
300  /**
301   * Determine if UserGroupInformation is using Kerberos to determine
302   * user identities or is relying on simple authentication
303   * 
304   * @return true if UGI is working in a secure environment
305   */
306  public static boolean isSecurityEnabled() {
307    return !isAuthenticationMethodEnabled(AuthenticationMethod.SIMPLE);
308  }
309  
310  @InterfaceAudience.Private
311  @InterfaceStability.Evolving
312  private static boolean isAuthenticationMethodEnabled(AuthenticationMethod method) {
313    ensureInitialized();
314    return (authenticationMethod == method);
315  }
316  
317  /**
318   * Information about the logged in user.
319   */
320  private static UserGroupInformation loginUser = null;
321  private static String keytabPrincipal = null;
322  private static String keytabFile = null;
323
324  private final Subject subject;
325  // All non-static fields must be read-only caches that come from the subject.
326  private final User user;
327  private final boolean isKeytab;
328  private final boolean isKrbTkt;
329  private List<RpcAuthMethod> rpcAuthMethodList;
330
331  private static final boolean windows = 
332      System.getProperty("os.name").startsWith("Windows");
333
334  private static class RealUser implements Principal {
335    private final UserGroupInformation realUser;
336    
337    RealUser(UserGroupInformation realUser) {
338      this.realUser = realUser;
339    }
340    
341    @Override
342    public String getName() {
343      return realUser.getUserName();
344    }
345    
346    public UserGroupInformation getRealUser() {
347      return realUser;
348    }
349    
350    @Override
351    public boolean equals(Object o) {
352      if (this == o) {
353        return true;
354      } else if (o == null || getClass() != o.getClass()) {
355        return false;
356      } else {
357        return realUser.equals(((RealUser) o).realUser);
358      }
359    }
360    
361    @Override
362    public int hashCode() {
363      return realUser.hashCode();
364    }
365    
366    @Override
367    public String toString() {
368      return realUser.toString();
369    }
370  }
371
372  private static LoginContext
373  newLoginContext(String appName, Subject subject,
374    Map<String,?> overrideOptions)
375      throws LoginException {
376    // Temporarily switch the thread's ContextClassLoader to match this
377    // class's classloader, so that we can properly load HadoopLoginModule
378    // from the JAAS libraries.
379    Thread t = Thread.currentThread();
380    ClassLoader oldCCL = t.getContextClassLoader();
381    t.setContextClassLoader(HadoopLoginModule.class.getClassLoader());
382    try {
383      if (overrideOptions != null) {
384        javax.security.auth.login.Configuration cfg =
385            new DynamicLoginConfiguration(getJAASConf(), overrideOptions);
386        return new LoginContext(appName, subject, null, cfg);
387      }
388      return new LoginContext(appName, subject);
389    } finally {
390      t.setContextClassLoader(oldCCL);
391    }
392  }
393
394  private static LoginContext newLoginContextWithKeyTab(String appName,
395      Subject subject, String keytab, String principal) throws LoginException {
396    Map<String,String> overrideOptions = new HashMap<String, String>();
397    overrideOptions.put("keyTab", keytab);
398    overrideOptions.put("principal", principal);
399    return newLoginContext(appName, subject, overrideOptions);
400  }
401
402  private static javax.security.auth.login.Configuration getJAASConf() {
403    return javax.security.auth.login.Configuration.getConfiguration();
404  }
405
406  private LoginContext getLogin() {
407    return user.getLogin();
408  }
409  
410  private void setLogin(LoginContext login) {
411    user.setLogin(login);
412  }
413
414  private void configureRpcAuthMethods(String confName) {
415    ArrayList<RpcAuthMethod> authMethodList = new ArrayList<RpcAuthMethod>();
416    if (isSecurityEnabled() && confName != null) {
417      Set<RpcAuthMethod> authMethods = new LinkedHashSet<RpcAuthMethod>();
418      AppConfigurationEntry[] appInfo =
419          getJAASConf().getAppConfigurationEntry(confName);
420      for (int i = 0; appInfo != null && i < appInfo.length; i++) {
421        String module = appInfo[i].getLoginModuleName();
422        RpcAuthMethod rpcAuthMethod = RpcAuthRegistry.getAuthMethodForLoginModule(module);
423        if (rpcAuthMethod != null) {
424          authMethods.add(rpcAuthMethod);
425        }
426      }
427      authMethods.add(RpcAuthRegistry.getAuthMethod(authenticationMethod));
428
429      if (isSecurityEnabled()
430          && !"hadoop_simple".equals(confName)
431          && authMethods.size() == 0) {
432        LOG.warn("Security is enabled but no suitable RPC authentication "
433            + "method is found in the provided JAAS configuration: " + confName);
434      }
435      authMethodList.addAll(authMethods);
436    } else {
437      authMethodList.add(RpcAuthRegistry.SIMPLE);
438    }
439
440    this.rpcAuthMethodList = Collections.unmodifiableList(authMethodList);
441  }
442
443  /**
444   * Create a UserGroupInformation for the given subject.
445   * This does not change the subject or acquire new credentials.
446   * @param subject the user's subject
447   */
448  UserGroupInformation(Subject subject) {
449    this(subject, userJAASConfName);
450  }
451
452  UserGroupInformation(Subject subject, String loginConfName) {
453    this.subject = subject;
454    this.user = subject.getPrincipals(User.class).iterator().next();
455    this.isKeytab = !subject.getPrivateCredentials(KerberosKey.class).isEmpty();
456    this.isKrbTkt = !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
457    configureRpcAuthMethods(loginConfName);
458
459    /* Since multiple methods are allowed at once, this isn't a great setting.
460     * We just prefer the dominant method. This concept really should be removed.
461     * Better to have the login modules set the methods of authentication based
462     * upon some common mapping or even callers just look directly at the subject
463     * to determine what is in it.
464     */
465    if (!subject.getPrincipals(KerberosPrincipal.class).isEmpty()) {
466      this.user.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
467      LOG.debug("found Kerberos Principal in subject, marking as such");
468    } else if (customAuthPrincipalClass != null && !subject.getPrincipals(customAuthPrincipalClass).isEmpty()) {
469      this.user.setAuthenticationMethod(AuthenticationMethod.CUSTOM);
470      LOG.debug("found custom auth principal " + customAuthPrincipalClass.getName() + "  in subject. " +
471          "marking authentication method as " + AuthenticationMethod.CUSTOM);
472    }
473  }
474  
475  /**
476   * checks if logged in using kerberos
477   * @return true if the subject logged via keytab or has a Kerberos TGT
478   */
479  public boolean hasKerberosCredentials() {
480    return isKeytab || isKrbTkt;
481  }
482
483  public List<RpcAuthMethod> getRpcAuthMethodList() {
484    return rpcAuthMethodList;
485  }
486
487  /**
488   * Return the current user, including any doAs in the current stack.
489   * @return the current user
490   * @throws IOException if login fails
491   */
492  @InterfaceAudience.Public
493  @InterfaceStability.Evolving
494  public synchronized
495  static UserGroupInformation getCurrentUser() throws IOException {
496    AccessControlContext context = AccessController.getContext();
497    Subject subject = Subject.getSubject(context);
498    if (subject == null || subject.getPrincipals(User.class).isEmpty()) {
499      return getLoginUser();
500    } else {
501      return new UserGroupInformation(subject);
502    }
503  }
504
505  /**
506   * Find the most appropriate UserGroupInformation to use
507   *
508   * @param ticketCachePath    The Kerberos ticket cache path, or NULL
509   *                           if none is specfied
510   * @param user               The user name, or NULL if none is specified.
511   *
512   * @return                   The most appropriate UserGroupInformation
513   */ 
514  public static UserGroupInformation getBestUGI(
515      String ticketCachePath, String user) throws IOException {
516    if (ticketCachePath != null) {
517      return getUGIFromTicketCache(ticketCachePath, user);
518    } else if (user == null) {
519      return getCurrentUser();
520    } else {
521      return createRemoteUser(user);
522    }    
523  }
524
525  /**
526   * Create a UserGroupInformation from a Kerberos ticket cache.
527   * 
528   * @param user                The principal name to load from the ticket
529   *                            cache
530   * @param ticketCachePath     the path to the ticket cache file
531   *
532   * @throws IOException        if the kerberos login fails
533   */
534  @InterfaceAudience.Public
535  @InterfaceStability.Evolving
536  public static UserGroupInformation getUGIFromTicketCache(
537            String ticketCache, String user) throws IOException {
538    if (!isAuthenticationMethodEnabled(AuthenticationMethod.KERBEROS)) {
539      return getBestUGI(null, user);
540    }
541    try {
542      Map<String,String> krbOptions = new HashMap<String,String>();
543      if (IBM_JAVA) {
544        krbOptions.put("useDefaultCcache", "true");
545        // The first value searched when "useDefaultCcache" is used.
546        System.setProperty("KRB5CCNAME", ticketCache);
547      } else {
548        krbOptions.put("ticketCache", ticketCache);
549      }
550      krbOptions.put("renewTGT", "false");
551      LoginContext login = newLoginContext(userJAASConfName, null, krbOptions);
552      login.login();
553
554      Subject loginSubject = login.getSubject();
555      Set<Principal> loginPrincipals = loginSubject.getPrincipals();
556      if (loginPrincipals.isEmpty()) {
557        throw new RuntimeException("No login principals found!");
558      }
559      if (loginPrincipals.size() != 1) {
560        LOG.warn("found more than one principal in the ticket cache file " +
561          ticketCache);
562      }
563      User ugiUser = new User(loginPrincipals.iterator().next().getName(),
564          AuthenticationMethod.KERBEROS, login);
565      loginSubject.getPrincipals().add(ugiUser);
566      UserGroupInformation ugi = new UserGroupInformation(loginSubject);
567      ugi.setLogin(login);
568      ugi.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
569      return ugi;
570    } catch (LoginException le) {
571      throw new IOException("failure to login using ticket cache file " +
572          ticketCache, le);
573    }
574  }
575
576  /**
577   * Get the currently logged in user.
578   * @return the logged in user
579   * @throws IOException if login fails
580   */
581  @InterfaceAudience.Public
582  @InterfaceStability.Evolving
583  public synchronized 
584  static UserGroupInformation getLoginUser() throws IOException {
585    if (loginUser == null) {
586      loginUserFromSubject(null);
587    }
588    return loginUser;
589  }
590  
591  /**
592   * Log in a user using the given subject
593   * @parma subject the subject to use when logging in a user, or null to 
594   * create a new subject.
595   * @throws IOException if login fails
596   */
597  @InterfaceAudience.Public
598  @InterfaceStability.Evolving
599  public synchronized 
600  static void loginUserFromSubject(Subject subject) throws IOException {
601    ensureInitialized();
602    try {
603      if (subject == null) {
604        subject = new Subject();
605      }
606      LoginContext login = newLoginContext(userJAASConfName, subject, null);
607      login.login();
608      UserGroupInformation realUser = new UserGroupInformation(subject, userJAASConfName);
609      realUser.setLogin(login);
610      // AuthenticationMethod is now computed based upon subject
611      // loginUser.setAuthenticationMethod(...);
612      setUserAuthenticationMethod(realUser);
613      realUser = new UserGroupInformation(login.getSubject(), userJAASConfName);
614      // If the HADOOP_PROXY_USER environment variable or property
615      // is specified, create a proxy user as the logged in user.
616      String proxyUser = System.getenv(HADOOP_PROXY_USER);
617      if (proxyUser == null) {
618        proxyUser = System.getProperty(HADOOP_PROXY_USER);
619      }
620      loginUser = proxyUser == null ? realUser : createProxyUser(proxyUser, realUser);
621
622      String fileLocation = System.getenv(HADOOP_TOKEN_FILE_LOCATION);
623      if (fileLocation != null) {
624        // Load the token storage file and put all of the tokens into the
625        // user. Don't use the FileSystem API for reading since it has a lock
626        // cycle (HADOOP-9212).
627        Credentials cred = Credentials.readTokenStorageFile(
628            new File(fileLocation), conf);
629        loginUser.addCredentials(cred);
630      }
631      loginUser.spawnAutoRenewalThreadForUserCreds();
632    } catch (LoginException le) {
633      LOG.debug("failure to login", le);
634      throw new IOException("failure to login: " + le.getMessage(), le);
635    }
636    if (LOG.isDebugEnabled()) {
637      LOG.debug("UGI loginUser:"+loginUser);
638    }
639  }
640
641  private static void setUserAuthenticationMethod(UserGroupInformation realUser) {
642    if (realUser.user.getAuthenticationMethod() == null) { // if the user's auth method is not set from subject
643      switch (authenticationMethod) {                      // set it from configured authenticationMethod
644        case TOKEN:
645        case CERTIFICATE:
646        case KERBEROS_SSL:
647        case PROXY:
648          throw new UnsupportedOperationException(
649              authenticationMethod + " login authentication is not supported");
650        default:
651          LOG.debug("Found no authentication principals in subject. Simple?");
652          realUser.user.setAuthenticationMethod(authenticationMethod);
653      }
654    }
655  }
656
657  @InterfaceAudience.Private
658  @InterfaceStability.Unstable
659  @VisibleForTesting
660  public synchronized static void setLoginUser(UserGroupInformation ugi) {
661    // if this is to become stable, should probably logout the currently
662    // logged in ugi if it's different
663    loginUser = ugi;
664  }
665  
666  /**
667   * Is this user logged in from a keytab file?
668   * @return true if the credentials are from a keytab file.
669   */
670  public boolean isFromKeytab() {
671    return isKeytab;
672  }
673  
674  /**
675   * Get the Kerberos TGT
676   * @return the user's TGT or null if none was found
677   */
678  private synchronized KerberosTicket getTGT() {
679    Set<KerberosTicket> tickets = subject
680        .getPrivateCredentials(KerberosTicket.class);
681    for (KerberosTicket ticket : tickets) {
682      if (SecurityUtil.isOriginalTGT(ticket)) {
683        if (LOG.isDebugEnabled()) {
684          LOG.debug("Found tgt " + ticket);
685        }
686        return ticket;
687      }
688    }
689    return null;
690  }
691  
692  private long getRefreshTime(KerberosTicket tgt) {
693    long start = tgt.getStartTime().getTime();
694    long end = tgt.getEndTime().getTime();
695    return start + (long) ((end - start) * TICKET_RENEW_WINDOW);
696  }
697
698  /**Spawn a thread to do periodic renewals of kerberos credentials*/
699  private void spawnAutoRenewalThreadForUserCreds() {
700    if (isSecurityEnabled()) {
701      //spawn thread only if we have kerb credentials
702      if (user.getAuthenticationMethod() == AuthenticationMethod.KERBEROS &&
703          !isKeytab) {
704        Thread t = new Thread(new Runnable() {
705          
706          @Override
707          public void run() {
708            String cmd = conf.get("hadoop.kerberos.kinit.command",
709                                  "kinit");
710            KerberosTicket tgt = getTGT();
711            if (tgt == null) {
712              return;
713            }
714            long nextRefresh = getRefreshTime(tgt);
715            while (true) {
716              try {
717                long now = Time.now();
718                if(LOG.isDebugEnabled()) {
719                  LOG.debug("Current time is " + now);
720                  LOG.debug("Next refresh is " + nextRefresh);
721                }
722                if (now < nextRefresh) {
723                  Thread.sleep(nextRefresh - now);
724                }
725                Shell.execCommand(cmd, "-R");
726                if(LOG.isDebugEnabled()) {
727                  LOG.debug("renewed ticket");
728                }
729                reloginFromTicketCache();
730                tgt = getTGT();
731                if (tgt == null) {
732                  LOG.warn("No TGT after renewal. Aborting renew thread for " +
733                           getUserName());
734                  return;
735                }
736                nextRefresh = Math.max(getRefreshTime(tgt),
737                                       now + MIN_TIME_BEFORE_RELOGIN);
738              } catch (InterruptedException ie) {
739                LOG.warn("Terminating renewal thread");
740                return;
741              } catch (IOException ie) {
742                LOG.warn("Exception encountered while running the" +
743                    " renewal command. Aborting renew thread. " + ie);
744                return;
745              }
746            }
747          }
748        });
749        t.setDaemon(true);
750        t.setName("TGT Renewer for " + getUserName());
751        t.start();
752      }
753    }
754  }
755  /**
756   * Log a user in from a keytab file. Loads a user identity from a keytab
757   * file and logs them in. They become the currently logged-in user.
758   * @param user the principal name to load from the keytab
759   * @param path the path to the keytab file
760   * @throws IOException if the keytab file can't be read
761   */
762  @InterfaceAudience.Public
763  @InterfaceStability.Evolving
764  public synchronized
765  static void loginUserFromKeytab(String user,
766                                  String path
767                                  ) throws IOException {
768    if (!isSecurityEnabled())
769      return;
770
771    keytabFile = path;
772    keytabPrincipal = user;
773    Subject subject = new Subject();
774    LoginContext login; 
775    long start = 0;
776    try {
777      login = newLoginContextWithKeyTab(serviceJAASConfName,
778          subject, keytabFile, keytabPrincipal);
779      start = Time.now();
780      login.login();
781      metrics.loginSuccess.add(Time.now() - start);
782      loginUser = new UserGroupInformation(subject, serviceJAASConfName);
783      loginUser.setLogin(login);
784      // AuthenticationMethod is now computed based upon subject
785      // loginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
786    } catch (LoginException le) {
787      if (start > 0) {
788        metrics.loginFailure.add(Time.now() - start);
789      }
790      throw new IOException("Login failure for " + user + " from keytab " + 
791                            path, le);
792    }
793    LOG.info("Login successful for user " + keytabPrincipal
794        + " using keytab file " + keytabFile);
795  }
796  
797  /**
798   * Re-login a user from keytab if TGT is expired or is close to expiry.
799   * 
800   * @throws IOException
801   */
802  public synchronized void checkTGTAndReloginFromKeytab() throws IOException {
803    if (!isSecurityEnabled()
804        || user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS
805        || !isKeytab)
806      return;
807    KerberosTicket tgt = getTGT();
808    if (tgt != null && Time.now() < getRefreshTime(tgt)) {
809      return;
810    }
811    reloginFromKeytab();
812  }
813
814  /**
815   * Re-Login a user in from a keytab file. Loads a user identity from a keytab
816   * file and logs them in. They become the currently logged-in user. This
817   * method assumes that {@link #loginUserFromKeytab(String, String)} had 
818   * happened already.
819   * The Subject field of this UserGroupInformation object is updated to have
820   * the new credentials.
821   * @throws IOException on a failure
822   */
823  @InterfaceAudience.Public
824  @InterfaceStability.Evolving
825  public synchronized void reloginFromKeytab()
826  throws IOException {
827    if (!isSecurityEnabled() ||
828         user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
829         !isKeytab)
830      return;
831    
832    long now = Time.now();
833    if (!hasSufficientTimeElapsed(now)) {
834      return;
835    }
836
837    KerberosTicket tgt = getTGT();
838    //Return if TGT is valid and is not going to expire soon.
839    if (tgt != null && now < getRefreshTime(tgt)) {
840      return;
841    }
842    
843    LoginContext login = getLogin();
844    if (login == null || keytabFile == null) {
845      throw new IOException("loginUserFromKeyTab must be done first");
846    }
847    
848    long start = 0;
849    // register most recent relogin attempt
850    user.setLastLogin(now);
851    try {
852      if (LOG.isDebugEnabled()) {
853        LOG.debug("Initiating logout for " + getUserName());
854      }
855      synchronized (UserGroupInformation.class) {
856        // clear up the kerberos state. But the tokens are not cleared! As per
857        // the Java kerberos login module code, only the kerberos credentials
858        // are cleared
859        login.logout();
860        // login and also update the subject field of this instance to
861        // have the new credentials (pass it to the LoginContext constructor)
862        login = newLoginContextWithKeyTab(serviceJAASConfName, getSubject(),
863                                          keytabFile, keytabPrincipal);
864        LOG.info("Initiating re-login for " + keytabPrincipal);
865        start = Time.now();
866        login.login();
867        metrics.loginSuccess.add(Time.now() - start);
868        setLogin(login);
869      }
870    } catch (LoginException le) {
871      if (start > 0) {
872        metrics.loginFailure.add(Time.now() - start);
873      }
874      throw new IOException("Login failure for " + keytabPrincipal + 
875          " from keytab " + keytabFile, le);
876    } 
877  }
878
879  /**
880   * Re-Login a user in from the ticket cache.  This
881   * method assumes that login had happened already.
882   * The Subject field of this UserGroupInformation object is updated to have
883   * the new credentials.
884   * @throws IOException on a failure
885   */
886  @InterfaceAudience.Public
887  @InterfaceStability.Evolving
888  public synchronized void reloginFromTicketCache()
889  throws IOException {
890    if (!isSecurityEnabled() || 
891        user.getAuthenticationMethod() != AuthenticationMethod.KERBEROS ||
892        !isKrbTkt)
893      return;
894    LoginContext login = getLogin();
895    if (login == null) {
896      throw new IOException("login must be done first");
897    }
898    long now = Time.now();
899    if (!hasSufficientTimeElapsed(now)) {
900      return;
901    }
902    // register most recent relogin attempt
903    user.setLastLogin(now);
904    try {
905      if (LOG.isDebugEnabled()) {
906        LOG.debug("Initiating logout for " + getUserName());
907      }
908      //clear up the kerberos state. But the tokens are not cleared! As per 
909      //the Java kerberos login module code, only the kerberos credentials
910      //are cleared
911      login.logout();
912      //login and also update the subject field of this instance to 
913      //have the new credentials (pass it to the LoginContext constructor)
914      login = newLoginContext(userJAASConfName, getSubject(), null);
915      LOG.info("Initiating re-login for " + getUserName());
916      login.login();
917      setLogin(login);
918    } catch (LoginException le) {
919      throw new IOException("Login failure for " + getUserName(), le);
920    } 
921  }
922
923
924  /**
925   * Log a user in from a keytab file. Loads a user identity from a keytab
926   * file and login them in. This new user does not affect the currently
927   * logged-in user.
928   * @param user the principal name to load from the keytab
929   * @param path the path to the keytab file
930   * @throws IOException if the keytab file can't be read
931   */
932  public synchronized
933  static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
934                                  String path
935                                  ) throws IOException {
936    if (!isSecurityEnabled())
937      return UserGroupInformation.getCurrentUser();
938    String oldKeytabFile = null;
939    String oldKeytabPrincipal = null;
940
941    long start = 0;
942    try {
943      oldKeytabFile = keytabFile;
944      oldKeytabPrincipal = keytabPrincipal;
945      keytabFile = path;
946      keytabPrincipal = user;
947      Subject subject = new Subject();
948      
949      LoginContext login = newLoginContextWithKeyTab(serviceJAASConfName, subject,
950                                                     keytabFile, keytabPrincipal);
951       
952      start = Time.now();
953      login.login();
954      metrics.loginSuccess.add(Time.now() - start);
955      UserGroupInformation newLoginUser =
956          new UserGroupInformation(subject, serviceJAASConfName);
957      newLoginUser.setLogin(login);
958      // AuthenticationMethod is now computed based upon subject
959      // newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
960      return newLoginUser;
961    } catch (LoginException le) {
962      if (start > 0) {
963        metrics.loginFailure.add(Time.now() - start);
964      }
965      throw new IOException("Login failure for " + user + " from keytab " + 
966                            path, le);
967    } finally {
968      if(oldKeytabFile != null) keytabFile = oldKeytabFile;
969      if(oldKeytabPrincipal != null) keytabPrincipal = oldKeytabPrincipal;
970    }
971  }
972
973  private boolean hasSufficientTimeElapsed(long now) {
974    if (now - user.getLastLogin() < MIN_TIME_BEFORE_RELOGIN ) {
975      LOG.warn("Not attempting to re-login since the last re-login was " +
976          "attempted less than " + (MIN_TIME_BEFORE_RELOGIN/1000) + " seconds"+
977          " before.");
978      return false;
979    }
980    return true;
981  }
982  
983  /**
984   * Did the login happen via keytab
985   * @return true or false
986   */
987  @InterfaceAudience.Public
988  @InterfaceStability.Evolving
989  public synchronized static boolean isLoginKeytabBased() throws IOException {
990    return getLoginUser().isKeytab;
991  }
992
993  /**
994   * Create a user from a login name. It is intended to be used for remote
995   * users in RPC, since it won't have any credentials.
996   * @param user the full user principal name, must not be empty or null
997   * @return the UserGroupInformation for the remote user.
998   */
999  @InterfaceAudience.Public
1000  @InterfaceStability.Evolving
1001  public static UserGroupInformation createRemoteUser(String user) {
1002    if (user == null || user.isEmpty()) {
1003      throw new IllegalArgumentException("Null user");
1004    }
1005    Subject subject = new Subject();
1006    subject.getPrincipals().add(new User(user));
1007    UserGroupInformation result = new UserGroupInformation(subject);
1008    result.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
1009    return result;
1010  }
1011
1012  /**
1013   * existing types of authentications' methods
1014   */
1015  @InterfaceAudience.Public
1016  @InterfaceStability.Evolving
1017  public static enum AuthenticationMethod {
1018    // currently we support only one auth per method, but eventually a 
1019    // subtype is needed to differentiate, ex. if digest is token or ldap
1020    SIMPLE(false),
1021    KERBEROS(true),
1022    TOKEN(false),
1023    CERTIFICATE(true),
1024    KERBEROS_SSL(true),
1025    PROXY(false),
1026    CUSTOM(true);
1027    
1028    private final boolean allowsDelegation;
1029    
1030    private AuthenticationMethod(boolean allowsDelegation) {
1031      this.allowsDelegation = allowsDelegation;
1032    }
1033    
1034    public boolean allowsDelegation() {
1035      return allowsDelegation;
1036    }
1037  }
1038
1039  /**
1040   * Create a proxy user using username of the effective user and the ugi of the
1041   * real user.
1042   * @param user
1043   * @param realUser
1044   * @return proxyUser ugi
1045   */
1046  @InterfaceAudience.Public
1047  @InterfaceStability.Evolving
1048  public static UserGroupInformation createProxyUser(String user,
1049      UserGroupInformation realUser) {
1050    if (user == null || user.isEmpty()) {
1051      throw new IllegalArgumentException("Null user");
1052    }
1053    if (realUser == null) {
1054      throw new IllegalArgumentException("Null real user");
1055    }
1056    Subject subject = new Subject();
1057    Set<Principal> principals = subject.getPrincipals();
1058    principals.add(new User(user));
1059    principals.add(new RealUser(realUser));
1060    UserGroupInformation result =new UserGroupInformation(subject);
1061    result.setAuthenticationMethod(AuthenticationMethod.PROXY);
1062    return result;
1063  }
1064
1065  /**
1066   * get RealUser (vs. EffectiveUser)
1067   * @return realUser running over proxy user
1068   */
1069  @InterfaceAudience.Public
1070  @InterfaceStability.Evolving
1071  public UserGroupInformation getRealUser() {
1072    for (RealUser p: subject.getPrincipals(RealUser.class)) {
1073      return p.getRealUser();
1074    }
1075    return null;
1076  }
1077
1078
1079  
1080  /**
1081   * This class is used for storing the groups for testing. It stores a local
1082   * map that has the translation of usernames to groups.
1083   */
1084  private static class TestingGroups extends Groups {
1085    private final Map<String, List<String>> userToGroupsMapping = 
1086      new HashMap<String,List<String>>();
1087    private Groups underlyingImplementation;
1088    
1089    private TestingGroups(Groups underlyingImplementation) {
1090      super(new org.apache.hadoop.conf.Configuration());
1091      this.underlyingImplementation = underlyingImplementation;
1092    }
1093    
1094    @Override
1095    public List<String> getGroups(String user) throws IOException {
1096      List<String> result = userToGroupsMapping.get(user);
1097      
1098      if (result == null) {
1099        result = underlyingImplementation.getGroups(user);
1100      }
1101
1102      return result;
1103    }
1104
1105    private void setUserGroups(String user, String[] groups) {
1106      userToGroupsMapping.put(user, Arrays.asList(groups));
1107    }
1108  }
1109
1110  /**
1111   * Create a UGI for testing HDFS and MapReduce
1112   * @param user the full user principal name
1113   * @param userGroups the names of the groups that the user belongs to
1114   * @return a fake user for running unit tests
1115   */
1116  @InterfaceAudience.Public
1117  @InterfaceStability.Evolving
1118  public static UserGroupInformation createUserForTesting(String user, 
1119                                                          String[] userGroups) {
1120    ensureInitialized();
1121    UserGroupInformation ugi = createRemoteUser(user);
1122    // make sure that the testing object is setup
1123    if (!(groups instanceof TestingGroups)) {
1124      groups = new TestingGroups(groups);
1125    }
1126    // add the user groups
1127    ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1128    return ugi;
1129  }
1130
1131
1132  /**
1133   * Create a proxy user UGI for testing HDFS and MapReduce
1134   * 
1135   * @param user
1136   *          the full user principal name for effective user
1137   * @param realUser
1138   *          UGI of the real user
1139   * @param userGroups
1140   *          the names of the groups that the user belongs to
1141   * @return a fake user for running unit tests
1142   */
1143  public static UserGroupInformation createProxyUserForTesting(String user,
1144      UserGroupInformation realUser, String[] userGroups) {
1145    ensureInitialized();
1146    UserGroupInformation ugi = createProxyUser(user, realUser);
1147    // make sure that the testing object is setup
1148    if (!(groups instanceof TestingGroups)) {
1149      groups = new TestingGroups(groups);
1150    }
1151    // add the user groups
1152    ((TestingGroups) groups).setUserGroups(ugi.getShortUserName(), userGroups);
1153    return ugi;
1154  }
1155  
1156  /**
1157   * Get the user's login name.
1158   * @return the user's name up to the first '/' or '@'.
1159   */
1160  public String getShortUserName() {
1161    if (windows && spoofUser) {
1162      return spoofedUser;
1163    }
1164
1165    for (User p: subject.getPrincipals(User.class)) {
1166      return p.getShortName();
1167    }
1168    return null;
1169  }
1170
1171  public String getPrimaryGroupName() throws IOException {
1172    String[] groups = getGroupNames();
1173    if (groups.length == 0) {
1174      throw new IOException("There is no primary group for UGI " + this);
1175    }
1176    return groups[0];
1177  }
1178
1179  /**
1180   * Get the user's full principal name.
1181   * @return the user's full principal name.
1182   */
1183  @InterfaceAudience.Public
1184  @InterfaceStability.Evolving
1185  public String getUserName() {
1186    if (windows && spoofUser) {
1187      return spoofedUser;
1188    }
1189
1190    return user.getName();
1191  }
1192
1193  /**
1194   * Add a TokenIdentifier to this UGI. The TokenIdentifier has typically been
1195   * authenticated by the RPC layer as belonging to the user represented by this
1196   * UGI.
1197   * 
1198   * @param tokenId
1199   *          tokenIdentifier to be added
1200   * @return true on successful add of new tokenIdentifier
1201   */
1202  public synchronized boolean addTokenIdentifier(TokenIdentifier tokenId) {
1203    return subject.getPublicCredentials().add(tokenId);
1204  }
1205
1206  /**
1207   * Get the set of TokenIdentifiers belonging to this UGI
1208   * 
1209   * @return the set of TokenIdentifiers belonging to this UGI
1210   */
1211  public synchronized Set<TokenIdentifier> getTokenIdentifiers() {
1212    return subject.getPublicCredentials(TokenIdentifier.class);
1213  }
1214  
1215  /**
1216   * Add a token to this UGI
1217   * 
1218   * @param token Token to be added
1219   * @return true on successful add of new token
1220   */
1221  public synchronized boolean addToken(Token<? extends TokenIdentifier> token) {
1222    return (token != null) ? addToken(token.getService(), token) : false;
1223  }
1224
1225  /**
1226   * Add a named token to this UGI
1227   * 
1228   * @param alias Name of the token
1229   * @param token Token to be added
1230   * @return true on successful add of new token
1231   */
1232  public synchronized boolean addToken(Text alias,
1233                                       Token<? extends TokenIdentifier> token) {
1234    getCredentialsInternal().addToken(alias, token);
1235    return true;
1236  }
1237  
1238  /**
1239   * Obtain the collection of tokens associated with this user.
1240   * 
1241   * @return an unmodifiable collection of tokens associated with user
1242   */
1243  public synchronized
1244  Collection<Token<? extends TokenIdentifier>> getTokens() {
1245    return Collections.unmodifiableCollection(
1246        getCredentialsInternal().getAllTokens());
1247  }
1248
1249  /**
1250   * Obtain the tokens in credentials form associated with this user.
1251   * 
1252   * @return Credentials of tokens associated with this user
1253   */
1254  public synchronized Credentials getCredentials() {
1255    Credentials creds = new Credentials(getCredentialsInternal());
1256    Iterator<Token<?>> iter = creds.getAllTokens().iterator();
1257    while (iter.hasNext()) {
1258      if (iter.next() instanceof Token.PrivateToken) {
1259        iter.remove();
1260      }
1261    }
1262    return creds;
1263  }
1264  
1265  /**
1266   * Add the given Credentials to this user.
1267   * @param credentials of tokens and secrets
1268   */
1269  public synchronized void addCredentials(Credentials credentials) {
1270    getCredentialsInternal().addAll(credentials);
1271  }
1272
1273  private synchronized Credentials getCredentialsInternal() {
1274    final Credentials credentials;
1275    final Set<Credentials> credentialsSet =
1276      subject.getPrivateCredentials(Credentials.class);
1277    if (!credentialsSet.isEmpty()){
1278      credentials = credentialsSet.iterator().next();
1279    } else {
1280      credentials = new Credentials();
1281      subject.getPrivateCredentials().add(credentials);
1282    }
1283    return credentials;
1284  }
1285
1286  /**
1287   * Get the group names for this user.
1288   * @return the list of users with the primary group first. If the command
1289   *    fails, it returns an empty list.
1290   */
1291  public synchronized String[] getGroupNames() {
1292    ensureInitialized();
1293    try {
1294      List<String> result = groups.getGroups(getShortUserName());
1295      return result.toArray(new String[result.size()]);
1296    } catch (IOException ie) {
1297      LOG.warn("No groups available for user " + getShortUserName());
1298      return new String[0];
1299    }
1300  }
1301  
1302  /**
1303   * Return the username.
1304   */
1305  @Override
1306  public String toString() {
1307    StringBuilder sb = new StringBuilder(getUserName());
1308    sb.append(" (auth:"+getAuthenticationMethod()+")");
1309    if (getRealUser() != null) {
1310      sb.append(" via ").append(getRealUser().toString());
1311    }
1312    return sb.toString();
1313  }
1314
1315  /**
1316   * Sets the authentication method in the subject
1317   * 
1318   * @param authMethod
1319   */
1320  public synchronized 
1321  void setAuthenticationMethod(AuthenticationMethod authMethod) {
1322    user.setAuthenticationMethod(authMethod);
1323  }
1324
1325  /**
1326   * Sets the authentication method in the subject
1327   * 
1328   * @param authMethod
1329   */
1330  //TODO: Delete this method once AuthMethod is deprecated.
1331  public void setAuthenticationMethod(AuthMethod authMethod) {
1332    switch (authMethod) {
1333      case SIMPLE:
1334        user.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
1335        break;
1336      case KERBEROS: {
1337        user.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
1338        break;
1339      }
1340      case DIGEST:
1341      case TOKEN: {
1342        user.setAuthenticationMethod(AuthenticationMethod.TOKEN);
1343        break;
1344      }
1345      default:
1346        user.setAuthenticationMethod(null);
1347    }
1348  }
1349
1350  /**
1351   * Get the authentication method from the subject
1352   * 
1353   * @return AuthenticationMethod in the subject, null if not present.
1354   */
1355  public synchronized AuthenticationMethod getAuthenticationMethod() {
1356    return user.getAuthenticationMethod();
1357  }
1358
1359  /**
1360   * Get the authentication method from the real user's subject.  If there
1361   * is no real user, return the given user's authentication method.
1362   * 
1363   * @return AuthenticationMethod in the subject, null if not present.
1364   */
1365  public synchronized AuthenticationMethod getRealAuthenticationMethod() {
1366    UserGroupInformation ugi = getRealUser();
1367    if (ugi == null) {
1368      ugi = this;
1369    }
1370    return ugi.getAuthenticationMethod();
1371  }
1372
1373  /**
1374   * Returns the authentication method of a ugi. If the authentication method is
1375   * PROXY, returns the authentication method of the real user.
1376   * 
1377   * @param ugi
1378   * @return AuthenticationMethod
1379   */
1380  public static AuthenticationMethod getRealAuthenticationMethod(
1381      UserGroupInformation ugi) {
1382    AuthenticationMethod authMethod = ugi.getAuthenticationMethod();
1383    if (authMethod == AuthenticationMethod.PROXY) {
1384      authMethod = ugi.getRealUser().getAuthenticationMethod();
1385    }
1386    return authMethod;
1387  }
1388
1389  /**
1390   * Compare the subjects to see if they are equal to each other.
1391   */
1392  @Override
1393  public boolean equals(Object o) {
1394    if (o == this) {
1395      return true;
1396    } else if (o == null || getClass() != o.getClass()) {
1397      return false;
1398    } else {
1399      return subject == ((UserGroupInformation) o).subject;
1400    }
1401  }
1402
1403  /**
1404   * Return the hash of the subject.
1405   */
1406  @Override
1407  public int hashCode() {
1408    return System.identityHashCode(subject);
1409  }
1410
1411  /**
1412   * Get the underlying subject from this ugi.
1413   * @return the subject that represents this user.
1414   */
1415  public Subject getSubject() {
1416    return subject;
1417  }
1418
1419  /**
1420   * Run the given action as the user.
1421   * @param <T> the return type of the run method
1422   * @param action the method to execute
1423   * @return the value from the run method
1424   */
1425  @InterfaceAudience.Public
1426  @InterfaceStability.Evolving
1427  public <T> T doAs(PrivilegedAction<T> action) {
1428    logPrivilegedAction(subject, action);
1429    return Subject.doAs(subject, action);
1430  }
1431  
1432  /**
1433   * Run the given action as the user, potentially throwing an exception.
1434   * @param <T> the return type of the run method
1435   * @param action the method to execute
1436   * @return the value from the run method
1437   * @throws IOException if the action throws an IOException
1438   * @throws Error if the action throws an Error
1439   * @throws RuntimeException if the action throws a RuntimeException
1440   * @throws InterruptedException if the action throws an InterruptedException
1441   * @throws UndeclaredThrowableException if the action throws something else
1442   */
1443  @InterfaceAudience.Public
1444  @InterfaceStability.Evolving
1445  public <T> T doAs(PrivilegedExceptionAction<T> action
1446                    ) throws IOException, InterruptedException {
1447    try {
1448      logPrivilegedAction(subject, action);
1449      return Subject.doAs(subject, action);
1450    } catch (PrivilegedActionException pae) {
1451      Throwable cause = pae.getCause();
1452      if (LOG.isDebugEnabled()) {
1453        LOG.debug("PrivilegedActionException as:" + this + " cause:" + cause);
1454      }
1455      if (cause instanceof IOException) {
1456        throw (IOException) cause;
1457      } else if (cause instanceof Error) {
1458        throw (Error) cause;
1459      } else if (cause instanceof RuntimeException) {
1460        throw (RuntimeException) cause;
1461      } else if (cause instanceof InterruptedException) {
1462        throw (InterruptedException) cause;
1463      } else {
1464        throw new UndeclaredThrowableException(cause);
1465      }
1466    }
1467  }
1468
1469  private void logPrivilegedAction(Subject subject, Object action) {
1470    if (LOG.isDebugEnabled()) {
1471      // would be nice if action included a descriptive toString()
1472      String where = new Throwable().getStackTrace()[2].toString();
1473      LOG.debug("PrivilegedAction as:"+this+" from:"+where);
1474    }
1475  }
1476
1477  private void print() throws IOException {
1478    System.out.println("User: " + getUserName());
1479    System.out.print("Group Ids: ");
1480    System.out.println();
1481    String[] groups = getGroupNames();
1482    System.out.print("Groups: ");
1483    for(int i=0; i < groups.length; i++) {
1484      System.out.print(groups[i] + " ");
1485    }
1486    System.out.println();    
1487  }
1488
1489  /**
1490   * A test method to print out the current user's UGI.
1491   * @param args if there are two arguments, read the user from the keytab
1492   * and print it out.
1493   * @throws Exception
1494   */
1495  public static void main(String [] args) throws Exception {
1496  System.out.println("Getting UGI for current user");
1497    UserGroupInformation ugi = getCurrentUser();
1498    ugi.print();
1499    System.out.println("UGI: " + ugi);
1500    System.out.println("Auth method " + ugi.user.getAuthenticationMethod());
1501    System.out.println("Keytab " + ugi.isKeytab);
1502    System.out.println("============================================================");
1503    
1504    if (args.length == 2) {
1505      System.out.println("Getting UGI from keytab....");
1506      loginUserFromKeytab(args[0], args[1]);
1507      getCurrentUser().print();
1508      System.out.println("Keytab: " + ugi);
1509      System.out.println("Auth method " + loginUser.user.getAuthenticationMethod());
1510      System.out.println("Keytab " + loginUser.isKeytab);
1511    }
1512  }
1513
1514}