/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.security;

import alluxio.Configuration;
import alluxio.PropertyKey;
import alluxio.security.authentication.AuthType;
import alluxio.security.login.AppLoginModule;
import alluxio.security.login.LoginModuleConfiguration;

import java.io.IOException;
import java.util.Set;

import javax.annotation.concurrent.ThreadSafe;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
 * A Singleton of LoginUser, which is an instance of {@link alluxio.security.User}. It represents
 * the user of Alluxio client, when connecting to Alluxio service.
 *
 * The implementation of getting a login user supports Windows, Unix, and Kerberos login modules.
 *
 * This singleton uses lazy initialization.
 */
@ThreadSafe
public final class LoginUser {

  /** User instance of the login user in Alluxio client process. */
  private static User sLoginUser;

  private LoginUser() {} // prevent instantiation

  /**
   * Gets current singleton login user. This method is called to identify the singleton user who
   * runs Alluxio client. When Alluxio client gets a user by this method and connects to Alluxio
   * service, this user represents the client and is maintained in service.
   *
   * @return the login user
   * @throws IOException if login fails
   */
  public static User get() throws IOException {
    if (sLoginUser == null) {
      synchronized (LoginUser.class) {
        if (sLoginUser == null) {
          sLoginUser = login();
        }
      }
    }
    return sLoginUser;
  }

  /**
   * Logs in based on the LoginModules.
   *
   * @return the login user
   * @throws IOException if login fails
   */
  private static User login() throws IOException {
    AuthType authType =
        Configuration.getEnum(PropertyKey.SECURITY_AUTHENTICATION_TYPE, AuthType.class);
    checkSecurityEnabled(authType);
    Subject subject = new Subject();

    try {
      CallbackHandler callbackHandler = null;
      if (authType.equals(AuthType.SIMPLE) || authType.equals(AuthType.CUSTOM)) {
        callbackHandler = new AppLoginModule.AppCallbackHandler();
      }

      // Create LoginContext based on authType, corresponding LoginModule should be registered
      // under the authType name in LoginModuleConfiguration.
      LoginContext loginContext =
          new LoginContext(authType.getAuthName(), subject, callbackHandler,
              new LoginModuleConfiguration());
      loginContext.login();
    } catch (LoginException e) {
      throw new IOException("Failed to login: " + e.getMessage(), e);
    }

    Set<User> userSet = subject.getPrincipals(User.class);
    if (userSet.isEmpty()) {
      String msg = String.format("Failed to login: No Alluxio User is found. Current login "
          + "principals are %s.%n If you are running hadoop with Alluxio, please double check "
          + "whether you have alluxio client jar included in HADOOP_CLASSPATH. Otherwise, you "
          + "are expected to see this error.", subject.getPrincipals().toString());
      throw new IOException(msg);
    }
    if (userSet.size() > 1) {
      String msg = String.format("Failed to login: More than one Alluxio User is found. Current "
          + "login principals are %s.", subject.getPrincipals().toString());
      throw new IOException(msg);
    }
    return userSet.iterator().next();
  }

  /**
   * Checks whether Alluxio is running in secure mode, such as {@link AuthType#SIMPLE},
   * {@link AuthType#KERBEROS}, {@link AuthType#CUSTOM}.
   *
   * @param authType the authentication type in configuration
   */
  private static void checkSecurityEnabled(AuthType authType) {
    // TODO(dong): add Kerberos condition check.
    if (authType != AuthType.SIMPLE && authType != AuthType.CUSTOM) {
      throw new UnsupportedOperationException("User is not supported in " + authType.getAuthName()
          + " mode");
    }
  }
}

@@ -3265,34 +3265,29 @@
     
-String msg = String.format
+throw new IOException
 (%22Fa
@@ -3330,332 +3330,9 @@
 und.
- Current login %22%0A          + %22principals are %25s.%25n If you are running hadoop with Alluxio, please double check %22%0A          + %22whether you have alluxio client jar included in HADOOP_CLASSPATH. Otherwise, you %22%0A          + %22are expected to see this error.%22, subject.getPrincipals().toString());%0A      throw new IOException(msg
+%22
 );%0A 
@@ -3382,29 +3382,51 @@
 ring
+Builder
  msg =
+ new
  String
-.format(
+Builder(%0A          
 %22Fai
@@ -3469,92 +3469,93 @@
 User
- is
+s are
  found
-. Current %22%0A          + %22login principals are %25s.%22, subject.getPrincipals()
+:%22);%0A      for (User user : userSet) %7B%0A        msg.append(%22 %22).append(user
 .toS
@@ -3560,24 +3560,32 @@
 oString());%0A
+      %7D%0A
       throw 
@@ -3603,16 +3603,27 @@
 tion(msg
+.toString()
 );%0A    %7D

Remove irrelavent message