/* * 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.login; import alluxio.Configuration; import alluxio.PropertyKey; import alluxio.security.User; import java.io.IOException; import java.util.Map; import javax.annotation.concurrent.NotThreadSafe; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; /** * An app login module that creates a user based on the user name provided through application * configuration. Specifically, through Alluxio property alluxio.security.login.username. This * module is useful if multiple Alluxio clients running under same OS user name want to get * different identifies (for resource and data management), or if Alluxio clients running under * different OS user names want to get same identify. */ @NotThreadSafe public final class AppLoginModule implements LoginModule { private Subject mSubject; private User mUser; private CallbackHandler mCallbackHandler; /** * Constructs a new {@link AppLoginModule}. */ public AppLoginModule() {} @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { mSubject = subject; mCallbackHandler = callbackHandler; } /** * Retrieves the user name by querying the property of * {@link PropertyKey#SECURITY_LOGIN_USERNAME} through {@link AppCallbackHandler}. * * @return true if user name provided by application is set and not empty * @throws LoginException when the login fails */ @Override public boolean login() throws LoginException { Callback[] callbacks = new Callback[1]; callbacks[0] = new NameCallback("user name: "); try { mCallbackHandler.handle(callbacks); } catch (IOException | UnsupportedCallbackException e) { throw new LoginException(e.getMessage()); } String userName = ((NameCallback) callbacks[0]).getName(); if (!userName.isEmpty()) { mUser = new User(userName); return true; } return false; } /** * Aborts the authentication (second phase). * * <p> * This method is called if the LoginContext's overall authentication failed. (login failed) It * cleans up any state that was changed in the login and commit methods. * * @return true in all cases * @throws LoginException when the abortion fails */ @Override public boolean abort() throws LoginException { logout(); mUser = null; return true; } /** * Commits the authentication (second phase). * * <p> * This method is called if the LoginContext's overall authentication succeeded. The * implementation first checks if there is already Alluxio user in the subject. If not, it adds * the previously logged in Alluxio user into the subject. * * @return true if an Alluxio user is found or created * @throws LoginException not Alluxio user is found or created */ @Override public boolean commit() throws LoginException { // if there is already an Alluxio user, it's done. if (!mSubject.getPrincipals(User.class).isEmpty()) { return true; } // add the logged in user into subject if (mUser != null) { mSubject.getPrincipals().add(mUser); return true; } // throw exception if no Alluxio user is found or created. throw new LoginException("Cannot find a user"); } /** * Logs out the user * * <p> * The implementation removes the User associated with the Subject. * * @return true in all cases * @throws LoginException if logout fails */ @Override public boolean logout() throws LoginException { if (mSubject.isReadOnly()) { throw new LoginException("logout Failed: Subject is Readonly."); } if (mUser != null) { mSubject.getPrincipals().remove(mUser); } return true; } /** * A callback handler for {@link AppLoginModule}. */ @NotThreadSafe public static final class AppCallbackHandler implements CallbackHandler { private String mUserName; /** * Creates a new instance of {@link AppCallbackHandler}. */ public AppCallbackHandler() { if (Configuration.containsKey(PropertyKey.SECURITY_LOGIN_USERNAME)) { mUserName = Configuration.get(PropertyKey.SECURITY_LOGIN_USERNAME); } else { mUserName = ""; } } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callback; nameCallback.setName(mUserName); } else { Class<?> callbackClass = (callback == null) ? null : callback.getClass(); throw new UnsupportedCallbackException(callback, callbackClass + " is unsupported."); } } } } }