/**
   * This method is used when the admin is updating the credentials with an empty credential. A
   * random password will be generated and will be mailed to the user.
   */
  @Override
  public boolean doPreUpdateCredentialByAdmin(
      String userName, Object newCredential, UserStoreManager userStoreManager)
      throws UserStoreException {

    if (log.isDebugEnabled()) {
      log.debug("Pre update credential by admin is called in IdentityMgtEventListener");
    }
    IdentityMgtConfig config = IdentityMgtConfig.getInstance();
    if (!config.isListenerEnable()) {
      return true;
    }

    try {
      // Enforcing the password policies.
      if (newCredential != null
          && (newCredential instanceof StringBuffer
              && (newCredential.toString().trim().length() > 0))) {
        policyRegistry.enforcePasswordPolicies(newCredential.toString(), userName);
      }

    } catch (PolicyViolationException pe) {
      log.error(pe.getMessage());
      throw new UserStoreException(pe.getMessage());
    }

    if (newCredential == null
        || (newCredential instanceof StringBuffer
            && ((StringBuffer) newCredential).toString().trim().length() < 1)) {

      if (!config.isEnableTemporaryPassword()) {
        log.error("Empty passwords are not allowed");
        return false;
      }
      if (log.isDebugEnabled()) {
        log.debug("Credentials are null. Using a temporary password as credentials");
      }
      // temporary passwords will be used
      char[] temporaryPassword = UserIdentityManagementUtil.generateTemporaryPassword();
      // setting the password value
      ((StringBuffer) newCredential)
          .replace(0, temporaryPassword.length, new String(temporaryPassword));

      UserIdentityMgtBean bean = new UserIdentityMgtBean();
      bean.setUserId(userName);
      bean.setConfirmationCode(newCredential.toString());
      bean.setRecoveryType(IdentityMgtConstants.Notification.TEMPORARY_PASSWORD);
      log.debug("Sending the tempory password to the user " + userName);
      UserIdentityManagementUtil.notifyViaEmail(bean);
    } else {
      log.debug("Updating credentials of user " + userName + " by admin with a non-empty password");
    }
    return true;
  }
  /**
   * This method will set the default/random password if the password provided is null. The thread
   * local parameter EMPTY_PASSWORD_USED will be used to track if the password empty in the
   * doPostAddUser. This method will filter the security question URIs from claims and put those to
   * the thread local properties.
   */
  @Override
  public boolean doPreAddUser(
      String userName,
      Object credential,
      String[] roleList,
      Map<String, String> claims,
      String profile,
      UserStoreManager userStoreManager)
      throws UserStoreException {

    if (log.isDebugEnabled()) {
      log.debug("Pre add user is called in IdentityMgtEventListener");
    }
    IdentityMgtConfig config = IdentityMgtConfig.getInstance();
    if (!config.isListenerEnable()) {
      return true;
    }

    try {
      // Enforcing the password policies.
      if (credential != null
          && (credential instanceof StringBuffer && (credential.toString().trim().length() > 0))) {
        policyRegistry.enforcePasswordPolicies(credential.toString(), userName);
      }

    } catch (PolicyViolationException pe) {
      log.error(pe.getMessage());
      throw new UserStoreException(pe.getMessage());
    }

    // empty password account creation
    if (credential == null
        || (credential instanceof StringBuffer && (credential.toString().trim().length() < 1))) {

      if (!config.isEnableTemporaryPassword()) {
        log.error("Empty passwords are not allowed");
        return false;
      }
      if (log.isDebugEnabled()) {
        log.debug("Credentials are null. Using a temporary password as credentials");
      }
      // setting the thread-local to check in doPostAddUser
      threadLocalProperties.get().put(EMPTY_PASSWORD_USED, true);
      // temporary passwords will be used
      char[] temporaryPassword = UserIdentityManagementUtil.generateTemporaryPassword();

      // setting the password value
      ((StringBuffer) credential)
          .replace(0, temporaryPassword.length, new String(temporaryPassword));
    }

    // Filtering security question URIs from claims and add them to the thread local dto
    Map<String, String> userDataMap = new HashMap<String, String>();

    // TODO why challenge Q
    Iterator<Entry<String, String>> it = claims.entrySet().iterator();
    while (it.hasNext()) {

      Map.Entry<String, String> claim = it.next();

      if (claim.getKey().contains(UserCoreConstants.ClaimTypeURIs.CHALLENGE_QUESTION_URI)
          || claim.getKey().contains(UserCoreConstants.ClaimTypeURIs.IDENTITY_CLAIM_URI)) {
        userDataMap.put(claim.getKey(), claim.getValue());
        it.remove();
      }
    }

    UserIdentityClaimsDO identityDTO = new UserIdentityClaimsDO(userName, userDataMap);
    // adding dto to thread local to be read again from the doPostAddUser method
    threadLocalProperties.get().put(USER_IDENTITY_DO, identityDTO);
    return true;
  }
  /**
   * This method locks the accounts after a configured number of authentication failure attempts.
   * And unlocks accounts based on successful authentications.
   */
  @Override
  public boolean doPostAuthenticate(
      String userName, boolean authenticated, UserStoreManager userStoreManager)
      throws UserStoreException {

    if (log.isDebugEnabled()) {
      log.debug("Post authenticator is called in IdentityMgtEventListener");
    }

    IdentityMgtConfig config = IdentityMgtConfig.getInstance();

    if (!config.isEnableAuthPolicy()) {
      return authenticated;
    }

    UserIdentityClaimsDO userIdentityDTO = module.load(userName, userStoreManager);
    if (userIdentityDTO == null) {
      userIdentityDTO = new UserIdentityClaimsDO(userName);
    }

    boolean userOTPEnabled = userIdentityDTO.getOneTimeLogin();

    // One time password check
    if (authenticated
        && config.isAuthPolicyOneTimePasswordCheck()
        && (!userStoreManager.isReadOnly())) {

      // reset password of the user and notify user of the new password
      if (userOTPEnabled) {

        String password = UserIdentityManagementUtil.generateTemporaryPassword().toString();
        userStoreManager.updateCredentialByAdmin(userName, password);

        // Get email user claim value
        String email =
            userStoreManager.getUserClaimValue(
                userName, UserCoreConstants.ClaimTypeURIs.EMAIL_ADDRESS, null);

        if (email == null) {
          throw new UserStoreException("No user email provided for user " + userName);
        }

        List<NotificationSendingModule> notificationModules =
            config.getNotificationSendingModules();

        if (notificationModules != null) {

          NotificationDataDTO notificationData = new NotificationDataDTO();

          NotificationData emailNotificationData = new NotificationData();
          String emailTemplate = null;
          int tenantId = userStoreManager.getTenantId();
          String firstName = null;
          try {
            firstName =
                Utils.getClaimFromUserStoreManager(
                    userName, tenantId, "http://wso2.org/claims/givenname");
          } catch (IdentityException e2) {
            throw new UserStoreException("Could not load user given name");
          }
          emailNotificationData.setTagData("first-name", firstName);
          emailNotificationData.setTagData("user-name", userName);
          emailNotificationData.setTagData("otp-password", password);

          emailNotificationData.setSendTo(email);

          Config emailConfig = null;
          ConfigBuilder configBuilder = ConfigBuilder.getInstance();
          try {
            emailConfig =
                configBuilder.loadConfiguration(ConfigType.EMAIL, StorageType.REGISTRY, tenantId);
          } catch (Exception e1) {
            throw new UserStoreException("Could not load the email template configuration");
          }

          emailTemplate = emailConfig.getProperty("otp");

          Notification emailNotification = null;
          try {
            emailNotification =
                NotificationBuilder.createNotification(
                    "EMAIL", emailTemplate, emailNotificationData);
          } catch (Exception e) {
            throw new UserStoreException("Could not create the email notification");
          }
          NotificationSender sender = new NotificationSender();

          for (NotificationSendingModule notificationSendingModule : notificationModules) {

            if (IdentityMgtConfig.getInstance().isNotificationInternallyManaged()) {
              notificationSendingModule.setNotificationData(notificationData);
              notificationSendingModule.setNotification(emailNotification);
              sender.sendNotification(notificationSendingModule);
              notificationData.setNotificationSent(true);
            }
          }

        } else {
          throw new UserStoreException("No notification modules configured");
        }
      }
    }

    // Password expire check. Not for OTP enabled users.
    if (authenticated
        && config.isAuthPolicyExpirePasswordCheck()
        && !userOTPEnabled
        && (!userStoreManager.isReadOnly())) {
      // TODO - password expire impl
      // Refactor adduser and change password api to stamp the time
      // Check user's expire time in the claim
      // if expired redirect to change password
      // else pass through
      /*
      long timestamp = userIdentityDTO.getPasswordTimeStamp();
      // Only allow behavior to users with this claim. Intent bypass for admin?
      if (timestamp > 0) {
      	Calendar passwordExpireTime = Calendar.getInstance();
      	passwordExpireTime.setTimeInMillis(timestamp);

      	int expireDuration = config.getAuthPolicyPasswordExpireTime();
      	if (expireDuration > 0) {

      		passwordExpireTime.add(Calendar.DATE, expireDuration);

      		Calendar currentTime = Calendar.getInstance();

      		if (currentTime.compareTo(passwordExpireTime) > 0) {
      			// password expired
      			// set flag to redirect
      			log.error("Password is expired ...........");
      			// throw new UserStoreException("Password is expired");
      		}

      	}
      }
      */
    }

    if (!authenticated && config.isAuthPolicyAccountLockOnFailure()) {
      userIdentityDTO.setFailAttempts();
      // reading the max allowed #of failure attempts

      if (userIdentityDTO.getFailAttempts() >= config.getAuthPolicyMaxLoginAttempts()) {
        if (log.isDebugEnabled()) {
          log.debug(
              "User, "
                  + userName
                  + " has exceed the max failed login attempts. "
                  + "User account would be locked");
        }
        userIdentityDTO.setAccountLock(true);
        // lock time from the config
        int lockTime = IdentityMgtConfig.getInstance().getAuthPolicyLockingTime();
        if (lockTime != 0) {
          userIdentityDTO.setUnlockTime(System.currentTimeMillis() + (lockTime * 60 * 1000));
        }
      }

      try {
        module.store(userIdentityDTO, userStoreManager);
      } catch (IdentityException e) {
        throw new UserStoreException("Error while doPostAuthenticate", e);
      }

    } else {
      // if the account was locked due to account verification process,
      // the unlock the account and reset the number of failedAttempts
      if (userIdentityDTO.isAccountLocked() || userIdentityDTO.getFailAttempts() > 0) {
        userIdentityDTO.setAccountLock(false);
        userIdentityDTO.setFailAttempts(0);
        userIdentityDTO.setUnlockTime(0);
        try {
          module.store(userIdentityDTO, userStoreManager);
        } catch (IdentityException e) {
          throw new UserStoreException("Error while doPostAuthenticate", e);
        }
      }
    }

    return true;
  }
  /**
   * This method used to confirm the self registered user account and unlock it.
   *
   * @param username
   * @param code
   * @param captcha
   * @param tenantDomain
   * @return
   * @throws IdentityMgtServiceException
   */
  public VerificationBean confirmUserSelfRegistration(
      String username, String code, CaptchaInfoBean captcha, String tenantDomain)
      throws IdentityMgtServiceException {

    VerificationBean bean = new VerificationBean();

    if (log.isDebugEnabled()) {
      log.debug("User registration verification request received with username :"******" Error while validating captcha for user : "******"Trying to confirm users in unauthorized tenant space";
        log.error(msg);
      }
      if (tenantDomain == null || tenantDomain.isEmpty()) {
        tenantDomain = loggedInTenant;
      }
    }

    UserDTO userDTO = null;
    try {
      userDTO = Utils.processUserId(username + "@" + tenantDomain);

    } catch (IdentityException e) {
      bean =
          handleError(
              VerificationBean.ERROR_CODE_INVALID_USER
                  + " Error verifying user account for user : "******"Error retrieving the user store manager for the tenant : " + tenantDomain, e);
        return bean;
      }

      try {
        bean = processor.verifyConfirmationCode(1, username, code);
        if (bean.isVerified()) {
          UserIdentityManagementUtil.unlockUserAccount(username, userStoreManager);
          bean.setVerified(true);

        } else {
          bean.setVerified(false);
          bean.setKey("");
          log.error("User verification failed against the given confirmation code");
        }
      } catch (IdentityException e) {
        bean = handleError("Error while validating confirmation code for user : " + username, e);
        return bean;
      }
    } finally {
      if (IdentityMgtConfig.getInstance().isSaasEnabled()) {
        PrivilegedCarbonContext.endTenantFlow();
      }
    }
    return bean;
  }
  /**
   * This method is used to register an user in the system. The account will be locked if the
   * Authentication.Policy.Account.Lock.On.Creation is set to true. Else user will be able to login
   * after registration.
   *
   * @param userName
   * @param password
   * @param claims
   * @param profileName
   * @param tenantDomain
   * @return
   * @throws IdentityMgtServiceException
   */
  public VerificationBean registerUser(
      String userName,
      String password,
      UserIdentityClaimDTO[] claims,
      String profileName,
      String tenantDomain)
      throws IdentityMgtServiceException {

    VerificationBean vBean = new VerificationBean();

    org.wso2.carbon.user.core.UserStoreManager userStoreManager = null;
    Permission permission = null;

    if (!IdentityMgtConfig.getInstance().isSaasEnabled()) {
      String loggedInTenant =
          PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
      if (tenantDomain != null && !tenantDomain.isEmpty() && !loggedInTenant.equals(tenantDomain)) {
        String msg = "Trying to create users in unauthorized tenant space";
        log.error(msg);
        throw new IdentityMgtServiceException(msg);
      }
      if (tenantDomain == null || tenantDomain.isEmpty()) {
        tenantDomain = loggedInTenant;
      }
    }

    RealmService realmService = IdentityMgtServiceComponent.getRealmService();
    int tenantId;

    try {

      tenantId = Utils.getTenantId(tenantDomain);
      if (realmService.getTenantUserRealm(tenantId) != null) {
        userStoreManager =
            (org.wso2.carbon.user.core.UserStoreManager)
                realmService.getTenantUserRealm(tenantId).getUserStoreManager();
      }

    } catch (Exception e) {
      vBean =
          handleError(
              VerificationBean.ERROR_CODE_UNEXPECTED
                  + " Error retrieving the user store manager for the tenant",
              e);
      return vBean;
    }

    try {

      if (userStoreManager == null) {
        vBean = new VerificationBean();
        vBean.setVerified(false);
        vBean.setError(
            VerificationBean.ERROR_CODE_UNEXPECTED
                + " Error retrieving the user store manager for the tenant");
        return vBean;
      }

      Map<String, String> claimsMap = new HashMap<String, String>();
      for (UserIdentityClaimDTO userIdentityClaimDTO : claims) {
        claimsMap.put(userIdentityClaimDTO.getClaimUri(), userIdentityClaimDTO.getClaimValue());
      }

      userStoreManager.addUser(userName, password, null, claimsMap, profileName);

      String identityRoleName =
          UserCoreConstants.INTERNAL_DOMAIN
              + CarbonConstants.DOMAIN_SEPARATOR
              + IdentityConstants.IDENTITY_DEFAULT_ROLE;

      if (!userStoreManager.isExistingRole(identityRoleName, false)) {
        permission = new Permission("/permission/admin/login", UserMgtConstants.EXECUTE_ACTION);
        userStoreManager.addRole(
            identityRoleName, new String[] {userName}, new Permission[] {permission}, false);
      } else {
        userStoreManager.updateUserListOfRole(
            identityRoleName, new String[] {}, new String[] {userName});
      }

      IdentityEventListener identityEventListener =
          IdentityUtil.readEventListenerProperty(
              UserOperationEventListener.class.getName(), IdentityMgtEventListener.class.getName());

      boolean isListenerEnable = true;

      if (identityEventListener != null) {
        if (StringUtils.isNotBlank(identityEventListener.getEnable())) {
          isListenerEnable = Boolean.parseBoolean(identityEventListener.getEnable());
        }
      }

      IdentityMgtConfig config = IdentityMgtConfig.getInstance();

      if (isListenerEnable && config.isAuthPolicyAccountLockOnCreation()) {
        UserDTO userDTO = new UserDTO(userName);
        userDTO.setTenantId(tenantId);

        UserRecoveryDTO dto = new UserRecoveryDTO(userDTO);
        dto.setNotification(IdentityMgtConstants.Notification.ACCOUNT_CONFORM);
        dto.setNotificationType("EMAIL");

        RecoveryProcessor processor = IdentityMgtServiceComponent.getRecoveryProcessor();
        vBean = processor.updateConfirmationCode(1, userName, tenantId);

        dto.setConfirmationCode(vBean.getKey());
        NotificationDataDTO notificationDto = processor.notifyWithEmail(dto);
        vBean.setVerified(notificationDto.isNotificationSent());

        //				Send email data only if not internally managed.
        if (!(IdentityMgtConfig.getInstance().isNotificationInternallyManaged())) {
          vBean.setNotificationData(notificationDto);
        }

      } else {
        vBean.setVerified(true);
      }
    } catch (UserStoreException | IdentityException e) {
      UserIdentityManagementUtil.getCustomErrorMessages(e, userName);
      // Rollback if user exists
      try {
        if (userStoreManager.isExistingUser(userName)) {
          userStoreManager.deleteUser(userName);
        }
      } catch (org.wso2.carbon.user.core.UserStoreException e1) {
        UserIdentityManagementUtil.getCustomErrorMessages(e1, userName);
      }

      return vBean;
    }

    return vBean;
  }
  /**
   * Verifies the user against the provided claims and captcha information.
   *
   * @param claims
   * @param captcha
   * @param tenantDomain
   * @return
   * @throws IdentityMgtServiceException
   */
  public VerificationBean verifyAccount(
      UserIdentityClaimDTO[] claims, CaptchaInfoBean captcha, String tenantDomain)
      throws IdentityMgtServiceException {

    VerificationBean vBean = new VerificationBean();

    if (IdentityMgtConfig.getInstance().isCaptchaVerificationInternallyManaged()) {
      try {
        CaptchaUtil.processCaptchaInfoBean(captcha);
      } catch (Exception e) {
        vBean =
            handleError(
                VerificationBean.ERROR_CODE_INVALID_CAPTCHA + " Error processing captcha", e);
        return vBean;
      }
    }

    if (!IdentityMgtConfig.getInstance().isSaasEnabled()) {
      String loggedInTenant =
          PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
      if (tenantDomain != null && !tenantDomain.isEmpty() && !loggedInTenant.equals(tenantDomain)) {
        String msg = "Trying to verify account unauthorized tenant space";
        log.error(msg);
        throw new IdentityMgtServiceException(msg);
      }
      if (tenantDomain == null || tenantDomain.isEmpty()) {
        tenantDomain = loggedInTenant;
      }
    }

    try {
      int tenantId = Utils.getTenantId(tenantDomain);
      String userName = UserIdentityManagementUtil.getUsernameByClaims(claims, tenantId);

      if (userName != null) {
        UserDTO userDTO = new UserDTO(userName);
        userDTO.setTenantId(tenantId);

        UserRecoveryDTO dto = new UserRecoveryDTO(userDTO);
        dto.setNotification(IdentityMgtConstants.Notification.ACCOUNT_ID_RECOVERY);
        dto.setNotificationType("EMAIL");

        RecoveryProcessor processor = IdentityMgtServiceComponent.getRecoveryProcessor();
        NotificationDataDTO notificationDto = processor.notifyWithEmail(dto);

        vBean.setVerified(notificationDto.isNotificationSent());

        //				Send email data only if not internally managed.
        if (!(IdentityMgtConfig.getInstance().isNotificationInternallyManaged())) {
          vBean.setNotificationData(notificationDto);
        }

      } else {
        vBean.setError("User not found");
        vBean.setVerified(false);
      }
    } catch (Exception e) {
      vBean =
          handleError(
              VerificationBean.ERROR_CODE_INVALID_USER + " Error verifying user account", e);
      return vBean;
    }

    return vBean;
  }