Skip to content

Commit

Permalink
Merge pull request #818 from ThaminduR/account-status-notify
Browse files Browse the repository at this point in the history
Inform account status when password reset is initiated
  • Loading branch information
ThaminduR authored May 17, 2024
2 parents a001dfb + 0f1cfa2 commit 781fb14
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand All @@ -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();
Expand All @@ -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 {
Expand Down Expand Up @@ -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<String, Object> 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 {
Expand Down Expand Up @@ -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) {

Expand Down

0 comments on commit 781fb14

Please sign in to comment.