/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.esr.service.impl;

//Java Classes
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;

import org.acegisecurity.GrantedAuthorityImpl;

import org.apache.commons.lang.Validate;

import gov.va.med.fw.model.AbstractKeyedEntity;
import gov.va.med.fw.model.BaseEntityKey;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.security.AccountDisabledException;
import gov.va.med.fw.security.AccountLockedException;
import gov.va.med.fw.security.InvalidPasswordException;
import gov.va.med.fw.security.InvalidSecurityCodeException;
import gov.va.med.fw.security.InsufficientPrivilegesException;
import gov.va.med.fw.security.PasswordChangeNotAllowedException;
import gov.va.med.fw.security.PasswordSuspendedException;
import gov.va.med.fw.security.SecurityContext;
import gov.va.med.fw.security.SecurityContextHelper;
import gov.va.med.fw.security.SecurityService;
import gov.va.med.fw.security.PasswordEncryptionService;
import gov.va.med.fw.security.UserPrincipal;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.ServiceOptimisticLockException;
import gov.va.med.fw.ui.security.ChangePasswordInfo;
import gov.va.med.fw.util.StringUtils;

import gov.va.med.esr.common.model.security.ESRUserPrincipal;
import gov.va.med.esr.common.model.security.ESRUserPrincipalImpl;
import gov.va.med.esr.common.model.security.UserLastLoginInfo;
import gov.va.med.esr.common.model.security.UserLogin;
import gov.va.med.esr.common.model.system.SystemParameter;
import gov.va.med.esr.common.model.lookup.Capability;
import gov.va.med.esr.common.persistent.security.SecurityDAO;

import gov.va.med.esr.service.PasswordChangeHistory;
import gov.va.med.esr.service.SystemParameterService;

/**
 * @author DNS   mansog
 * @since Mar 17, 2005
 */
public class SecurityServiceImpl extends AbstractRuleAwareServiceImpl implements SecurityService
{
    protected static final long DAY_MILLISECONDS               = 24 * 60 * 60 * 1000;

    private SecurityDAO         securityDAO                    = null;
    private SystemParameterService systemParameterService = null;
    private PasswordEncryptionService passwordEncryptionService = null;

    private int  passwordChangeWarningDays = 15;                 //default value
    //CCR12708- auto account reset is no longer allowed
    // private int  lockTimeOnFailedAttemptsInMins = 15;

    public SecurityServiceImpl() {
        super();
    }

    /**
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        Validate.notNull(securityDAO, "SecurityDAO is required");
        Validate.notNull(systemParameterService, "SystemParameterService is required");

        //CCR12708-Users accounts no longer get reset after 15 minutes. So, no need to have this property.
        //validateNotLess(lockTimeOnFailedAttemptsInMins, 1,
        //"lockTimeOnFailedAttemptsInMins must be greater than 0.");
        validateNotLess(passwordChangeWarningDays, 1,
        "passwordChangeWarningDays must be greater than 0 ");
    }

    private void validateNotLess(int property, int value, String message) {
        if (property < value) {
            throw new IllegalArgumentException(message);
        }
    }

    /**
     * @return Returns the securityDAO.
     */
    public SecurityDAO getSecurityDAO() {
        return this.securityDAO;
    }

    /**
     * @param securityDAO The securityDAO to set.
     */
    public void setSecurityDAO(SecurityDAO securityDAO) {
        this.securityDAO = securityDAO;
    }

    public SystemParameterService getSystemParameterService() {
        return systemParameterService;
    }

    public void setSystemParameterService(
            SystemParameterService systemParameterService) {
        this.systemParameterService = systemParameterService;
    }

    public PasswordEncryptionService getPasswordEncryptionService() {
        return passwordEncryptionService;
    }

    public void setPasswordEncryptionService(
            PasswordEncryptionService passwordEncryptionService) {
        this.passwordEncryptionService = passwordEncryptionService;
    }

    //CCR12708-Users accounts no longer get reset after 15 minutes. So, no need to have this property.
    /*public int getLockTimeOnFailedAttemptsInMins() {
        return lockTimeOnFailedAttemptsInMins;
    }

    public void setLockTimeOnFailedAttemptsInMins(
            int lockTimeOnFailedAttemptsInMins) {
        this.lockTimeOnFailedAttemptsInMins = lockTimeOnFailedAttemptsInMins;
    }*/

    public int getPasswordChangeWarningDays() {
        return passwordChangeWarningDays;
    }

    public void setPasswordChangeWarningDays(int passwordChangeWarningDays) {
        this.passwordChangeWarningDays = passwordChangeWarningDays;
    }

    /*
     * Get the UserPrincipal given the user identifier. Returns null if no user
     * exists.
     *
     * @see gov.va.med.esr.service.SecurityService#getById(java.lang.String)
     */
    public UserPrincipal getUserById(EntityKey userId) throws ServiceException {
        try {
            ESRUserPrincipalImpl user = (ESRUserPrincipalImpl) securityDAO.getUserById(userId);
            if (user != null) {
                checkVersionNumbers(userId, user.getEntityKey());
                return user;
            }
        } catch (DAOException e) {
            throw new ServiceException("Error finding user with id " + userId
                    + ".", e);
        }
        return null;
    }

    /* (non-Javadoc)
     * @see gov.va.med.fw.security.SecurityService#authenticate(java.lang.String, java.lang.String)
     */
    public Subject authenticate(String username, char[] password)
            throws LoginException {
        ESRUserPrincipal userPrincipal = authenticateUser(username, password);
        try {
            Map systemProps = getSystemProperties();
            onLoginSuccess((ESRUserPrincipalImpl) userPrincipal,systemProps);
        } catch (DAOException e1) {
            logger.error("Exception updating user logon status information.",
                    e1);
        }catch(ServiceException e2){
            logger.error("Exception updating user logon status information.",
                    e2);
        }

        Subject subject = new Subject();
        subject.getPrincipals().add(userPrincipal);
        return subject;
    }

    /**
     * Authenticate the user given username and password. This method throws
     * LoginException if authentication is not successful for any reason.
     * Different LoginExcpetion's are thrown depending upon the type of
     * failure occurs.
     * If there are consecutive failure occurs, this method also
     * lock the user account.
     *
     * @param username
     * @param password
     * @return <code>ESRUserPrincipal</code>.
     * @throws FailedLoginException
     *             if user does not exists or bad username/password combination.
     * @throws AccountAlreadyLockedException
     *             if the user account is currently locked.
     * @throws AccountLockedException
     *             if the user account was not locked before but is locked now because of invalid credentials.
     * @throws AccountExpiredException
     *             if the user account is expired.
     * @throws CredentialExpiredException
     *             if the user password is expired.
     * @throws LoginException
     *             if there is an other error authenticating the user.
     */
    private ESRUserPrincipal authenticateUser(String username, char[] password)
            throws LoginException {

        ESRUserPrincipalImpl userPrincipal = null;
        Map systemProps = null;
        try {
            userPrincipal = (ESRUserPrincipalImpl) getUserByName(username);
            systemProps = getSystemProperties();
        } catch (ServiceException e) {
            LoginException ex = new LoginException("Error authenticating user " + username +
                ". Reason: " + e.toString());
            ex.initCause(e);
            throw ex;
        }
        if (userPrincipal == null) {
        	throw new FailedLoginException(
                    "User Account for " + username + " Does Not Exist.");
        }

        //If not found in the database default values are used
        if (systemProps == null)
            systemProps = new HashMap ();

        lockInactiveUser(userPrincipal);

        //check for account locked
        if (isAccountLocked(userPrincipal)) {
            throw new AccountAlreadyLockedException("User account for " + username
                    + " is locked.");
        }
        else if(!isAuthorizedToLogin(userPrincipal)) {
        	throw new InsufficientPrivilegesException(username + " does not have Login to System Capability");
        }
        //CCR13343 SSOi integration
        //remove all validation on passwords/account disabled/expired
        /*
        else if (isPasswordSuspended(userPrincipal)) {
            throw new PasswordSuspendedException("User password for " + username + " is suspended");
        }
        else if (!verifyPassword(password, userPrincipal.getPassword())) {
            try {
                log the failed login information.
                onLoginFailed(userPrincipal,getNoOfFailedAttemptsToLock(systemProps));
            } catch (DAOException e1) {
                @TODO: What should we do here ?
                logger.error(
                        "Exception updating user logon failure information.",
                        e1);
            }
            throw new FailedLoginException(
                    "Invalid username/password combination.");
        }
          else if (isAccountExpired(userPrincipal,
                getPasswordActiveDays(systemProps),
                getPasswordExpireDays(systemProps))){
            throw new AccountExpiredException("User account for " + username
                    + " is expired.");
        }
    	else if (isPasswordExpired(userPrincipal,getPasswordActiveDays(systemProps))) {
            throw new CredentialExpiredException("User Credentials for "
                    + username + " are expired.");
        } else if (isAccountDisabled(userPrincipal)) {
            throw new AccountDisabledException("User account for " + username
                    + " is disabled.");
        }*/
        return userPrincipal;
    }

    private void lockInactiveUser(ESRUserPrincipalImpl userPrincipal) {
    	 //lock the account if no activity in one year
        if (userPrincipal.getLockDate() == null) {
	    	Calendar ayearAgo = Calendar.getInstance();
	        ayearAgo.setTime(getCurrentDate());
	        ayearAgo.add(Calendar.YEAR, -1);

	        Date lastLoginDate = userPrincipal.getSuccessfulLoginDate();
	        Date lastModified = userPrincipal.getModifiedOn();

	        if (lastLoginDate != null && lastLoginDate.before(ayearAgo.getTime()) && lastModified.before(ayearAgo.getTime())) {
	        	userPrincipal.setLockDate(lastLoginDate);
	        	try {
					securityDAO.update(userPrincipal);
				} catch (DAOException el) {
					logger.error("Exception updating account lock information." + el.getMessage());
				}
	        }
        }
    }

    private boolean verifyPassword(String clearText, String dbPassword) {
        //check both are same if not encrypt and compare
        //return false if passwords are empty
        if (StringUtils.isEmpty(clearText) || StringUtils.isEmpty(dbPassword))
        {
            return false;
        }

        try {
            if (dbPassword.equals(getPasswordEncryptionService().encryptPassword(clearText)) ||
                    dbPassword.equals(clearText))
            {
                return true;
            }
        }
        catch(Exception e) {
            //ignore encryption exception
            logger.error("Password Compare error " + e);
        }
        return false;
    }


    /*
     * Lock the user account by setting the lock date to current date.
     *
     * @see gov.va.med.esr.service.SecurityService#lockAccount(java.lang.String)
     */
    public void lockAccount(EntityKey userId) throws ServiceException {
        ESRUserPrincipalImpl userPrincipal = getUserPrincipal(userId);
        setLockProperties(userPrincipal);
        try {
            securityDAO.update(userPrincipal);
        } catch (DAOException e) {
            throw new ServiceException("Failed to lock a user account", e);
        }
    }

    /*
     * Unlock the user account by setting the lock date to NULL and reset the
     * failed attempt counter.
     *
     * @see gov.va.med.esr.service.SecurityService#unlockAccount(java.lang.String)
     */
    public void unlockAccount(EntityKey userId) throws ServiceException {
        ESRUserPrincipalImpl userPrincipal = getUserPrincipal(userId);
        resetLockProperties(userPrincipal);
        try {
            securityDAO.update(userPrincipal);
        } catch (DAOException e) {
            throw new ServiceException("Failed to unlock a user account", e);
        }
    }

    /*
     * Enable the account by setting the inactive date to NULL.
     *
     * @see gov.va.med.esr.service.SecurityService#enableAccount(java.lang.String)
     */
    public void enableAccount(EntityKey userId) throws ServiceException {
        ESRUserPrincipalImpl userPrincipal = getUserPrincipal(userId);
        userPrincipal.setInActiveDate(null);
        try {
            securityDAO.update(userPrincipal);
        } catch (DAOException e) {
            throw new ServiceException("Failed to enable a user account", e);
        }
    }

    /*
     * Disable the account by setting the inactive date to current date.
     *
     * @see gov.va.med.esr.service.SecurityService#disableAccount(java.lang.String)
     */
    public void disableAccount(EntityKey userId) throws ServiceException {
        ESRUserPrincipalImpl userPrincipal = getUserPrincipal(userId);
        userPrincipal.setInActiveDate(getCurrentDate());
        try {
            securityDAO.update(userPrincipal);
        } catch (DAOException e) {
            throw new ServiceException("Failed to disable a user account", e);
        }
    }

    /**
     * Change user password.
     *
     * @param changePasswordInfo
     * @throws ServiceException if there is any error.
     * @throws LoginException
     */
    public void changePassword(ChangePasswordInfo changePasswordInfo)
            throws LoginException, ServiceException {
        //TODO: Need to revisit to make sure its doing its function.
        Validate.notNull(changePasswordInfo,
                "ChangePasswordInfo can not be null");

        String username = changePasswordInfo.getUserID();
        String password = changePasswordInfo.getPassword();
        String newPassword = changePasswordInfo.getNewPassword();

        Validate.notNull(username, "user name can not be null");
        Validate.notNull(password, "password can not be null");
        Validate.notNull(newPassword, "new password can not be null");
        ESRUserPrincipalImpl userPrincipal = null;
        try {
            userPrincipal = (ESRUserPrincipalImpl) authenticateUser(username,
                    (password != null) ? password.toCharArray() : null);
            //verify for password reuse
            validatePasswordReuse(userPrincipal, newPassword);

        } catch (CredentialExpiredException ex) {
                //Password change is allowed when credentials are expired.
                //load the UserPrincipal directly in this case.
                userPrincipal = getUserByName(username);
        } catch (LoginException ex) {
            throw ex;
        }
        resetPasswordProperties(userPrincipal,
                getPasswordEncryptionService().encryptPassword(newPassword));
        try {
            securityDAO.updatePassword(userPrincipal);
        } catch (DAOException ex) {
            throw new ServiceException(
                    "Error while changing password. Reason: ", ex);
        }
    }

    /* (non-Javadoc)
     * @see gov.va.med.fw.security.SecurityService#resetPassword(java.lang.String, java.lang.String)
     */
    public void resetPassword(String username, String password)
            throws ServiceException {
        try {
            ESRUserPrincipalImpl userPrincipal = getUserByName(username);
            resetPasswordProperties(userPrincipal, password);
            securityDAO.update(userPrincipal);
        } catch (DAOException ex) {
            throw new ServiceException("Error while resetting the password.",
                    ex);
        }
    }

    /**
     * Reset the properties related to change password.
     * @param userPrincipal
     * @param password
     */
    private void resetPasswordProperties(ESRUserPrincipalImpl userPrincipal,
            String password) {
        userPrincipal.setPassword(password);
        userPrincipal.setPasswordChangeDate(getCurrentDate());
        userPrincipal.setFailAttemptCount(0);
        userPrincipal.setPasswordExpireDate(null);
    }

    /**
     * Update the login status information on successful login as Set the last
     * access date to current date. Reset the failed attempt counter to zero.
     *
     * @param userPrincipal
     * @throws DAOException
     */
    private void onLoginSuccess(ESRUserPrincipalImpl userPrincipal,Map props)
            throws DAOException {
        Date current = getCurrentDate();

        if (userPrincipal.getInitialLoginDate() == null) {
            userPrincipal.setInitialLoginDate(current);

        }

        if (userPrincipal.getSignatureVerified() == null ||
            Boolean.FALSE.equals(userPrincipal.getSignatureVerified())){
            userPrincipal.setSignatureVerified(Boolean.TRUE);
        }
        resetLockProperties(userPrincipal);
        //setPasswordWarningProperties(userPrincipal, props);
        //update user and log success
        logUserLogin(userPrincipal, current, null);
    }


    private void setPasswordWarningProperties(ESRUserPrincipalImpl userPrincipal,
            Map props) {
        long now = getCurrentDate().getTime();
        long passwordChangeTime = userPrincipal.getPasswordChangeDate().getTime();
        long activeDays = (now - passwordChangeTime) / DAY_MILLISECONDS;
        int nonWarningDays = getPasswordActiveDays(props) - passwordChangeWarningDays;
        int accountExpiryDays = getPasswordActiveDays(props) + getPasswordExpireDays(props);
        if (activeDays >= nonWarningDays && activeDays < accountExpiryDays) {
            userPrincipal.setPasswordChangeWarningDays(true);
            userPrincipal.setDaysRemainingToExpirePassword((getPasswordActiveDays(props) - (int) activeDays));
        }
        //if password expire date is set calculate waning days based on that date
        Date passwordExpireDate = userPrincipal.getPasswordExpireDate();
        if (passwordExpireDate != null && passwordExpireDate.after(getCurrentDate())) {
            int daysRemaining = (int) ((passwordExpireDate.getTime() - now)/ DAY_MILLISECONDS);
            if (daysRemaining <= passwordChangeWarningDays) {
                userPrincipal.setPasswordChangeWarningDays(true);
                userPrincipal.setDaysRemainingToExpirePassword(daysRemaining);
            }
        }
    }

    /**
     * Increment the failed attempt counter. If counter has reached the max
     * allowed attempts, lock the account by setting the lock date to current
     * date.
     *
     * @param userPrincipal
     * @throws DAOException
     * @throws AccountLockedException
     */
    private void onLoginFailed(ESRUserPrincipalImpl userPrincipal,
            int noOfFailedAttemptsToLock)
            throws DAOException, AccountLockedException {

    	// invalid password. update the failed attempt information.
        //verify whether we need reset the counter if the login is after
        //15 minutes of locking

    	//CCR12708-Users accounts no longer get reset after 15 minutes. So, no need to have the
    	//following code block in place that deals with the reset.
    	/** Date lockDate = userPrincipal.getLockDate();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(getCurrentDate());
        calendar.add(Calendar.MINUTE, -(lockTimeOnFailedAttemptsInMins));
        Date failedLoginDate = null;
        if (userPrincipal.getUserLastLoginInfo() != null) {
        	failedLoginDate = userPrincipal.getUserLastLoginInfo().getFailedLoginDate();
        }
        if (lockDate != null && lockDate.getTime() < calendar.getTimeInMillis()) {
            //lock time expired reset the counter
            userPrincipal.setFailAttemptCount(1);
            userPrincipal.setLockDate(null);
            logUserLogin(userPrincipal, null, getCurrentDate());
        }
        //Reset the counter if the last failed login attempt was 15 minutes ago
        else if (lockDate == null && failedLoginDate != null &&
        		userPrincipal.getFailAttemptCount() > 0 &&
        		failedLoginDate.getTime() < calendar.getTimeInMillis()){
            userPrincipal.setFailAttemptCount(1);
            userPrincipal.setLockDate(null);
            logUserLogin(userPrincipal, null, getCurrentDate());
        }
        */

        boolean lock = false;
        int failedAttemptCount = userPrincipal.getFailAttemptCount() + 1;
        userPrincipal.setFailAttemptCount(failedAttemptCount);
        if (failedAttemptCount >= noOfFailedAttemptsToLock) {
            userPrincipal.setLockDate(getCurrentDate());
            lock = true;
        }
        //update and log failure
        logUserLogin(userPrincipal, null, getCurrentDate());
        if (lock) {
            throw new AccountLockedException("User account for "+userPrincipal.getName()+" has been locked.");
        }

    }

    /**
     * Log the user login information
     *
     * @param userPrincipal
     * @param passLoginDate
     * @param failedLoginDate
     * @throws DAOException
     */
    private void logUserLogin(ESRUserPrincipalImpl userPrincipal, Date passLoginDate, Date failedLoginDate) throws DAOException {
        UserLogin userLogin = new UserLogin(userPrincipal, passLoginDate, failedLoginDate);
        securityDAO.updateAndLog(userPrincipal,userLogin);
    }

    /**
     * Check whether the user password is expired.
     *
     * @param userPrincipal
     * @return true if expired, false otherwise
     */
    private boolean isPasswordExpired(ESRUserPrincipal userPrincipal,
            int passwordActiveDays) {
        Date passwordExpireDate = userPrincipal.getPasswordExpireDate();

        //Check againist the expire date set by the administrator
        if (passwordExpireDate != null &&
                passwordExpireDate.before(getCurrentDate()))
        {
            //password is expired
            return true;
        }
        Date passwordChangeDate = userPrincipal.getPasswordChangeDate();
        if (passwordChangeDate == null) { // should never be null, return true
            return true;
        }
        Calendar calendar = Calendar.getInstance();
        // Use the getCurrentDate to get the current date as we may be using
        // only database time and not to rely on application server time.
        calendar.setTime(getCurrentDate());
        calendar.add(Calendar.DATE, -(passwordActiveDays));
        return passwordChangeDate.getTime() < calendar.getTimeInMillis();
    }

    /**
     * Check whether the user account is locked
     *
     * @param userPrincipal
     * @return true if locked, false otherwise.
     */
    private boolean isAccountLocked(ESRUserPrincipal userPrincipal) {
        //CCR13343 SSOi integration
    	//lock date now set through admin user account edit
    	//do not lock the account until date is passed
    	Date lockDate = userPrincipal.getLockDate();
        if (lockDate != null && lockDate.before(getCurrentDate())){
            return true;
        }
        return false;
    }

    /**
     * Check whether the user account is disabled.
     *
     * @param userPrincipal
     * @return true if disabled, false otherwise
     */
    private boolean isAccountDisabled(ESRUserPrincipal userPrincipal) {
        return (userPrincipal.getInActiveDate() != null);
    }

    /**
     * Check whether the user account is expired.
     *
     * @param userPrincipal
     * @return true if expired, false otherwise.
     */
    private boolean isAccountExpired(ESRUserPrincipal userPrincipal,
            int passwordActiveDays, int passwordExpireDays) {
        Date passwordChangeDate = userPrincipal.getPasswordChangeDate();
        Date passwordExpireDate = userPrincipal.getPasswordExpireDate();
        if (passwordChangeDate == null) { // should never be null, return true
            return true;
        }
        Calendar calendar = Calendar.getInstance();
        //Check whether password expired (password change date is older than (90 + 120) days
        calendar.setTime(getCurrentDate());
        calendar.add(Calendar.DATE, -(passwordActiveDays + passwordExpireDays));
        if (passwordChangeDate.getTime() < calendar.getTimeInMillis()){
            return true;
        }

        //Check whether admin entered password expiration date also expired
        if (passwordExpireDate != null){
            calendar.setTime(getCurrentDate());
            calendar.add(Calendar.DATE, -(passwordExpireDays));
            if (passwordExpireDate.getTime() < calendar.getTimeInMillis()){
                return true;
            }
        }
        return false;
    }

    private boolean isPasswordSuspended(ESRUserPrincipal userPrincipal) {
        Date suspendDate = userPrincipal.getPasswordSuspendDate();
        if (suspendDate != null && suspendDate.before(getCurrentDate())){
            return true;
        }
        return false;
    }

    /**
     * Set the lock properties of a given user.
     * @param userPrincipal
     */
    private void setLockProperties(ESRUserPrincipalImpl userPrincipal) {
        userPrincipal.setLockDate(getCurrentDate());
        userPrincipal.setFailAttemptCount(0); //reset the count
    }

    /**
     * Reset the lock properties for a given user.
     * @param userPrincipal
     */
    private void resetLockProperties(ESRUserPrincipalImpl userPrincipal) {
        //userPrincipal.setLockDate(null);
        userPrincipal.setFailAttemptCount(0);
    }

    public void verifySecurityCode(String userName, String securityCode) throws LoginException
    {
        ESRUserPrincipalImpl userPrincipal = null;
        try {
            userPrincipal = (ESRUserPrincipalImpl) getUserByName(userName);

            if (userPrincipal == null){
                //user does not exist. just throw exception
                throw new FailedLoginException(
                        "Invalid username name: " + userName);
            }

            if (!isSecurityCodeValid(userPrincipal,securityCode)) {
                throw new InvalidSecurityCodeException("Valid Security is required for authentication. userid:" + userName);
            }

        } catch (ServiceException e) {
            LoginException ex = new LoginException("Error authenticating user " + userName +
                ". Reason: " + e.toString());
            ex.initCause(e);
            throw ex;
        }
    }

    public boolean isSecurityCodeValid(ESRUserPrincipalImpl user, String securityCode)
    {
        if (isSecurityCodeRequired(user)) {
            // validate
            if (StringUtils.isEmpty(securityCode)) {
                //security code not provided
                return false;
            }
            //compare the codes and return
            return StringUtils.equals(user.getAgreementSignatureCode(),securityCode);
        }
        else
            return true;
    }

    public boolean isSecurityCodeRequired(ESRUserPrincipalImpl user) {

        boolean validate = false;
        //Code is required if the user is logging for the first time
        if (user.getSignatureVerified() == null ||
            Boolean.FALSE.equals(user.getSignatureVerified()) ||
            user.getInitialLoginDate() == null ||
            user.getSuccessfulLoginDate() == null) {
            //requires security code validation
            validate = true;
        }
        //Verify whether it requires annual validation
        else {
            //create the annual login date for the current year
            int currentYear = Calendar.getInstance().get(Calendar.YEAR);
            Calendar nextLogin = Calendar.getInstance();
            nextLogin.clear();
            nextLogin.setTime(user.getInitialLoginDate());
            int initialLoginYear = nextLogin.get(Calendar.YEAR);

            //Validation not required return true
            if (currentYear > initialLoginYear) {
                //increment the year to the current
                nextLogin.add(Calendar.YEAR,currentYear - nextLogin.get(Calendar.YEAR));

                //check whether the last login date is after this date
                Date lastLogindate = user.getSuccessfulLoginDate();
                Date currentDate = new Date();
                if (currentDate.after(nextLogin.getTime()) &&
                    lastLogindate.before(nextLogin.getTime())) {
                    //requires validation
                    validate = true;
                }
            }
        }

        return validate;
    }
    /**
     * Verify whether user has Login Capability Assigned
     * @param userPrincipal
     * @return whether user is authorized to login or not
     */
    private boolean isAuthorizedToLogin(UserPrincipal userPrincipal) {
    	if (userPrincipal.getAuthorities().contains(
    			new GrantedAuthorityImpl(Capability.LOGIN_INTO_SYSTEM.getName()))) {
    		return true;
    	} else {
    		return false;
    	}
    }
    private boolean isAdministartor(UserPrincipal userPrincipal) {
        if (userPrincipal == null) return false;
        if (userPrincipal.getAuthorities().contains(
                new GrantedAuthorityImpl(Capability.ADMINISTRATOR.getName()))) {
            return true;
        } else {
            return false;
        }
    }
    /**
     * Helper method to get a User by user identifier.
     *
     * @param userId
     * @return UserPrincipalImpl, never null.
     * @throws ServiceException
     *             if userId is null or no user exists.
     */
    private ESRUserPrincipalImpl getUserPrincipal(EntityKey userId)
            throws ServiceException {
        Validate.notNull(userId, "User identifier can not be NULL.");

        try {
            return (ESRUserPrincipalImpl) securityDAO.getUserById(userId);
        } catch (DAOException e) {
            throw new ServiceException("No user exists with a user identifier "
                    + userId, e);
        }
    }

    protected ESRUserPrincipalImpl getUserByName(String username)
            throws ServiceException {
        Validate.notNull(username, "User name can not be NULL.");
        ESRUserPrincipalImpl user = null;
        try {
            user = (ESRUserPrincipalImpl) securityDAO.findUserByUsername(username);

            if (user != null) {
                UserLastLoginInfo lastLoginInfo =
                    getSecurityDAO().getUserLastLoginInfo(user.getEntityKey());
                user.setUserLastLoginInfo(lastLoginInfo);
            }
        } catch(DAOException ex) {
            throw new ServiceException("Error while locating the user "
                    + username + " Reason: ", ex);
        }
        return user;
    }

    protected Map getSystemProperties() throws ServiceException{
        List properties = getSystemParameterService().findAll();
        Map propMap = new HashMap();
        for (int i=0; i<properties.size(); i++) {
            SystemParameter par = (SystemParameter)properties.get(i);
            propMap.put(par.getName(),par.getValue());
        }
        return propMap;
    }

    private int getNoOfFailedAttemptsToLock(Map propMap){
         String value = (String) propMap.get(SystemParameter.FAILED_ATTEMPTS);
         try {
             return Integer.parseInt(value);
         }catch(NumberFormatException e) {
             return 3; // default value
         }
     }

     private int getPasswordActiveDays(Map propMap){
         String value = (String) propMap.get(SystemParameter.PASSWORD_ACTIVE_DAYS);
         try {
             return Integer.parseInt(value);
         }catch(NumberFormatException e) {
             return 90; // default value
         }
     }

     private int getPasswordExpireDays(Map propMap){
         String value = (String) propMap.get(SystemParameter.PASSWORD_EXPIRED_DAYS);
         try {
             return Integer.parseInt(value);
         }catch(NumberFormatException e) {
             return 120; // default value
         }
     }
     /**
     * Return the current date.
     * Use this method to get the current date as we change the implementation of this
     * to use  only database time and not to rely on application server time.
     * @return Date
     */
    protected Date getCurrentDate() {
        return new Date();
    }

    public void validatePasswordReuse(ESRUserPrincipalImpl userPrincipal,
            String newPassword) throws LoginException
    {
        try {
        List changeHistory = getSecurityDAO().getPasswordChangeHistory(
                userPrincipal.getEntityKey());
        //verify the passwords and
        String encryptedNewPassword =
            getPasswordEncryptionService().encryptPassword(newPassword);

        //Password can not be changed within 3 days
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(getCurrentDate());
        calendar.add(Calendar.DATE, -(3));

        //password can not be changed within last days of the last change
        //get the first record to check the last change
        SecurityContext securityContext = SecurityContextHelper.getSecurityContext();
        UserPrincipal loggedInUser = (securityContext != null) ? securityContext.getUserPrincipal() : null;
        if (isAdministartor(loggedInUser) ||
            (userPrincipal.getName().equalsIgnoreCase(securityContext.getUserName())
            && isAdministartor(userPrincipal)))
        {
            //administrators can change password any time
        }
        else {
            if (changeHistory.size() > 0) {
                PasswordChangeHistory pwdChangeHistory = (PasswordChangeHistory)changeHistory.get(0);
                if (userPrincipal.getName().equalsIgnoreCase(pwdChangeHistory.getPasswordChangedBy())
                   &&  calendar.getTime().before(pwdChangeHistory.getPasswordChangeTime())){
                    throw new PasswordChangeNotAllowedException(
                            "Password can not be changed within 3 days" );
                }
            }
        }

        //validate based on number of passwords and time
        for (int i=0; i<changeHistory.size(); i++){
            PasswordChangeHistory pwdHistory = (PasswordChangeHistory)changeHistory.get(i);

            if (encryptedNewPassword.equals(pwdHistory.getPassword()) ||
                    newPassword.equals(pwdHistory.getPassword())){
                //password match check for valid conditions
                if (i < 4 ) { //|| calendar.getTime().before(pwdHistory.getPasswordChangeTime())){
                    //password is reused thorw an exception
                    throw new InvalidPasswordException(
                        "Password is reused: Same password exists in the system  " +
                        pwdHistory.getPasswordChangeTime().toString());
                }
            }
        }
        }catch (DAOException e){
            throw new LoginException("Error while retrieving the password history");
        }
        catch (ServiceException se){
            throw new LoginException("Error while encrypting the passwords");
        }
    }

    protected void checkVersionNumbers(EntityKey incomingKey, EntityKey onfileKey)
    throws ServiceException {
        BaseEntityKey incoming = (BaseEntityKey) incomingKey;
        BaseEntityKey onfile = (BaseEntityKey) onfileKey;
        if (incoming.getVersion() != null && onfile.getVersion() != null &&
            incoming.getVersion().intValue() != onfile.getVersion().intValue()) {
            throw new ServiceOptimisticLockException(
                "Version number specified does not match on file version number",null,null);
        }
    }
}