diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/AuditConstants.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/AuditConstants.java index 21f03808a3..b90c927b54 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/AuditConstants.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/AuditConstants.java @@ -36,6 +36,7 @@ public class AuditConstants { public static final String ACTION_PASSWORD_RECOVERY = "Password recovery"; public static final String ACTION_USERNAME_RECOVERY = "Username recovery"; public static final String ACTION_PASSWORD_RESET = "Password reset"; + public static final String ACTION_ACCOUNT_STATUS_NOTIFY = "Account status notify"; public static final String NOTIFICATION_TEMPLATE_TYPE = "Notification template"; public static final String USER_STORE_DOMAIN = "UserStoreDomain"; public static final String RECOVERY_SCENARIO = "RecoveryScenario"; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/IdentityRecoveryConstants.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/IdentityRecoveryConstants.java index b29bc5bee5..01191ed205 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/IdentityRecoveryConstants.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/IdentityRecoveryConstants.java @@ -203,6 +203,13 @@ public class IdentityRecoveryConstants { // Self sign up properties. public static final String SIGNUP_PROPERTY_REGISTRATION_OPTION = "registrationOption"; + // Properties related to password recovery failure due to account status. + public static final String ERROR_KEY = "error-key"; + public static final String NOTIFICATION_TYPE_ACCOUNT_STATUS_NOTIFY = "passwordRecoveryFailureNotify"; + public static final String ACCOUNT_STATUS_LOCKED = "password.recovery.failed.account.locked"; + public static final String ACCOUNT_STATUS_DISABLED = "password.recovery.failed.account.disabled"; + public static final String IGNORE_IF_TEMPLATE_NOT_FOUND = "ignoreIfTemplateNotFound"; + private IdentityRecoveryConstants() { } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/password/NotificationPasswordRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/password/NotificationPasswordRecoveryManager.java index a52c9e1a83..21556f90d0 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/password/NotificationPasswordRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/password/NotificationPasswordRecoveryManager.java @@ -65,7 +65,9 @@ import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -156,12 +158,15 @@ IdentityEventConstants.Event.PRE_SEND_RECOVERY_NOTIFICATION, new UserRecoveryDat throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_FEDERATED_USER, user.getUserName()); } + String eventName = Utils.resolveEventName(notificationChannel); if (Utils.isAccountDisabled(user)) { // If the NotifyUserAccountStatus is disabled, notify with an empty NotificationResponseBean. if (getNotifyUserAccountStatus()) { throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_DISABLED_ACCOUNT, user.getUserName()); } + triggerAccountStatusNotification(user, notificationChannel, + IdentityRecoveryConstants.ACCOUNT_STATUS_DISABLED, eventName, properties); return new NotificationResponseBean(user); } else if (Utils.isAccountLocked(user)) { // Check user in PENDING_SR or PENDING_AP status. @@ -171,6 +176,8 @@ IdentityEventConstants.Event.PRE_SEND_RECOVERY_NOTIFICATION, new UserRecoveryDat throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_LOCKED_ACCOUNT, user.getUserName()); } + triggerAccountStatusNotification(user, notificationChannel, + IdentityRecoveryConstants.ACCOUNT_STATUS_LOCKED, eventName, properties); return new NotificationResponseBean(user); } UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); @@ -188,7 +195,6 @@ IdentityEventConstants.Event.PRE_SEND_RECOVERY_NOTIFICATION, new UserRecoveryDat NotificationResponseBean notificationResponseBean = new NotificationResponseBean(user); if (isNotificationInternallyManage) { // Manage notifications by the identity server. - String eventName = Utils.resolveEventName(notificationChannel); triggerNotification(user, notificationChannel, IdentityRecoveryConstants.NOTIFICATION_TYPE_PASSWORD_RESET, secretKey, eventName, properties, recoveryDataDO); } else { @@ -1022,6 +1028,55 @@ private void triggerNotification(User user, String notificationChannel, String t } + /** + * Trigger notification to send account status information. + * + * @param user User + * @param notificationChannel Notification channel + * @param status Account status + * @param eventName Event name + * @param metaProperties Meta properties to be sent with the notification. + * @throws IdentityRecoveryException Error while triggering notification. + */ + private void triggerAccountStatusNotification(User user, String notificationChannel, + String status, String eventName, Property[] metaProperties) + throws IdentityRecoveryException { + + HashMap properties = new HashMap<>(); + properties.put(IdentityEventConstants.EventProperty.USER_NAME, user.getUserName()); + properties.put(IdentityEventConstants.EventProperty.TENANT_DOMAIN, user.getTenantDomain()); + properties.put(IdentityEventConstants.EventProperty.USER_STORE_DOMAIN, user.getUserStoreDomain()); + properties.put(IdentityEventConstants.EventProperty.NOTIFICATION_CHANNEL, notificationChannel); + if (StringUtils.isNotBlank(status)) { + properties.put(IdentityRecoveryConstants.ERROR_KEY, + Base64.getUrlEncoder().encodeToString(status.getBytes(StandardCharsets.UTF_8))); + } + // This property is used to ignore throwing an error if the template is not found. This allows to preserve the + // backward compatibility for the tenants without the specific email template. + properties.put(IdentityRecoveryConstants.IGNORE_IF_TEMPLATE_NOT_FOUND, true); + + if (metaProperties != null) { + for (Property metaProperty : metaProperties) { + if (StringUtils.isNotBlank(metaProperty.getValue()) && StringUtils.isNotBlank(metaProperty.getKey())) { + properties.put(metaProperty.getKey(), metaProperty.getValue()); + } + } + } + properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE, + IdentityRecoveryConstants.NOTIFICATION_TYPE_ACCOUNT_STATUS_NOTIFY); + Event identityMgtEvent = new Event(eventName, properties); + try { + IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService().handleEvent(identityMgtEvent); + auditAccountStatusNotify(notificationChannel, user, null, + FrameworkConstants.AUDIT_SUCCESS); + } catch (IdentityEventException e) { + auditAccountStatusNotify(notificationChannel, user, + e.getMessage(), FrameworkConstants.AUDIT_FAILED); + throw Utils.handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_TRIGGER_NOTIFICATION, + user.getUserName(), e); + } + } + private void publishEvent(User user, String notify, String code, String password, Property[] metaProperties, String eventName, UserRecoveryData userRecoveryData) throws IdentityRecoveryException { @@ -1137,6 +1192,23 @@ private void auditPasswordRecovery(String action, String notificationChannel, Us Utils.createAuditMessage(action, user.getUserName(), dataObject, result); } + private void auditAccountStatusNotify(String notificationChannel, User user, String errorMsg, String result) { + + JSONObject dataObject = new JSONObject(); + dataObject.put(AuditConstants.REMOTE_ADDRESS_KEY, MDC.get(AuditConstants.REMOTE_ADDRESS_QUERY_KEY)); + dataObject.put(AuditConstants.USER_AGENT_KEY, MDC.get(AuditConstants.USER_AGENT_QUERY_KEY)); + dataObject.put(AuditConstants.NOTIFICATION_CHANNEL, notificationChannel); + dataObject.put(AuditConstants.SERVICE_PROVIDER_KEY, MDC.get(AuditConstants.SERVICE_PROVIDER_QUERY_KEY)); + dataObject.put(AuditConstants.USER_STORE_DOMAIN, user.getUserStoreDomain()); + dataObject.put(AuditConstants.TENANT_DOMAIN, user.getTenantDomain()); + dataObject.put(AuditConstants.NOTIFICATION_TEMPLATE_TYPE, IdentityRecoveryConstants.NOTIFICATION_TYPE_ACCOUNT_STATUS_NOTIFY); + + if (AUDIT_FAILED.equals(result)) { + dataObject.put(AuditConstants.ERROR_MESSAGE_KEY, errorMsg); + } + Utils.createAuditMessage(AuditConstants.ACTION_ACCOUNT_STATUS_NOTIFY, user.getUserName(), dataObject, result); + } + private void auditPasswordReset(User user, String action, String errorMsg, String result, String recoveryScenario, String recoveryStep) {