/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.authentication;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.apache.log4j.Logger;
import org.ow2.proactive.authentication.principals.UserNamePrincipal;
import net.sf.jpam.Pam;
import net.sf.jpam.PamReturnValue;
/**
* Authentication based on user and group file.
*
* @author The ProActive Team
* @since ProActive Scheduling 0.9.1
*/
public abstract class PAMLoginModule extends FileLoginModule implements Loggable {
/**
* connection logger
*/
private final Logger logger = getLogger();
/**
* PAM module name to be installed in the pam configuration
**/
public static final String PAM_MODULE_NAME = "proactive-jpam";
/**
* authentication status
*/
private boolean succeeded = false;
private final Pam pam;
public PAMLoginModule() {
pam = new Pam(PAM_MODULE_NAME);
}
/**
* @see LoginModule#initialize(Subject, CallbackHandler, Map, Map)
*/
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
}
/**
* Authenticate the user by getting the user name and password from the
* CallbackHandler.
* <p>
* <p>
*
* @return true in all cases since this <code>PAMLoginModule</code>
* should not be ignored.
* @throws FailedLoginException if the authentication fails.
* <p>
* @throws LoginException if this <code>LDAPLoginModule</code> is unable to
* perform the authentication.
*/
@Override
public boolean login() throws LoginException {
succeeded = false;
if (callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
}
try {
Callback[] callbacks = new Callback[] { new NoCallback() };
// gets the user name, password, group Membership, and group Hierarchy from call back handler
callbackHandler.handle(callbacks);
Map<String, Object> params = ((NoCallback) callbacks[0]).get();
String username = (String) params.get("username");
String password = (String) params.get("pw");
params.clear();
((NoCallback) callbacks[0]).clear();
if (username == null) {
logger.info("No username has been specified for authentication");
throw new FailedLoginException("No username has been specified for authentication");
}
succeeded = logUser(username, password);
return succeeded;
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " + "from the user");
}
}
/**
* Check user and password from file, or authenticate with PAM.
*
* @param username user's login
* @param password user's password
* @return true user login and password are correct, and requested group is authorized for the user
* @throws LoginException if authentication and group membership fails.
*/
protected boolean logUser(String username, String password) throws LoginException {
try {
return super.logUser(username, password, false);
} catch (LoginException ex) {
return pamLogUser(username, password);
}
}
private boolean pamLogUser(String username, String password) throws LoginException {
logger.debug("Authenticating user " + username + " with PAM.");
PamReturnValue answer = pam.authenticate(username, password);
if (answer.equals(PamReturnValue.PAM_SUCCESS)) {
subject.getPrincipals().add(new UserNamePrincipal(username));
super.groupMembershipFromFile(username);
return true;
} else {
logger.info("PAM authentication failed for user " + username + ": " + answer);
throw new FailedLoginException(answer.toString());
}
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules succeeded).
* <p>
*
* @return true if this LDAPLoginModule's own login and commit attempts
* succeeded, or false otherwise.
* @throws LoginException if the commit fails.
*/
@Override
public boolean commit() throws LoginException {
return succeeded;
}
/**
* <p>
* This method is called if the LoginContext's overall authentication
* failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules did not succeed).
* <p>
* <p>
* If this LDAPLoginModule's own authentication attempt succeeded (checked
* by retrieving the private state saved by the <code>login</code> and
* <code>commit</code> methods), then this method cleans up any state that
* was originally saved.
* <p>
* <p>
*
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
* @throws LoginException if the abort fails.
*/
@Override
public boolean abort() throws LoginException {
boolean result = succeeded;
succeeded = false;
return result;
}
/**
* Logout the user.
* <p>
*
* @return true in all cases since this <code>LoginModule</code> should
* not be ignored.
* @throws LoginException if the logout fails.
*/
@Override
public boolean logout() throws LoginException {
succeeded = false;
return true;
}
}