package com.fnic.business;
import static com.fnic.utils.Constants.*;
import java.io.*;
import java.util.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import models.*;
import org.apache.log4j.*;
import com.fnic.beans.form.*;
import com.fnic.beans.response.*;
import com.fnic.dao.*;
import com.fnic.userAccount.beans.data.*;
import customUtils.utils.StringUtils;
import com.fnic.security.jaas.*;
public class AS400LoginModule implements LoginModule {
public static Logger logger = Logger.getLogger(AS400LoginModule.class);
public static final String OPT_DEBUG = "debug";
// initial state
private Subject subject = null;
private CallbackHandler callbackHandler = null;
// configurable option
private static boolean debug = false;
// the authorization status
private boolean succeeded = false;
// the authentication status
private boolean commitSucceeded = false;
// user input
private UserForm inputBean = null;
// FNISCallbackHandler - Specific to Tomcat
FNISCallbackHandler fnisCBH = null;
/**
* Initialize this LoginModule.
*
* @param subject
* the Subject to be authenticated.
* @param callbackHandler
* a CallbackHandler for communicating with the end user
* (prompting for user names and passwords, for example).
* @param sharedState
* shared LoginModule state.
* @param options
* options specified in the login Configuration for this
* particular LoginModule
.
*/
public void initialize(
Subject subject,
CallbackHandler callbackHandler,
Map sharedState,
Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
// initialize any configured options
debug = TRUE.equalsIgnoreCase((String)options.get(OPT_DEBUG));
if (debug) {
logger.debug("AS400LoginModule.initialize: subject [principals]: " + subject.getPrincipals());
logger.debug("AS400LoginModule.initialize: options: " + options);
}
}
/**
* Phase I - Authorization Authorize the user by prompting for a user name
* and password. The password may contain the hash equivalent of the password.
*
* @return true in all cases since this LoginModule should not be ignored.
* @exception FailedLoginException
* if the authentication fails.
* @exception LoginException
* if this LoginModule is unable to perform the
* authentication.
*/
@SuppressWarnings("unused")
public boolean login() throws LoginException {
boolean isTrusted = false;
if (callbackHandler == null)
throw new AS400LoginException(MSG_NO_CALLBACK_HANDLER,
BusinessResult.RC.INTERNAL_ERROR);
// create the callback objects for user name and password...
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback(PROMPT_USER_NAME);
callbacks[1] = new PasswordCallback(PROMPT_PASSWORD, false);
// retrieve user, password, and isTrusted input parameters...
try {
try {
inputBean = new UserForm();
FNISCallbackHandler fnisCBH = new FNISCallbackHandler("FNIS", inputBean.getLogOnID(), inputBean.getPassword());
fnisCBH.handle(callbacks);
//callbackHandler.handle(callbacks);
} catch (Exception cbhe) {
logger.error("AS400LoginModule.login: Exception encountered during callback handle request.\n" +
cbhe.getMessage() + "\n" + cbhe.getStackTrace());
}
try {
String userName = ((NameCallback) callbacks[0]).getName().trim();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) {
// treat a NULL password as an empty password
tmpPassword = new char[0];
}
char pwdArray[] = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, pwdArray, 0, tmpPassword.length);
if (debug)
logger.debug("AS400LoginModule.login: username = " + userName);
if (userName == null) {
inputBean.setLogOnID("");
} else {
inputBean.setLogOnID(userName);
}
String password = "";
// Loop through the pwd array anc convert to a single string
for (int i = 0; i < pwdArray.length; i++) {
password += pwdArray[i];
}
if (password == null) {
inputBean.setPassword("");
} else {
inputBean.setPassword(password);
}
if (debug)
logger.debug("AS400LoginModule.login: username [" + inputBean.getLogOnID() +
"] password [" + inputBean.getPassword() + "]");
((PasswordCallback)callbacks[1]).clearPassword();
} catch (Exception pcbe) {
logger.error("AS400LoginModule.login: Exception encountered after callback handle request.\n" +
pcbe.getMessage() + "\n" + pcbe.getStackTrace());
}
try {
if (debug)
logger.debug("AS400LoginModule.login: Before processing choice callback");
callbacks = new Callback[1];
callbacks[0] = new ChoiceCallback(
PROMPT_IS_TRUSTED,
new String[] {FALSE, TRUE},
0, false);
FNISCallbackHandler fnisCBH = new FNISCallbackHandler("FNIS", inputBean.getLogOnID(), inputBean.getPassword());
fnisCBH.handle(callbacks);
if (debug)
logger.debug("AS400LoginModule.login: After processing choice callback handler");
isTrusted = StringUtils.toBoolean(((ChoiceCallback)callbacks[0]).getChoices()[((ChoiceCallback)callbacks[0]).getSelectedIndexes()[0]], false);
if (debug)
logger.debug("AS400LoginModule.login: Trusted user? [" + isTrusted + "]");
} catch (Exception ccbe) {
logger.error(String.format(MSG_DEBUG_LOGIN,
this.getClass().getName(),
inputBean.getLogOnID(),
inputBean.getPassword()));
}
} catch (Exception e) {
logger.error("AS400LoginModule.login: Exception encountered during initial callback request.\n" +
e.getMessage() + "\n" + e.getStackTrace());
throw new AS400LoginException(e.getMessage(), BusinessResult.RC.INTERNAL_ERROR);
}
// validate input...
ValidationStrategy valStrat = new ValidationStrategy();
valStrat.addValidationRule(UserForm.FIELDS.LOGON_ID.toString(), true, MSG_INVALID_USER_OR_PW);
if (!isTrusted)
valStrat.addValidationRule(UserForm.FIELDS.PASSWORD.toString(), true, MSG_INVALID_USER_OR_PW);
else
valStrat.addValidationRule(UserForm.FIELDS.PASSWORD.toString(), false, MSG_INVALID_USER_OR_PW);
inputBean.setValidationStrategy(valStrat);
if (!inputBean.isValid())
throw new AS400LoginException(inputBean.getValidationMessage(
inputBean.getInvalidFieldIDS().get(0)), BusinessResult.RC.BAD_PARAMETER);
// retrieve user record...
User user = UserAccountQueryDAO.getInstance().getUser(inputBean.getLogOnID(), com.fnic.Props.getProperty(PROP_JNDI_POOL_KEY));
if (user == null) {
throw new AS400LoginException(MSG_INVALID_USER_OR_PW, BusinessResult.RC.BAD_PARAMETER);
}
if (debug)
logger.debug("AS400LoginModule: found user account info for: " + user.getLogOnID());
UserForm existingUser = new UserForm(user);
PasswordRequirements reqs = null;
try {
reqs = UserAccountMaintenance.getInstance().getPasswordRequirements(existingUser.getPasswordRules());
inputBean.setPassword(UserAccountEdits.getInstance().setPasswordCase(inputBean.getPassword(), reqs));
} catch (IOException e1) {
throw new AS400LoginException(MSG_PW_REQ_ERROR, BusinessResult.RC.INTERNAL_ERROR);
}
// is the user account disabled? //GWC20120112 Adding loophole for 'catextca3' user for development testing.
if (!existingUser.isEnabled()) {
if (debug)
logger.debug("AS400LoginModule.login: After check for whether account is enabled.");
if (inputBean.getLogOnID().trim().equalsIgnoreCase("catextca3")) { //GWC20120112
try { //GWC20120112
UserAccountMaintenance uam = UserAccountMaintenance.getInstance(); //GWC20120112
uam.enableUser(inputBean.getLogOnID()); //GWC20120112
if (debug)
logger.debug("AS400LoginModule.login: " + inputBean.getLogOnID() + " account has been re-enabled.");
} catch (IOException uame) { //GWC20120112
logger.error("AS400LoginModule: Exception encountered during 'enable account' processing."); //GWC20120112
throw new AS400LoginException(MSG_USER_DISABLED, BusinessResult.RC.DATA_UNAVAILABLE); //GWC20120112
} //GWC20120112
} else { //GWC20120112
throw new AS400LoginException(MSG_USER_DISABLED, BusinessResult.RC.DATA_UNAVAILABLE); //GWC20120112
} //GWC20120112
}
// is the user active (equal to or past effective date)?
if (!existingUser.isActive())
throw new AS400LoginException(MSG_USER_NOT_ACTIVATED, BusinessResult.RC.DATA_UNAVAILABLE);
if (!isTrusted) {
// are clear passwords equal? If not did they pass in a hash?
//GWC20120112 Checking failure condition
if (debug) {
logger.debug("AS400LoginModule.login: user [ " + existingUser.getLogOnID() + " ] is not a trusted user account. ");
if (existingUser.isPasswordCorrect(inputBean.getPassword())) {
logger.debug("AS400LoginModule.login: existingUser.isPasswordCorrect = true");
}
if (user.getEncodedPassword().equals(inputBean.getPassword())) {
logger.debug("AS400LoginModule.login: user.getEncodedPassword equals inputBean.getPassword = true");
}
}
//GWC20120112 Checking failure condition
if (!existingUser.isPasswordCorrect(inputBean.getPassword()) &&
!user.getEncodedPassword().equals(inputBean.getPassword())) {
if (debug)//GWC20120112
logger.debug("AS400LoginModule.login: Preparing to check additional password requirements: max attempts");
// are we using max password attempts?
if (reqs.getMaxPasswordAttempts() < 1) // no... not used.
existingUser.setPasswordAttempts(0); // reset attempts to 0.
else { // yes, we're using them...
existingUser.incrementPasswordAttempts();
if (existingUser.getPasswordAttempts() >= reqs.getMaxPasswordAttempts())
existingUser.setEnabled(false);
}
// update the user account...
try {
if (debug)
logger.debug("AS400LoginModule: Updating user account info based on current login attempt.");
UserAccountUpdateDAO.getInstance().updateUserRecord(existingUser.asUser(), com.fnic.Props.getProperty(PROP_JNDI_POOL_KEY));
} catch (Exception e) {
logger.error("AS400LoginModule: Error encountered during user record update. " + e.getMessage() );
throw new AS400LoginException(MSG_INVALID_USER_OR_PW, BusinessResult.RC.BAD_PARAMETER);
}
}
else {
// good password resets attempts to 0...
if (existingUser.getPasswordAttempts() > 0) {
existingUser.setPasswordAttempts(0);
try {
UserAccountUpdateDAO.getInstance().updateUserRecord(existingUser.asUser(), com.fnic.Props.getProperty(PROP_JNDI_POOL_KEY));
} catch (Exception e) { }
}
}
}
// password expired?
String message = null;
if (reqs.getDaysUntilExpiration() > -1) {
if (debug) //GWC20120113 May have to bypass expiration check also for CATEXT3A
logger.debug("AS400LoginModule: Password expiration policy: Expire after: " + reqs.getDaysUntilExpiration() + " days");
if (existingUser.isPasswordExpired()) {
if (debug)
logger.debug("AS400LoginModule: Check of existing user indicates password is expired." );
message = MSG_EXPIRED_PW;
} else {
// password close to expiring?
message = UserAccountEdits.getInstance().isPasswordAboutToExpire(reqs, existingUser) ?
String.format(MSG_PW_WILL_EXPIRE, existingUser.getDaysUntilPasswordExpires()) :
MSG_LOGON_SUCCESSFUL;
}
}
else
message = MSG_LOGON_SUCCESSFUL;
if (debug)
logger.debug("AS400LoginModule.login: login message result = " + message);
// create the callback to output result message...
callbacks = new Callback[1];
callbacks[0] = new TextOutputCallback(TextOutputCallback.INFORMATION, message);
try {
if (debug)
logger.debug("AS400LoginModule.login: Calling the TextOutputCallback handler");
//callbackHandler.handle(callbacks);
if (fnisCBH == null) {
logger.debug("AS400LoginModule.login: re-creating FNISCallback handler");
fnisCBH = new FNISCallbackHandler("FNIS", existingUser.getLogOnID(), existingUser.getPassword());
}
fnisCBH.handle(callbacks);
} catch (Exception e) {
logger.error("AS400LoginModule.login: Exception encountered during TextOutput Callback request.\n" +
e.getMessage() + "\n" + e.getStackTrace());
throw new AS400LoginException(e.getMessage(), BusinessResult.RC.INTERNAL_ERROR);
}
succeeded = true;
return true;
}
/**
* Phase II - Authentication
*
* This method is called if the LoginContext's overall authentication
* succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules succeeded).
*
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the login method), then this
* method associates a SamplePrincipal with the Subject located in the
* LoginModule. If this LoginModule's own authentication attempted failed,
* then this method removes any state that was originally saved.
*
* @exception LoginException if the commit fails.
* @return true if this LoginModule's own login and commit attempts
* succeeded, or false otherwise.
*/
public boolean commit() throws LoginException {
if (debug)
logger.debug("AS400LoginModule.commit: Overall authentication was successful. Time to associate the principle with the subject."); //GWC20120113
if (succeeded == false) {
if (debug)
logger.debug("AS400LoginModule.commit: The succeeded flag is false. Returning to login context processing."); //GWC20120113
return false;
} else {
if (debug)
logger.debug("AS400LoginModule.commit: The authentication succeeded."); //GWC20120113
// add the user's identity...
FNISPrincipal prin = new FNISPrincipal(inputBean.getLogOnID());
if (debug)
logger.debug("AS400LoginModule.commit: Created the FNFPrincipal."); //GWC20120113
if (!subject.getPrincipals().contains(prin)) {
subject.getPrincipals().add(prin);
}
if (debug) {
logger.debug("AS400LoginModule.commit: Added FNISPrincipal [" + inputBean.getLogOnID() + "] to Subject.");
}
FNISGroup group = new FNISGroup("Fidelity_Users");
if (!subject.getPrincipals().contains(group)) {
subject.getPrincipals().add(group);
}
if (debug)
logger.debug("AS400LoginModule.commit: Added FNISGroup [Fidelity_Users] to Subject");
// retrieve the applications to which the user has access. These
// will be part of the user's public credentials...
List accessList =
UserAccountQueryDAO.getInstance().getUserAccess(inputBean.getLogOnID(), com.fnic.Props.getProperty(PROP_JNDI_POOL_KEY));
if (debug)
logger.debug("AS400LoginModule.commit: Completed fetching access permissions from database.");
if (accessList != null) { //GWC20120113 Added braces so trace statement can be added
prin.getApplications().addAll(accessList);
if (debug)
logger.debug("AS400LoginModule.commit: Completed adding access permissions to the FNISPrincipal.");
} else {
logger.error("AS400LoginModule.commit: application access list is null for user [" + inputBean.getLogOnID() + "]");
}
subject.setReadOnly();
// clean out state...
inputBean = null;
commitSucceeded = true;
if (debug)
logger.debug("AS400LoginModule.commit: Returning from successful commit processing.");
return true;
}
}
/**
* This method is called if the LoginContext's overall authentication
* failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
* LoginModules did not succeed).
*
* If this LoginModule's own authentication attempt succeeded (checked by
* retrieving the private state saved by the login and
* commit methods), then this method cleans up any state that
* was originally saved.
*
* @exception LoginException if the abort fails.
* @return false if this LoginModule's own login and/or commit attempts
* failed, and true otherwise.
*/
public boolean abort() throws LoginException {
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// login succeeded but overall authentication failed
succeeded = false;
inputBean = null;
} else {
// overall authentication succeeded and commit succeeded,
// but someone else's commit failed
logout();
}
return true;
}
/**
* Log the user out.
* This method removes the Principals that were added by
* the commit method.
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this LoginModule should not be ignored.
*/
public boolean logout() throws LoginException {
//subject.getPrincipals().clear(); //GWC20120113 subject is read-only. must destroy subject to clear.
subject = null; //GWC20120113 destroy subject instead.
succeeded = false;
succeeded = commitSucceeded;
inputBean = null;
return true;
}
}