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; } }