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