/*
* 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.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyException;
import java.security.PrivateKey;
import java.util.Map;
import java.util.Properties;
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.crypto.HybridEncryptionUtil;
import org.ow2.proactive.authentication.principals.GroupNamePrincipal;
import org.ow2.proactive.authentication.principals.UserNamePrincipal;
/**
* Authentication based on user and group file.
*
* @author The ProActive Team
* @since ProActive Scheduling 0.9.1
*/
public abstract class FileLoginModule implements Loggable, LoginModule {
/** connection logger */
private Logger logger = getLogger();
public static final String ENCRYPTED_DATA_SEP = " ";
/**
* JAAS call back handler used to get authentication request parameters
*/
protected CallbackHandler callbackHandler;
/** authentication status */
private boolean succeeded = false;
/** The file where to store the allowed user//password */
protected String loginFile = getLoginFileName();
/** The file where to store group management */
protected String groupFile = getGroupFileName();
protected Subject subject;
/**
* Defines login file name
*
* @return the login file name
*/
protected abstract String getLoginFileName();
/**
* Defines group file name
*
* @return the group file name
*/
protected abstract String getGroupFileName();
/**
* Defines private key
*
* @return private key in use
*/
protected abstract PrivateKey getPrivateKey() throws KeyException;
/**
*
* @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
*/
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
checkLoginFile();
checkGroupFile();
if (logger.isDebugEnabled()) {
logger.debug("Using Login file at : " + this.loginFile);
logger.debug("Using Group file at : " + this.groupFile);
}
this.callbackHandler = callbackHandler;
}
protected void checkLoginFile() {
//test login file existence
if (!(new File(this.loginFile).exists())) {
throw new RuntimeException("The file " + this.loginFile + " has not been found \n" +
"Unable to perform user authentication by file method");
}
}
protected void checkGroupFile() {
//test group file existence
if (!(new File(this.groupFile).exists())) {
throw new RuntimeException("The file " + this.groupFile + " has not been found \n" +
"Unable to perform user authentication by file method");
}
}
/**
*
* @see javax.security.auth.spi.LoginModule#login()
* @throws LoginException if userName of password are not correct
*/
public boolean login() throws LoginException {
succeeded = false;
// prompt for a user name and password
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 username, password, group Membership, and group Hierarchy from callback 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, true);
return succeeded;
} catch (java.io.IOException ioe) {
logger.error("", ioe);
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
logger.error("", uce);
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information from the user");
}
}
/**
* First Check user and password from login file. If user is authenticated,
* check group membership from group file.
* @param username user's login
* @param password user's password
* @param printErrorMessage if a message should be printed if the password is incorrect.
* @return true user login and password are correct, and requested group is authorized for the user
* @throws LoginException if authentication or group membership fails.
*/
protected boolean logUser(String username, String password, boolean printErrorMessage) throws LoginException {
if (!authenticateUserFromFile(username, password)) {
String message = "[" + FileLoginModule.class.getSimpleName() + "] Incorrect Username/Password";
if (printErrorMessage) {
logger.info(message);
} else {
logger.debug(message);
}
throw new FailedLoginException("Incorrect Username/Password");
}
subject.getPrincipals().add(new UserNamePrincipal(username));
groupMembershipFromFile(username);
logger.debug("authentication succeeded for user '" + username + "'");
return true;
}
/**
* Check user and password from login file.
* @param username user's login
* @param password user's password
* @return true if user is found in login file and its password is correct, falser otherwise
* @throws LoginException if login file is not found or unreadable.
*/
private boolean authenticateUserFromFile(String username, String password) throws LoginException {
Properties props = new Properties();
PrivateKey privateKey = null;
try {
privateKey = getPrivateKey();
} catch (KeyException e) {
throw new LoginException(e.toString());
}
try (FileInputStream stream = new FileInputStream(loginFile)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
props.load(reader);
} catch (FileNotFoundException e) {
throw new LoginException(e.toString());
} catch (IOException e) {
throw new LoginException(e.toString());
}
// verify the username and password
if (!props.containsKey(username)) {
return false;
} else {
String encryptedPassword = (String) props.get(username);
try {
if (!HybridEncryptionUtil.decryptBase64String(encryptedPassword, privateKey, ENCRYPTED_DATA_SEP)
.equals(password)) {
return false;
}
} catch (KeyException e) {
throw new LoginException(e.toString());
}
return true;
}
}
/**
* Return corresponding group for an user from the group file.
* @param username user's login
* @throws LoginException if group file is not found or unreadable.
*/
protected void groupMembershipFromFile(String username) throws LoginException {
try (FileInputStream stream = new FileInputStream(groupFile)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line = null;
while ((line = reader.readLine()) != null) {
String[] u2g = line.split(":");
if (u2g[0].trim().equals(username)) {
subject.getPrincipals().add(new GroupNamePrincipal(u2g[1]));
logger.debug("adding group principal '" + u2g[1] + "' for user '" + username + "'");
}
}
} catch (FileNotFoundException e) {
throw new LoginException(e.toString());
} catch (IOException e) {
throw new LoginException(e.toString());
}
}
/**
* @see javax.security.auth.spi.LoginModule#commit()
*/
public boolean commit() throws LoginException {
return succeeded;
}
/**
* @see javax.security.auth.spi.LoginModule#abort()
*/
public boolean abort() throws LoginException {
boolean result = succeeded;
succeeded = false;
return result;
}
/**
* @see javax.security.auth.spi.LoginModule#logout()
*/
public boolean logout() throws LoginException {
succeeded = false;
return true;
}
}