/*
* 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.exception.status.UnauthenticatedException;
import alluxio.security.authentication.AuthType;
import alluxio.security.login.AppLoginModule;
import alluxio.security.login.LoginModuleConfiguration;
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
*/
public static User get() throws UnauthenticatedException {
if (sLoginUser == null) {
synchronized (LoginUser.class) {
if (sLoginUser == null) {
sLoginUser = login();
}
}
}
return sLoginUser;
}
/**
* Logs in based on the LoginModules.
*
* @return the login user
*/
private static User login() throws UnauthenticatedException {
AuthType authType =
Configuration.getEnum(PropertyKey.SECURITY_AUTHENTICATION_TYPE, AuthType.class);
checkSecurityEnabled(authType);
Subject subject = new Subject();
try {
// Use the class loader of User.class to construct the LoginContext. LoginContext uses this
// class loader to dynamically instantiate login modules. This enables
// Subject#getPrincipals to use reflection to search for User.class instances.
LoginContext loginContext = createLoginContext(authType, subject, User.class.getClassLoader(),
new LoginModuleConfiguration());
loginContext.login();
} catch (LoginException e) {
throw new UnauthenticatedException("Failed to login: " + e.getMessage(), e);
}
Set<User> userSet = subject.getPrincipals(User.class);
if (userSet.isEmpty()) {
throw new UnauthenticatedException("Failed to login: No Alluxio User is found.");
}
if (userSet.size() > 1) {
StringBuilder msg = new StringBuilder(
"Failed to login: More than one Alluxio Users are found:");
for (User user : userSet) {
msg.append(" ").append(user.toString());
}
throw new UnauthenticatedException(msg.toString());
}
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");
}
}
/**
* Creates a new {@link LoginContext} with the correct class loader.
*
* @param authType the {@link AuthType} to use
* @param subject the {@link Subject} to use
* @param classLoader the {@link ClassLoader} to use
* @param configuration the {@link javax.security.auth.login.Configuration} to use
* @return the new {@link LoginContext} instance
* @throws LoginException if LoginContext cannot be created
*/
private static LoginContext createLoginContext(AuthType authType, Subject subject,
ClassLoader classLoader, javax.security.auth.login.Configuration configuration)
throws LoginException {
CallbackHandler callbackHandler = null;
if (authType.equals(AuthType.SIMPLE) || authType.equals(AuthType.CUSTOM)) {
callbackHandler = new AppLoginModule.AppCallbackHandler();
}
ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
// Create LoginContext based on authType, corresponding LoginModule should be registered
// under the authType name in LoginModuleConfiguration.
return new LoginContext(authType.getAuthName(), subject, callbackHandler, configuration);
} finally {
Thread.currentThread().setContextClassLoader(previousClassLoader);
}
}
}