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