From dabdf8213ae194b57e805db147f5e980579d1e05 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 29 Aug 2023 10:56:43 +0530 Subject: [PATCH 01/27] Update recovery service for recovery V2 API --- .../recovery/IdentityRecoveryConstants.java | 41 ++- .../ResendConfirmationManager.java | 38 +- .../connector/RecoveryConfigImpl.java | 28 ++ .../recovery/dto/PasswordRecoverDTO.java | 25 ++ .../recovery/dto/RecoveryChannelInfoDTO.java | 25 ++ .../recovery/dto/RecoveryInformationDTO.java | 25 ++ .../recovery/dto/ResendConfirmationDTO.java | 25 ++ .../impl/UserAccountRecoveryManager.java | 132 ++++++- .../password/PasswordRecoveryManagerImpl.java | 121 ++++++- .../username/UsernameRecoveryManagerImpl.java | 18 +- .../recovery/model/UserRecoveryData.java | 65 ++++ .../recovery/model/UserRecoveryFlowData.java | 104 ++++++ .../NotificationPasswordRecoveryManager.java | 119 ++++++- .../password/PasswordRecoveryManager.java | 27 ++ .../recovery/store/JDBCRecoveryDataStore.java | 325 +++++++++++++++++- .../recovery/store/UserRecoveryDataStore.java | 16 + 16 files changed, 1107 insertions(+), 27 deletions(-) create mode 100644 components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java 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 3330601967..ae4ffdf5ba 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 @@ -164,6 +164,7 @@ public class IdentityRecoveryConstants { public static final int SMS_OTP_CODE_LENGTH = 6; public static final String ENABLE_DETAILED_ERROR_RESPONSE = "Recovery.ErrorMessage.EnableDetailedErrorMessages"; + public static final int RECOVERY_FLOW_ID_DEFAULT_EXPIRY_TIME = 15; // Recovery code given at the username and password recovery initiation. public static final int RECOVERY_CODE_DEFAULT_EXPIRY_TIME = 1; public static final int RESEND_CODE_DEFAULT_EXPIRY_TIME = 1; @@ -207,6 +208,8 @@ public enum ErrorMessages { ERROR_CODE_INVALID_TENANT("18016", "Invalid tenant '%s'."), ERROR_CODE_CHALLENGE_QUESTION_NOT_FOUND("18017", "No challenge question found. %s"), ERROR_CODE_EMAIL_NOT_FOUND("18018", "Sending email address is not found for the user %s."), + ERROR_CODE_INVALID_FLOW_ID("18019", "Invalid flow confirmation code '%s'."), + ERROR_CODE_EXPIRED_FLOW_ID("18020", "Expired flow confirmation code '%s'."), ERROR_CODE_INVALID_CREDENTIALS("17002", "Invalid Credentials"), ERROR_CODE_LOCKED_ACCOUNT("17003", "User account is locked - '%s'."), ERROR_CODE_DISABLED_ACCOUNT("17004", "user account is disabled '%s'."), @@ -288,6 +291,8 @@ public enum ErrorMessages { ERROR_CODE_DISABLE_LITE_SIGN_UP("20060", "Lite sign up feature is disabled"), ERROR_CODE_ERROR_DELETING_RECOVERY_DATA("20061", "Error deleting user recovery data of the tenant: %s"), ERROR_CODE_ERROR_GETTING_CONNECTOR_CONFIG("20062", "Error while getting connector configurations"), + ERROR_CODE_STORING_RECOVERY_OTP_DATA("20063", "Error while storing recovery data"), + ERROR_CODE_UPDATING_RECOVERY_OTP_DATA("20064", "Error while updating recovery data"), ERROR_CODE_ERROR_RETRIVING_CLAIM("18004", "Error when retrieving the locale claim of user '%s' of '%s' domain."), ERROR_CODE_RECOVERY_DATA_NOT_FOUND_FOR_USER("18005", "Recovery data not found."), @@ -356,6 +361,10 @@ public enum ErrorMessages { ERROR_CODE_EXPIRED_RECOVERY_CODE("UAR-10013", "Invalid recovery code: '%s'"), ERROR_CODE_USER_ACCOUNT_RECOVERY_VALIDATION_FAILED("UAR-10014", "User account recovery validation failed for user account: '%s'"), + ERROR_CODE_INVALID_RECOVERY_FLOW_ID("UAR-10015", "Invalid confirmation code : '%s'"), + ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'"), + ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " + + "recovery flow id : '%s'"), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), ERROR_CODE_ERROR_GETTING_USERSTORE_MANAGER("UAR-15002", "Error getting userstore manager"), ERROR_CODE_ERROR_RETRIEVING_USER_CLAIM("UAR-15003", "Error getting the claims: '%s' " @@ -604,13 +613,17 @@ public static class ConnectorConfig { public static final String ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET = "Recovery.AutoLogin.Enable"; public static final String SELF_REGISTRATION_AUTO_LOGIN = "SelfRegistration.AutoLogin.Enable"; public static final String SELF_REGISTRATION_AUTO_LOGIN_ALIAS_NAME = "SelfRegistration.AutoLogin.AliasName"; + public static final String RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS = "Recovery.Otp" + + ".Password.MaxFailedAttempts"; + public static final String RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS = "Recovery.Otp" + + ".Password.MaxResendAttempts"; } public static class SQLQueries { public static final String STORE_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA " - + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS)" - + "VALUES (?,?,?,?,?,?,?,?)"; + + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS, " + + "RECOVERY_FLOW_ID) VALUES (?,?,?,?,?,?,?,?,?)"; public static final String LOAD_RECOVERY_DATA = "SELECT " + "* FROM IDN_RECOVERY_DATA WHERE USER_NAME = ? AND USER_DOMAIN = ? AND TENANT_ID = ? AND CODE = ? AND " + "SCENARIO = ? AND STEP = ?"; @@ -621,6 +634,9 @@ public static class SQLQueries { public static final String LOAD_RECOVERY_DATA_FROM_CODE = "SELECT * FROM IDN_RECOVERY_DATA WHERE CODE = ?"; + public static final String LOAD_RECOVERY_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * FROM IDN_RECOVERY_DATA WHERE" + + " RECOVERY_FLOW_ID = ? AND STEP = ?"; + public static final String INVALIDATE_CODE = "DELETE FROM IDN_RECOVERY_DATA WHERE CODE = ?"; public static final String INVALIDATE_USER_CODES = @@ -634,6 +650,9 @@ public static class SQLQueries { public static final String INVALIDATE_USER_CODE_BY_SCENARIO = "DELETE FROM IDN_RECOVERY_DATA WHERE " + "USER_NAME = ? AND SCENARIO = ? AND STEP = ? AND USER_DOMAIN = ? AND TENANT_ID =?"; + public static final String INVALIDATE_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_DATA WHERE " + + "RECOVERY_FLOW_ID = ?"; + public static final String UPDATE_CODE = "UPDATE IDN_RECOVERY_DATA SET CODE = ?, STEP = ?, REMAINING_SETS = ? " + "WHERE CODE = ?"; @@ -666,6 +685,24 @@ public static class SQLQueries { public static final String LOAD_RECOVERY_DATA_OF_USER_BY_STEP_CASE_INSENSITIVE = "SELECT " + "* FROM IDN_RECOVERY_DATA WHERE LOWER(USER_NAME)=LOWER(?) AND SCENARIO = ? AND USER_DOMAIN = ? " + "AND TENANT_ID = ? AND STEP = ?"; + + public static final String STORE_RECOVERY_OTP_DATA = "INSERT INTO IDN_RECOVERY_OTP_DATA " + + "(RECOVERY_FLOW_ID, OTP, ATTEMPT, RESEND_COUNT, TIME_CREATED) VALUES (?,?,?,?,?)"; + + public static final String UPDATE_RECOVERY_OTP_DATA = "UPDATE IDN_RECOVERY_OTP_DATA SET OTP = ? " + + "WHERE RECOVERY_FLOW_ID = ?"; + + public static final String UPDATE_OTP_ATTEMPT = "UPDATE IDN_RECOVERY_OTP_DATA SET ATTEMPT = ? " + + "WHERE RECOVERY_FLOW_ID = ?"; + + public static final String UPDATE_OTP_RESEND_COUNT = "UPDATE IDN_RECOVERY_OTP_DATA SET RESEND_COUNT = ? " + + "WHERE RECOVERY_FLOW_ID = ?"; + + public static final String LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * FROM IDN_RECOVERY_OTP_DATA " + + "WHERE RECOVERY_FLOW_ID = ?"; + + public static final String INVALIDATE_OTP_DATA_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_OTP_DATA WHERE " + + "RECOVERY_FLOW_ID = ?"; } public static class Questions { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index be4344bb1a..c16664dd90 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -43,6 +43,7 @@ import org.wso2.carbon.identity.recovery.internal.service.impl.UserAccountRecoveryManager; import org.wso2.carbon.identity.recovery.model.Property; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; +import org.wso2.carbon.identity.recovery.model.UserRecoveryFlowData; import org.wso2.carbon.identity.recovery.store.JDBCRecoveryDataStore; import org.wso2.carbon.identity.recovery.store.UserRecoveryDataStore; import org.wso2.carbon.identity.recovery.util.Utils; @@ -147,6 +148,19 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese UserRecoveryData userRecoveryData = userAccountRecoveryManager .getUserRecoveryData(resendCode, RecoverySteps.RESEND_CONFIRMATION_CODE); User user = userRecoveryData.getUser(); + String recoveryFlowId = userRecoveryData.getRecoveryFlowId(); + UserRecoveryFlowData userRecoveryFlowData = userAccountRecoveryManager.loadUserRecoveryFlowData( + userRecoveryData); + int resendCount = userRecoveryFlowData.getResendCount(); + if (resendCount >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, tenantDomain))) { + userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + recoveryFlowId); + } + userAccountRecoveryManager.updateRecoveryDataResendCount(recoveryFlowId, resendCount + 1); // Validate the tenant domain and the recovery scenario in the request. validateRequestAttributes(user, scenario, userRecoveryData.getRecoveryScenario(), tenantDomain, resendCode); @@ -164,8 +178,12 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese } else { userRecoveryDataStore.invalidate(user); confirmationCode = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), recoveryScenario); + if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, + NotificationChannels.SMS_CHANNEL.getChannelType())) { + confirmationCode = recoveryFlowId + "." + confirmationCode; + } // Store new confirmation code. - addRecoveryDataObject(confirmationCode, notificationChannel, scenario, step, user); + addRecoveryDataObject(confirmationCode, recoveryFlowId, notificationChannel, scenario, step, user); } ResendConfirmationDTO resendConfirmationDTO = new ResendConfirmationDTO(); @@ -185,6 +203,7 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese IdentityRecoveryConstants.SuccessEvents.SUCCESS_STATUS_CODE_RESEND_CONFIRMATION_CODE.getCode()); resendConfirmationDTO.setSuccessMessage( IdentityRecoveryConstants.SuccessEvents.SUCCESS_STATUS_CODE_RESEND_CONFIRMATION_CODE.getMessage()); + resendConfirmationDTO.setRecoveryFlowId(recoveryFlowId); return resendConfirmationDTO; } @@ -269,6 +288,7 @@ private String generateResendCode(String notificationChannel, RecoveryScenarios UserRecoveryData userRecoveryData) throws IdentityRecoveryServerException { String resendCode = UUID.randomUUID().toString(); + String recoveryFlowId = userRecoveryData.getRecoveryFlowId(); /* Checking whether the existing confirmation code issued time is in the tolerance period. If so this code updates the existing RESEND_CONFIRMATION_CODE with the new one by not changing the TIME_CREATED. */ if (Utils.reIssueExistingConfirmationCode(getResendConfirmationCodeData(userRecoveryData.getUser()), @@ -276,8 +296,8 @@ private String generateResendCode(String notificationChannel, RecoveryScenarios invalidateResendConfirmationCode(resendCode, notificationChannel, userRecoveryData); return resendCode; } - addRecoveryDataObject(resendCode, notificationChannel, scenario, RecoverySteps.RESEND_CONFIRMATION_CODE, - userRecoveryData.getUser()); + addRecoveryDataObject(resendCode, recoveryFlowId, notificationChannel, scenario, + RecoverySteps.RESEND_CONFIRMATION_CODE, userRecoveryData.getUser()); return resendCode; } @@ -337,17 +357,21 @@ private void invalidateResendConfirmationCode(String resendCode, String notifica * @param user User object * @throws IdentityRecoveryServerException Error storing recovery data */ - private void addRecoveryDataObject(String secretKey, String recoveryData, RecoveryScenarios recoveryScenario, - RecoverySteps recoveryStep, User user) + private void addRecoveryDataObject(String secretKey, String recoveryFlowId, String recoveryData, + RecoveryScenarios recoveryScenario, RecoverySteps recoveryStep, User user) throws IdentityRecoveryServerException { - UserRecoveryData recoveryDataDO = new UserRecoveryData(user, secretKey, recoveryScenario, recoveryStep); + UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, recoveryScenario, recoveryStep); // Store available channels in remaining setIDs. recoveryDataDO.setRemainingSetIds(recoveryData); try { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - userRecoveryDataStore.store(recoveryDataDO); + if (StringUtils.equals(RecoverySteps.UPDATE_PASSWORD.name(), String.valueOf(recoveryStep))) { + userRecoveryDataStore.storeConfirmationCode(recoveryDataDO); + } else { + userRecoveryDataStore.store(recoveryDataDO); + } } catch (IdentityRecoveryException e) { throw Utils.handleServerException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_ERROR_STORING_RECOVERY_DATA, diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java index 3286603bf9..0405768fad 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java @@ -112,6 +112,10 @@ public Map getPropertyNameMapping() { "Recovery callback URL regex"); nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET, "Enable Auto Login After Password Reset"); + nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, + "Max failed attempts for OTP based recovery"); + nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, + "Max resend attempts for OTP based recovery"); return nameMapping; } @@ -168,6 +172,8 @@ public String[] getPropertyNames() { properties.add(IdentityRecoveryConstants.ConnectorConfig.FORCE_MIN_NO_QUESTION_ANSWERED); properties.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_CALLBACK_REGEX); properties.add(IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET); + properties.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS); + properties.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS); return properties.toArray(new String[0]); } @@ -194,6 +200,8 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG String minimumForcedChallengeQuestionsAnswered = "1"; String recoveryCallbackRegex = IdentityRecoveryConstants.DEFAULT_CALLBACK_REGEX; String enableAdminPasswordResetAutoLoginProperty = "false"; + String recoveryOTPMaxFailedAttempts = "3"; + String recoveryOTPMaxResendAttempts = "5"; String notificationBasedPasswordRecovery = IdentityUtil.getProperty( IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_BASED_PW_RECOVERY); @@ -234,6 +242,10 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG IdentityRecoveryConstants.ConnectorConfig.RECOVERY_CALLBACK_REGEX); String adminPasswordResetAutoLoginProperty = IdentityUtil.getProperty( IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET); + String otpMaxFailedAttempts = IdentityUtil.getProperty(IdentityRecoveryConstants. + ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS); + String otpMaxResendAttempts = IdentityUtil.getProperty(IdentityRecoveryConstants. + ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS); if (StringUtils.isNotEmpty(expiryTimeSMSOTPProperty)) { expiryTimeSMSOTP = expiryTimeSMSOTPProperty; @@ -295,6 +307,12 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG if (StringUtils.isNotEmpty(adminPasswordResetAutoLoginProperty)) { enableAdminPasswordResetAutoLoginProperty = adminPasswordResetAutoLoginProperty; } + if (StringUtils.isNotEmpty(otpMaxFailedAttempts)) { + recoveryOTPMaxFailedAttempts = otpMaxFailedAttempts; + } + if (StringUtils.isNotEmpty(otpMaxResendAttempts)) { + recoveryOTPMaxResendAttempts = otpMaxResendAttempts; + } Map defaultProperties = new HashMap<>(); defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_BASED_PW_RECOVERY, @@ -336,6 +354,10 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_CALLBACK_REGEX, recoveryCallbackRegex); defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET, enableAdminPasswordResetAutoLoginProperty); + defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, + recoveryOTPMaxFailedAttempts); + defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, + recoveryOTPMaxResendAttempts); Properties properties = new Properties(); properties.putAll(defaultProperties); @@ -411,6 +433,12 @@ public Map getMetaData() { meta.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_CALLBACK_REGEX, getPropertyObject(IdentityMgtConstants.DataTypes.STRING.getValue())); + meta.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, + getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue())); + + meta.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, + getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue())); + return meta; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java index c623e80a60..4ed8fbab2c 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java @@ -32,6 +32,11 @@ public class PasswordRecoverDTO { */ private String message; + /** + * Recovery flow id. + */ + private String recoveryFlowId; + /** * User notified channel. */ @@ -108,6 +113,26 @@ public void setMessage(String message) { this.message = message; } + /** + * Get the recovery flow id. + * + * @return Recovery flow id + */ + public String getRecoveryFlowId() { + + return recoveryFlowId; + } + + /** + * Set the recovery flow id. + * + * @param recoveryFlowId Recovery flow id + */ + public void setRecoveryFlowId(String recoveryFlowId) { + + this.recoveryFlowId = recoveryFlowId; + } + /** * Get the channel which the notification was sent. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java index 731f1a3143..99f50fd11e 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java @@ -27,6 +27,11 @@ public class RecoveryChannelInfoDTO { */ private String username; + /** + * Recovery flow id of the recovery flow. + */ + private String recoveryFlowId; + /** * Recovery Code given to the user. */ @@ -57,6 +62,26 @@ public void setUsername(String username) { this.username = username; } + /** + * Get the recovery Flow id of the recovery flow. + * + * @return Recovery Flow id of the recovery flow + */ + public String getRecoveryFlowId() { + + return recoveryFlowId; + } + + /** + * Set the recovery Flow id of the recovery flow. + * + * @param recoveryFlowId Recovery Flow Id + */ + public void setRecoveryFlowId(String recoveryFlowId) { + + this.recoveryFlowId = recoveryFlowId; + } + /** * Get the recovery Code of the user. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java index 2d35c23bc4..937ba587c9 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java @@ -27,6 +27,11 @@ public class RecoveryInformationDTO { */ private String username; + /** + * Recovery flow id of the initiated recovery flow. + */ + private String recoveryFlowId; + /** * Available Recovery channel Information. */ @@ -132,6 +137,26 @@ public void setUsername(String userName) { this.username = userName; } + /** + * Get recovery flow id. + * + * @return recoveryFlowId + */ + public String getRecoveryFlowId() { + + return recoveryFlowId; + } + + /** + * Set recovery flow id. + * + * @param recoveryFlowId RecoveryFlowId + */ + public void setRecoveryFlowId(String recoveryFlowId) { + + this.recoveryFlowId = recoveryFlowId; + } + /** * Get RecoveryChannelInfoDTO. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java index 76f7b51b0f..77cf2ae5bf 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java @@ -27,6 +27,11 @@ public class ResendConfirmationDTO { */ private String successMessage; + /** + * Recovery flow id. + */ + private String recoveryFlowId; + /** * Recovery Info sent channel. */ @@ -47,6 +52,26 @@ public class ResendConfirmationDTO { */ private String externalConfirmationCode; + /** + * Get recovery flow id. + * + * @return Recovery flow id + */ + public String getRecoveryFlowId() { + + return recoveryFlowId; + } + + /** + * Set recovery flow id. + * + * @param recoveryFlowId Recovery Flow Id + */ + public void setRecoveryFlowId(String recoveryFlowId) { + + this.recoveryFlowId = recoveryFlowId; + } + /** * Get external confirmation code. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index fbbc468009..af8a031a1a 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -42,6 +42,7 @@ import org.wso2.carbon.identity.recovery.internal.IdentityRecoveryServiceDataHolder; import org.wso2.carbon.identity.recovery.model.NotificationChannel; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; +import org.wso2.carbon.identity.recovery.model.UserRecoveryFlowData; import org.wso2.carbon.identity.recovery.store.JDBCRecoveryDataStore; import org.wso2.carbon.identity.recovery.store.UserRecoveryDataStore; import org.wso2.carbon.identity.recovery.util.Utils; @@ -146,6 +147,7 @@ public RecoveryChannelInfoDTO retrieveUserRecoveryInformation(Map getInternalNotificationChannelList(String user * @param notificationChannelDTOs List of NotificationChannelsResponseDTOs available for the user. * @return RecoveryChannelInfoDTO object. */ - private RecoveryChannelInfoDTO buildUserRecoveryInformationResponseDTO(String username, String recoveryCode, - NotificationChannelDTO[] notificationChannelDTOs) { + private RecoveryChannelInfoDTO buildUserRecoveryInformationResponseDTO(String username, String recoveryFlowId, + String recoveryCode, NotificationChannelDTO[] notificationChannelDTOs) { RecoveryChannelInfoDTO recoveryChannelInfoDTO = new RecoveryChannelInfoDTO(); recoveryChannelInfoDTO.setUsername(username); + recoveryChannelInfoDTO.setRecoveryFlowId(recoveryFlowId); recoveryChannelInfoDTO.setRecoveryCode(recoveryCode); recoveryChannelInfoDTO.setNotificationChannelDTOs(notificationChannelDTOs); return recoveryChannelInfoDTO; @@ -925,6 +930,112 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr return recoveryData; } + /** + * Get user recovery data using the recovery flow id. + * + * @param recoveryFlowId Recovery flow id of the user. + * @param step Recovery step + * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. + */ + public UserRecoveryData getUserRecoveryDataFromFlowId(String recoveryFlowId, RecoverySteps step) + throws IdentityRecoveryException { + + UserRecoveryData recoveryData; + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + try { + // Retrieve recovery data bound to the recoveryFlowId. + recoveryData = userRecoveryDataStore.loadFromRecoveryFlowId(recoveryFlowId, step); + } catch (IdentityRecoveryException e) { + // Map code expired error to new error codes for user account recovery. + if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_FLOW_ID.getCode().equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); + } else if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_FLOW_ID.getCode().equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID.getCode()); + } else if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_CODE.getCode() + .equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_RECOVERY_CODE.getCode()); + } else { + e.setErrorCode(Utils.prependOperationScenarioToErrorCode(e.getErrorCode(), + IdentityRecoveryConstants.USER_ACCOUNT_RECOVERY)); + } + throw e; + } + if (recoveryData == null) { + throw Utils + .handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_ACCOUNT_RECOVERY_DATA, + recoveryFlowId); + } + return recoveryData; + } + + /** + * Get user recovery flow data using the recovery flow id. + * + * @param recoveryDataDO User Recovery Data object. + * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. + */ + public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDataDO) + throws IdentityRecoveryException { + + UserRecoveryFlowData userRecoveryFlowData; + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + try { + userRecoveryFlowData = userRecoveryDataStore.loadRecoveryFlowData(recoveryDataDO); + } catch (IdentityRecoveryException e) { + // Map code expired error to new error codes for user account recovery. + if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_FLOW_ID.getCode().equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); + } else if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_FLOW_ID.getCode().equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID.getCode()); + } else { + e.setErrorCode(Utils.prependOperationScenarioToErrorCode(e.getErrorCode(), + IdentityRecoveryConstants.USER_ACCOUNT_RECOVERY)); + } + throw e; + } + if (userRecoveryFlowData == null) { + throw Utils + .handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_RECOVERY_FLOW_DATA, + recoveryDataDO.getRecoveryFlowId()); + } + return userRecoveryFlowData; + } + + /** + * Update recovery OTP attempt. + * + * @param recoveryFlowId Recovery Flow Id + * @param attempt Current Attempt + */ + public void updateRecoveryDataAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { + + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + userRecoveryDataStore.updateOTPAttempt(recoveryFlowId, attempt); + } + + /** + * Update recovery OTP resend count. + * + * @param recoveryFlowId Recovery Flow Id + * @param resendCount Current Resend Count + */ + public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { + + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + userRecoveryDataStore.updateOTPResendCount(recoveryFlowId, resendCount); + } + + /** + * Invalidate the recovery Data. + * + * @param recoveryFlowId Recovery Flow Id + */ + public void invalidateRecoveryData(String recoveryFlowId) throws IdentityRecoveryException { + + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + userRecoveryDataStore.invalidateWithRecoveryFlowId(recoveryFlowId); + } + /** * Add the notification channel recovery data to the store. * @@ -935,20 +1046,25 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object * @throws IdentityRecoveryServerException If an error occurred while storing recovery data. */ - private void addRecoveryDataObject(String username, String tenantDomain, String secretKey, + private void addRecoveryDataObject(String username, String tenantDomain, String recoveryFlowId, String secretKey, RecoveryScenarios scenario, String recoveryData) throws IdentityRecoveryServerException { // Create a user object. User user = Utils.buildUser(username, tenantDomain); - UserRecoveryData recoveryDataDO = new UserRecoveryData(user, secretKey, scenario, + UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, scenario, RecoverySteps.SEND_RECOVERY_INFORMATION); // Store available channels in remaining setIDs. recoveryDataDO.setRemainingSetIds(recoveryData); try { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - userRecoveryDataStore.invalidate(user); - userRecoveryDataStore.store(recoveryDataDO); + UserRecoveryData userRecoveryDataDO = userRecoveryDataStore.loadWithoutCodeExpiryValidation(user); + if (userRecoveryDataDO != null && userRecoveryDataDO.getRecoveryFlowId() != null) { + userRecoveryDataStore.invalidateWithRecoveryFlowId(userRecoveryDataDO.getRecoveryFlowId()); + } else { + userRecoveryDataStore.invalidate(user); + } + userRecoveryDataStore.storeInit(recoveryDataDO); } catch (IdentityRecoveryException e) { throw Utils.handleServerException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_ERROR_STORING_RECOVERY_DATA, diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 769adc833a..66e9be765e 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -106,7 +106,9 @@ public RecoveryInformationDTO initiate(Map claims, String tenant properties); RecoveryInformationDTO recoveryInformationDTO = new RecoveryInformationDTO(); String username = recoveryChannelInfoDTO.getUsername(); + String recoveryFlowId = recoveryChannelInfoDTO.getRecoveryFlowId(); recoveryInformationDTO.setUsername(username); + recoveryInformationDTO.setRecoveryFlowId(recoveryFlowId); // Do not add recovery channel information if Notification based recovery is not enabled. recoveryInformationDTO.setNotificationBasedRecoveryEnabled(isNotificationBasedRecoveryEnabled); if (isNotificationBasedRecoveryEnabled) { @@ -168,7 +170,9 @@ public PasswordRecoverDTO notify(String recoveryCode, String channelId, String t manageNotificationsInternally, properties); String secretKey = notificationResponseBean.getKey(); String resendCode = generateResendCode(notificationChannel, userRecoveryData); - return buildPasswordRecoveryResponseDTO(notificationChannel, secretKey, resendCode); + String recoveryFlowId = userRecoveryData.getRecoveryFlowId(); + userAccountRecoveryManager.loadUserRecoveryFlowData(userRecoveryData); + return buildPasswordRecoveryResponseDTO(notificationChannel, secretKey, resendCode, recoveryFlowId); } /** @@ -202,6 +206,67 @@ public PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain return buildPasswordResetCodeDTO(confirmationCode); } + /** + * Validate the code given for password recovery and return the password reset code. + * + * @param otp One Time Password + * @param confirmationCode Confirmation code + * @param tenantDomain Tenant domain + * @param properties Meta properties in the confirmation request + * @return PasswordResetCodeDTO {@link PasswordResetCodeDTO} object which contains password reset code + * @throws IdentityRecoveryException Error while confirming password recovery + */ + @Override + public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String tenantDomain, + Map properties) throws IdentityRecoveryException { + + validateTenantDomain(tenantDomain); + UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); + String[] ids = confirmationCode.split("\\."); + String recoveryFlowId; + String code; + if (ids.length != 2) { + recoveryFlowId = confirmationCode; + code = otp; + } else { + recoveryFlowId = ids[0]; + code = confirmationCode; + } + // Get Recovery data. + UserRecoveryData userRecoveryData = userAccountRecoveryManager + .getUserRecoveryDataFromFlowId(recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); + int attempt = userRecoveryData.getAttempt(); + if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, + tenantDomain); + } + String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), + userRecoveryData.getUser().getUserStoreDomain()); + if (StringUtils.equals(code, userRecoveryData.getSecret())) { + if (log.isDebugEnabled()) { + log.debug("Valid confirmation code for user: " + domainQualifiedName); + } + return buildPasswordResetCodeDTO(code); + } else { + if ((attempt + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { + userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + recoveryFlowId); + } + userAccountRecoveryManager.updateRecoveryDataAttempt(recoveryFlowId, attempt + 1); + if (log.isDebugEnabled()) { + log.debug("Invalid confirmation code for user: " + domainQualifiedName); + } + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); + } + } + /** * Reset the password for password recovery, if the password reset code is valid. * @@ -246,6 +311,50 @@ public SuccessfulPasswordResetDTO reset(String resetCode, char[] password, Map properties) + throws IdentityRecoveryException { + + // Validate the password. + if (ArrayUtils.isEmpty(password)) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_PASSWORD_IN_REQUEST.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_PASSWORD_IN_REQUEST.getMessage(), null); + } + String newPassword = String.valueOf(password); + NotificationPasswordRecoveryManager notificationPasswordRecoveryManager = NotificationPasswordRecoveryManager + .getInstance(); + Property[] metaProperties = buildPropertyList(null, properties); + try { + notificationPasswordRecoveryManager.updatePassword(resetCode, confirmationCode, newPassword, metaProperties); + } catch (IdentityRecoveryServerException e) { + String errorCode = Utils.prependOperationScenarioToErrorCode(e.getErrorCode(), + IdentityRecoveryConstants.PASSWORD_RECOVERY_SCENARIO); + throw Utils.handleServerException(errorCode, e.getMessage(), null); + } catch (IdentityRecoveryClientException e) { + throw mapClientExceptionWithImprovedErrorCodes(e); + } catch (IdentityEventException e) { + if (log.isDebugEnabled()) { + log.debug("PasswordRecoveryManagerImpl: Error while resetting password ", e); + } + throw Utils.handleServerException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UNEXPECTED_ERROR_PASSWORD_RESET.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UNEXPECTED_ERROR_PASSWORD_RESET.getMessage(), + null); + } + return buildSuccessfulPasswordUpdateDTO(); + } + /** * Resend the password recovery information to the user via user specified channel. * @@ -461,7 +570,7 @@ private PasswordResetCodeDTO buildPasswordResetCodeDTO(String resetCode) { * @return PasswordRecoverDTO object */ private PasswordRecoverDTO buildPasswordRecoveryResponseDTO(String notificationChannel, String confirmationCode, - String resendCode) { + String resendCode, String recoveryFlowId) { PasswordRecoverDTO passwordRecoverDTO = new PasswordRecoverDTO(); passwordRecoverDTO.setNotificationChannel(notificationChannel); @@ -469,6 +578,7 @@ private PasswordRecoverDTO buildPasswordRecoveryResponseDTO(String notificationC passwordRecoverDTO.setConfirmationCode(confirmationCode); } passwordRecoverDTO.setResendCode(resendCode); + passwordRecoverDTO.setRecoveryFlowId(recoveryFlowId); passwordRecoverDTO.setCode( IdentityRecoveryConstants.SuccessEvents.SUCCESS_STATUS_CODE_PASSWORD_RECOVERY_INTERNALLY_NOTIFIED .getCode()); @@ -581,7 +691,8 @@ private String generateResendCode(String notificationChannel, UserRecoveryData u invalidateRecoveryInfoSendCode(resendCode, notificationChannel, userRecoveryData); return resendCode; } - addRecoveryDataObject(resendCode, notificationChannel, userRecoveryData.getUser()); + addRecoveryDataObject(resendCode, userRecoveryData.getRecoveryFlowId(), notificationChannel, + userRecoveryData.getUser()); return resendCode; } @@ -638,10 +749,10 @@ private void invalidateRecoveryInfoSendCode(String resendCode, String notificati * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object * @throws IdentityRecoveryServerException Error storing recovery data */ - private void addRecoveryDataObject(String secretKey, String recoveryData, User user) + private void addRecoveryDataObject(String secretKey, String recoveryFlowId, String recoveryData, User user) throws IdentityRecoveryServerException { - UserRecoveryData recoveryDataDO = new UserRecoveryData(user, secretKey, + UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY, RecoverySteps.RESEND_CONFIRMATION_CODE); // Store available channels in remaining setIDs. recoveryDataDO.setRemainingSetIds(recoveryData); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java index 5cc530d0c4..e6678d752b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java @@ -160,7 +160,12 @@ public UsernameRecoverDTO notify(String recoveryCode, String channelId, String t // Validate Recovery data. UserRecoveryData userRecoveryData = recoveryAccountManager .getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION); - invalidateRecoveryCode(recoveryCode); + String recoveryFlowId = userRecoveryData.getRecoveryFlowId(); + if (recoveryFlowId != null) { + invalidateRecoveryFlowId(recoveryFlowId); + } else { + invalidateRecoveryCode(recoveryCode); + } String notificationChannel = extractNotificationChannelDetails(userRecoveryData.getRemainingSetIds(), channelIdCode); @@ -285,6 +290,17 @@ private void invalidateRecoveryCode(String recoveryCode) throws IdentityRecovery userRecoveryDataStore.invalidate(recoveryCode); } + /** + * Invalidate the recovery flow id. + * + * @param recoveryFlowId Recovery flow id + */ + private void invalidateRecoveryFlowId(String recoveryFlowId) throws IdentityRecoveryException { + + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + userRecoveryDataStore.invalidateWithRecoveryFlowId(recoveryFlowId); + } + /** * Trigger notification to send userName recovery information. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java index 0a856dff67..2fb875c4d0 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java @@ -28,7 +28,10 @@ */ public class UserRecoveryData { private User user; + private String recoveryFlowId; private String secret; + private int attempt; + private int resendCount; private String remainingSetIds; private boolean codeExpired; @@ -46,6 +49,15 @@ public UserRecoveryData(User user, String secret, Enum recoveryScenario, Enum re this.recoveryStep = recoveryStep; } + public UserRecoveryData(User user, String recoveryFlowId, String secret, Enum recoveryScenario, Enum recoveryStep) { + + this.user = user; + this.recoveryFlowId= recoveryFlowId; + this.secret = secret; + this.recoveryScenario = recoveryScenario; + this.recoveryStep = recoveryStep; + } + public UserRecoveryData(User user, String secret, Enum recoveryScenario) { this.user = user; @@ -71,6 +83,31 @@ public UserRecoveryData(User user, String secret, Enum recoveryScenario, Enum re this.timeCreated = timeCreated; } + public UserRecoveryData(User user, String recoveryFlowId, String secret, Enum recoveryScenario, Enum recoveryStep, + Timestamp timeCreated) { + + this.user = user; + this.recoveryFlowId=recoveryFlowId; + this.secret = secret; + this.recoveryScenario = recoveryScenario; + this.recoveryStep = recoveryStep; + this.timeCreated = timeCreated; + } + + public UserRecoveryData(User user, String recoveryFlowId, String secret, int attempt, int resendCount, + Enum recoveryScenario, Enum recoveryStep, String remainingSetIds, Timestamp timeCreated) { + + this.user = user; + this.recoveryFlowId=recoveryFlowId; + this.secret = secret; + this.attempt = attempt; + this.resendCount = resendCount; + this.recoveryScenario = recoveryScenario; + this.recoveryStep = recoveryStep; + this.remainingSetIds = remainingSetIds; + this.timeCreated = timeCreated; + } + public Timestamp getTimeCreated() { return timeCreated; @@ -81,6 +118,16 @@ public void setTimeCreated(Timestamp timeCreated) { this.timeCreated = timeCreated; } + public String getRecoveryFlowId() { + + return recoveryFlowId; + } + + public void setRecoveryFlowId(String recoveryFlowId) { + + this.recoveryFlowId = recoveryFlowId; + } + public String getRemainingSetIds() { return remainingSetIds; } @@ -98,6 +145,24 @@ public User getUser() { return user; } + public int getAttempt() { + return attempt; + } + + public void setAttempt(int attempt) { + + this.attempt = attempt; + } + + public int getResendCount() { + return resendCount; + } + + public void setResendCount(int resendCount) { + + this.resendCount = resendCount; + } + public Enum getRecoveryScenario() { return recoveryScenario; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java new file mode 100644 index 0000000000..04106972b5 --- /dev/null +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.recovery.model; + +import java.sql.Timestamp; + +/** + * This object represents an entry of the identity metadata database. + */ +public class UserRecoveryFlowData { + private String recoveryId; + + private Timestamp timeCreated; + + private int attempts; + + private int resendCount; + + public UserRecoveryFlowData(String recoveryId) { + + this.recoveryId = recoveryId; + } + public UserRecoveryFlowData(String recoveryId, Timestamp timeCreated) { + + this.recoveryId = recoveryId; + this.timeCreated = timeCreated; + } + + public UserRecoveryFlowData(String recoveryId, Timestamp timeCreated, int attempts, int resendCount) { + + this.recoveryId = recoveryId; + this.timeCreated = timeCreated; + this.attempts = attempts; + this.resendCount = resendCount; + } + + public Timestamp getTimeCreated() { + + return timeCreated; + } + + public void setTimeCreated(Timestamp timeCreated) { + + this.timeCreated = timeCreated; + } + + public String getRecoveryId() { + return recoveryId; + } + + /** + * Set the recovery id. + * + * @param recoveryId Recovery Id. + */ + public void setRecoveryId(String recoveryId) { + + this.recoveryId = recoveryId; + } + + public int getAttempts() { + return attempts; + } + + /** + * Set the attempts. + * + * @param attempts OTP attempts. + */ + public void setAttempts(int attempts) { + + this.attempts = attempts; + } + + public int getResendCount() { + return resendCount; + } + + /** + * Set the resendCount. + * + * @param resendCount resendCount. + */ + public void setResendCount(int resendCount) { + + this.resendCount = resendCount; + } +} 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 1caa1066a1..cdd5d5c0e6 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 @@ -249,16 +249,25 @@ private void checkAccountPendingStatus(User user) throws IdentityRecoveryExcepti private UserRecoveryData generateNewConfirmationCode(User user, String notificationChannel) throws IdentityRecoveryException { + String recoveryFlowId = null; UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + UserRecoveryData userRecoveryData = userRecoveryDataStore.loadWithoutCodeExpiryValidation(user); + if (userRecoveryData != null) { + recoveryFlowId = userRecoveryData.getRecoveryFlowId(); + } userRecoveryDataStore.invalidate(user); String secretKey = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY.name()); - UserRecoveryData recoveryDataDO = new UserRecoveryData(user, secretKey, + if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, + NotificationChannels.SMS_CHANNEL.getChannelType())) { + secretKey = recoveryFlowId + "." + secretKey; + } + UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY, RecoverySteps.UPDATE_PASSWORD); // Store the notified channel in the recovery object for future reference. recoveryDataDO.setRemainingSetIds(notificationChannel); - userRecoveryDataStore.store(recoveryDataDO); + userRecoveryDataStore.storeConfirmationCode(recoveryDataDO); return recoveryDataDO; } @@ -533,6 +542,21 @@ public void updatePassword(String code, String password, Property[] properties) updateUserPassword(code, password, properties); } + /** + * Update the password of the user. + * + * @param code Password Reset code. + * @param confirmationCode Confirmation code. + * @param password New password. + * @param properties Properties. + * @throws IdentityRecoveryException Error while updating the password. + * @throws IdentityEventException Error while updating the password. + */ + public void updatePassword(String code, String confirmationCode, String password, Property[] properties) + throws IdentityRecoveryException, IdentityEventException { + updateUserPassword(code, confirmationCode, password, properties); + } + /** * Update the password of the user. * @@ -612,6 +636,97 @@ public User updateUserPassword(String code, String password, Property[] properti return userRecoveryData.getUser(); } + /** + * Update the password of the user. + * + * @param code Password Reset code. + * @param confirmationCode Confirmation code. + * @param password New password. + * @param properties Properties. + * @throws IdentityRecoveryException Error while updating the password. + * @throws IdentityEventException Error while updating the password. + */ + public User updateUserPassword(String code, String confirmationCode, String password, Property[] properties) + throws IdentityRecoveryException, IdentityEventException { + + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + UserRecoveryData userRecoveryData = userRecoveryDataStore.loadFromRecoveryFlowId(confirmationCode, + RecoverySteps.UPDATE_PASSWORD); + validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); + publishEvent(userRecoveryData.getUser(), null, null, password, properties, + IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); + validateTenantDomain(userRecoveryData.getUser()); + int attempt = userRecoveryData.getAttempt(); + + if (!StringUtils.equals(code, userRecoveryData.getSecret())) { + if ((attempt + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser().getTenantDomain()))) { + userRecoveryDataStore.invalidateWithRecoveryFlowId(confirmationCode); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + confirmationCode); + } + userRecoveryDataStore.updateOTPAttempt(confirmationCode, attempt + 1); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); + } + + // Get the notification channel. + String notificationChannel = getServerSupportedNotificationChannel(userRecoveryData.getRemainingSetIds()); + boolean notificationsInternallyManaged = Boolean.parseBoolean( + Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE, + userRecoveryData.getUser().getTenantDomain())); + boolean isNotificationSendWhenSuccess = Boolean.parseBoolean(Utils.getRecoveryConfigs( + IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS, + userRecoveryData.getUser().getTenantDomain())); + String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), + userRecoveryData.getUser().getUserStoreDomain()); + + // Update the password. + updateNewPassword(userRecoveryData.getUser(), password, domainQualifiedName, userRecoveryData, + notificationsInternallyManaged); + userRecoveryDataStore.invalidateWithRecoveryFlowId(userRecoveryData.getRecoveryFlowId()); + if (notificationsInternallyManaged && + !NotificationChannels.EXTERNAL_CHANNEL.getChannelType().equals(notificationChannel)) { + String emailTemplate = null; + if (isAskPasswordFlow(userRecoveryData) && + isAskPasswordEmailTemplateTypeExists(userRecoveryData.getUser().getTenantDomain())) { + emailTemplate = IdentityRecoveryConstants.ACCOUNT_ACTIVATION_SUCCESS; + } else if (isNotificationSendWhenSuccess) { + emailTemplate = IdentityRecoveryConstants.NOTIFICATION_TYPE_PASSWORD_RESET_SUCCESS; + } + try { + String eventName = Utils.resolveEventName(notificationChannel); + if (StringUtils.isNotBlank(emailTemplate)) { + triggerNotification(userRecoveryData.getUser(), notificationChannel, emailTemplate, + StringUtils.EMPTY, eventName, properties, userRecoveryData); + } + } catch (IdentityRecoveryException e) { + String errorMsg = String.format("Error while sending password reset success notification to user : %s", + userRecoveryData.getUser().getUserName()); + log.error(errorMsg); + String recoveryScenario = userRecoveryData.getRecoveryScenario().name(); + String recoveryStep = userRecoveryData.getRecoveryStep().name(); + auditPasswordReset(userRecoveryData.getUser(), AuditConstants.ACTION_PASSWORD_RESET, errorMsg, + FrameworkConstants.AUDIT_SUCCESS, recoveryScenario, recoveryStep); + } + } + publishEvent(userRecoveryData.getUser(), null, code, password, properties, + IdentityEventConstants.Event.POST_ADD_NEW_PASSWORD, userRecoveryData); + if (log.isDebugEnabled()) { + String msg = "Password is updated for user: " + domainQualifiedName; + log.debug(msg); + } + String recoveryScenario = userRecoveryData.getRecoveryScenario().name(); + String recoveryStep = userRecoveryData.getRecoveryStep().name(); + auditPasswordReset(userRecoveryData.getUser(), AuditConstants.ACTION_PASSWORD_RESET, null, + FrameworkConstants.AUDIT_SUCCESS, recoveryScenario, recoveryStep); + + return userRecoveryData.getUser(); + } + /** * Update the new password of the user. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java index 6571b3d9d5..ef0af9a584 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java @@ -70,6 +70,19 @@ PasswordRecoverDTO notify(String recoveryId, String channelId, String tenantDoma PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain, Map properties) throws IdentityRecoveryException; + /** + * Validate the code given for password recovery and return the password reset token. + * + * @param otp One Time Password + * @param confirmationCode Confirmation code + * @param tenantDomain Tenant domain + * @param properties Meta properties in the confirmation request + * @return PasswordResetCodeDTO {@link PasswordResetCodeDTO} object which contains password reset code + * @throws IdentityRecoveryException Error while confirming password recovery + */ + PasswordResetCodeDTO confirm(String otp, String confirmationCode, String tenantDomain, Map properties) throws IdentityRecoveryException; + /** * Update the password for password recovery, if the password reset code is valid. * @@ -83,6 +96,20 @@ PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain, Map properties) throws IdentityRecoveryException; + /** + * Update the password for password recovery, if the password reset code is valid. + * + * @param resetCode Password reset code + * @param confirmationCode Confirmation code + * @param password New password + * @param properties Properties + * @return SuccessfulPasswordResetDTO {@link SuccessfulPasswordResetDTO} object which contain the information + * for a successful password update + * @throws IdentityRecoveryException Error while resetting the password + */ + SuccessfulPasswordResetDTO reset(String resetCode, String confirmationCode, char[] password, Map properties) + throws IdentityRecoveryException; + /** * Resend the password recovery information to the user via user specified channel. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 4212167d30..4ffd2a8154 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -36,6 +36,7 @@ import org.wso2.carbon.identity.recovery.RecoverySteps; import org.wso2.carbon.identity.recovery.internal.IdentityRecoveryServiceDataHolder; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; +import org.wso2.carbon.identity.recovery.model.UserRecoveryFlowData; import org.wso2.carbon.identity.recovery.util.Utils; import java.sql.Connection; @@ -58,6 +59,8 @@ import static org.wso2.carbon.identity.event.IdentityEventConstants.EventProperty.OPERATION_DESCRIPTION; import static org.wso2.carbon.identity.event.IdentityEventConstants.EventProperty.OPERATION_STATUS; import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_CODE; +import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_FLOW_ID; +import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_FLOW_ID; import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE; import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_RECOVERY_DATA_NOT_FOUND_FOR_USER; import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UNEXPECTED; @@ -93,6 +96,7 @@ public void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryExcept prepStmt.setTimestamp(7, new Timestamp(new Date().getTime()), Calendar.getInstance(TimeZone.getTimeZone(UTC))); prepStmt.setString(8, recoveryDataDO.getRemainingSetIds()); + prepStmt.setString(9, recoveryDataDO.getRecoveryFlowId()); prepStmt.execute(); IdentityDatabaseUtil.commitTransaction(connection); } catch (SQLException e) { @@ -105,6 +109,127 @@ public void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryExcept } } + @Override + public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException { + + Connection connection = IdentityDatabaseUtil.getDBConnection(true); + PreparedStatement prepStmt1 = null; + PreparedStatement prepStmt2 = null; + try { + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA); + prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); + prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); + prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); + prepStmt1.setString(4, recoveryDataDO.getSecret()); + prepStmt1.setString(5, String.valueOf(recoveryDataDO.getRecoveryScenario())); + prepStmt1.setString(6, String.valueOf(recoveryDataDO.getRecoveryStep())); + prepStmt1.setTimestamp(7, new Timestamp(new Date().getTime()), + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + prepStmt1.setString(8, recoveryDataDO.getRemainingSetIds()); + prepStmt1.setString(9, recoveryDataDO.getRecoveryFlowId()); + + prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_OTP_DATA); + prepStmt2.setString(1, recoveryDataDO.getRecoveryFlowId()); + prepStmt2.setString(2, null); + prepStmt2.setInt(3, 0); + prepStmt2.setInt(4, 0); + prepStmt2.setTimestamp(5, new Timestamp(new Date().getTime()), + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + + prepStmt1.execute(); + prepStmt2.execute(); + + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw Utils.handleServerException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_STORING_RECOVERY_OTP_DATA, null, e); + } finally { + IdentityDatabaseUtil.closeStatement(prepStmt1); + IdentityDatabaseUtil.closeStatement(prepStmt2); + IdentityDatabaseUtil.closeConnection(connection); + } + } + + @Override + public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException { + + Connection connection = IdentityDatabaseUtil.getDBConnection(true); + PreparedStatement prepStmt1 = null; + PreparedStatement prepStmt2 = null; + try { + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA); + prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); + prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); + prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); + prepStmt1.setString(4, recoveryDataDO.getSecret()); + prepStmt1.setString(5, String.valueOf(recoveryDataDO.getRecoveryScenario())); + prepStmt1.setString(6, String.valueOf(recoveryDataDO.getRecoveryStep())); + prepStmt1.setTimestamp(7, new Timestamp(new Date().getTime()), + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + prepStmt1.setString(8, recoveryDataDO.getRemainingSetIds()); + prepStmt1.setString(9, recoveryDataDO.getRecoveryFlowId()); + + prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_RECOVERY_OTP_DATA); + prepStmt2.setString(1, recoveryDataDO.getSecret()); + prepStmt2.setString(2, recoveryDataDO.getRecoveryFlowId()); + + prepStmt1.execute(); + prepStmt2.execute(); + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw Utils.handleServerException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_STORING_RECOVERY_DATA, null, e); + } finally { + IdentityDatabaseUtil.closeStatement(prepStmt1); + IdentityDatabaseUtil.closeStatement(prepStmt2); + IdentityDatabaseUtil.closeConnection(connection); + } + } + + @Override + public void updateOTPAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { + + Connection connection = IdentityDatabaseUtil.getDBConnection(true); + PreparedStatement prepStmt = null; + try { + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_OTP_ATTEMPT); + prepStmt.setInt(1, attempt); + prepStmt.setString(2, recoveryFlowId); + prepStmt.execute(); + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw Utils.handleServerException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_OTP_DATA, null, e); + } finally { + IdentityDatabaseUtil.closeStatement(prepStmt); + IdentityDatabaseUtil.closeConnection(connection); + } + } + + @Override + public void updateOTPResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { + + Connection connection = IdentityDatabaseUtil.getDBConnection(true); + PreparedStatement prepStmt = null; + try { + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_OTP_RESEND_COUNT); + prepStmt.setInt(1, resendCount); + prepStmt.setString(2, recoveryFlowId); + prepStmt.execute(); + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw Utils.handleServerException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_OTP_DATA, null, e); + } finally { + IdentityDatabaseUtil.closeStatement(prepStmt); + IdentityDatabaseUtil.closeConnection(connection); + } + } + @Override public UserRecoveryData load(User user, Enum recoveryScenario, Enum recoveryStep, String code) throws IdentityRecoveryException { @@ -209,8 +334,9 @@ public UserRecoveryData load(String code, boolean skipExpiryValidation) throws I Enum recoveryStep = RecoverySteps.valueOf(resultSet.getString("STEP")); Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); + String recoveryFlowId = resultSet.getString("RECOVERY_FLOW_ID"); - userRecoveryData = new UserRecoveryData(user, code, recoveryScenario, recoveryStep, + userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, recoveryScenario, recoveryStep, timeCreated); if (StringUtils.isNotBlank(resultSet.getString("REMAINING_SETS"))) { userRecoveryData.setRemainingSetIds(resultSet.getString("REMAINING_SETS")); @@ -244,6 +370,137 @@ public UserRecoveryData load(String code, boolean skipExpiryValidation) throws I throw Utils.handleClientException(ERROR_CODE_INVALID_CODE, code); } + @Override + public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recoveryStep) + throws IdentityRecoveryException { + + handleRecoveryDataEventPublishing(PRE_GET_USER_RECOVERY_DATA, + GET_USER_RECOVERY_DATA_SCENARIO_WITH_CODE_EXPIRY_VALIDATION, null, null, null, null, + new UserRecoveryData(null, recoveryFlowId, null, null, recoveryStep)); + + PreparedStatement prepStmt1 = null; + PreparedStatement prepStmt2 = null; + ResultSet resultSet1 = null; + ResultSet resultSet2 = null; + Connection connection = IdentityDatabaseUtil.getDBConnection(false); + + User user = null; + String code = null; + int attempt = 0; + int resendCount =0; + long createdTimeStamp = 0; + UserRecoveryData userRecoveryData = null; + Boolean isOperationSuccess = false; + Enum description = ERROR_CODE_INVALID_FLOW_ID; + try { + String sql1 = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID; + prepStmt1 = connection.prepareStatement(sql1); + prepStmt1.setString(1, recoveryFlowId); + + resultSet1 = prepStmt1.executeQuery(); + + if (resultSet1.next()) { + attempt = resultSet1.getInt("ATTEMPT"); + resendCount = resultSet1.getInt("RESEND_COUNT"); + Timestamp timeCreated = resultSet1.getTimestamp("TIME_CREATED", + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + createdTimeStamp = timeCreated.getTime(); + } + + String sql2 = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_DATA_FROM_RECOVERY_FLOW_ID; + prepStmt2 = connection.prepareStatement(sql2); + prepStmt2.setString(1, recoveryFlowId); + prepStmt2.setString(2, String.valueOf(recoveryStep)); + + resultSet2 = prepStmt2.executeQuery(); + + if (resultSet2.next()) { + user = new User(); + user.setUserName(resultSet2.getString("USER_NAME")); + user.setTenantDomain(IdentityTenantUtil.getTenantDomain(resultSet2.getInt("TENANT_ID"))); + user.setUserStoreDomain(resultSet2.getString("USER_DOMAIN")); + + code = resultSet2.getString("CODE"); + Enum recoveryScenario = RecoveryScenarios.valueOf(resultSet2.getString("SCENARIO")); + String remainingSets = resultSet2.getString("REMAINING_SETS"); + Timestamp secretCreatedTime = resultSet2.getTimestamp("TIME_CREATED", + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + + userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, attempt, resendCount, + recoveryScenario, recoveryStep, remainingSets, secretCreatedTime); + long secretCreatedTimeStamp = secretCreatedTime.getTime(); + boolean isCodeExpired = isCodeExpired(user.getTenantDomain(), userRecoveryData.getRecoveryScenario(), + userRecoveryData.getRecoveryStep(), secretCreatedTimeStamp, userRecoveryData.getRemainingSetIds()); + if (isCodeExpired) { + isOperationSuccess = false; + description = ERROR_CODE_EXPIRED_CODE; + throw Utils.handleClientException(ERROR_CODE_EXPIRED_CODE, code); + } + boolean isRecoveryFlowIdExpired = isRecoveryFlowIdExpired(user.getTenantDomain(), createdTimeStamp, + userRecoveryData.getRemainingSetIds()); + if (isRecoveryFlowIdExpired) { + isOperationSuccess = false; + description = ERROR_CODE_EXPIRED_FLOW_ID; + throw Utils.handleClientException(ERROR_CODE_EXPIRED_FLOW_ID, recoveryFlowId); + } + isOperationSuccess = true; + description = null; + return userRecoveryData; + } + } catch (SQLException e) { + isOperationSuccess = false; + description = ERROR_CODE_UNEXPECTED; + throw Utils.handleServerException(ERROR_CODE_UNEXPECTED, null, e); + } finally { + handleRecoveryDataEventPublishing(POST_GET_USER_RECOVERY_DATA, + GET_USER_RECOVERY_DATA_SCENARIO_WITH_CODE_EXPIRY_VALIDATION, isOperationSuccess, description, code, user, + userRecoveryData); + IdentityDatabaseUtil.closeAllConnections(connection, resultSet1, prepStmt1); + IdentityDatabaseUtil.closeAllConnections(connection, resultSet2, prepStmt2); + } + throw Utils.handleClientException(ERROR_CODE_INVALID_FLOW_ID, recoveryFlowId); + } + + @Override + public UserRecoveryFlowData loadRecoveryFlowData(UserRecoveryData recoveryDataDO) + throws IdentityRecoveryException { + + PreparedStatement prepStmt = null; + ResultSet resultSet = null; + Connection connection = IdentityDatabaseUtil.getDBConnection(false); + + try { + String sql = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID; + prepStmt = connection.prepareStatement(sql); + prepStmt.setString(1, recoveryDataDO.getRecoveryFlowId()); + + resultSet = prepStmt.executeQuery(); + + if (resultSet.next()) { + int attempt = resultSet.getInt("ATTEMPT"); + int resendCount = resultSet.getInt("RESEND_COUNT"); + Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", + Calendar.getInstance(TimeZone.getTimeZone(UTC))); + long createdTimeStamp = timeCreated.getTime(); + + UserRecoveryFlowData userRecoveryFlowData = new UserRecoveryFlowData(recoveryDataDO.getRecoveryFlowId(), + timeCreated, attempt, resendCount); + + boolean isRecoveryFlowIdExpired = isRecoveryFlowIdExpired(recoveryDataDO.getUser().getTenantDomain(), + createdTimeStamp, recoveryDataDO.getRemainingSetIds()); + if (isRecoveryFlowIdExpired) { + throw Utils.handleClientException(ERROR_CODE_EXPIRED_FLOW_ID, recoveryDataDO.getRecoveryFlowId()); + } + return userRecoveryFlowData; + } + } catch (SQLException e) { + throw Utils.handleServerException(ERROR_CODE_UNEXPECTED, null, e); + } finally { + IdentityDatabaseUtil.closeAllConnections(connection, resultSet, prepStmt); + } + throw Utils.handleClientException(ERROR_CODE_INVALID_FLOW_ID, recoveryDataDO.getRecoveryFlowId()); + } + @Override public void invalidate(String code) throws IdentityRecoveryException { @@ -370,9 +627,10 @@ public UserRecoveryData loadWithoutCodeExpiryValidation(User user) throws Identi code = resultSet.getString("CODE"); Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); + String recoveryFlowId = resultSet.getString("RECOVERY_FLOW_ID"); userRecoveryData = - new UserRecoveryData(user, code, scenario, step, timeCreated); + new UserRecoveryData(user, recoveryFlowId, code, scenario, step, timeCreated); if (StringUtils.isNotBlank(resultSet.getString("REMAINING_SETS"))) { userRecoveryData.setRemainingSetIds(resultSet.getString("REMAINING_SETS")); } @@ -567,6 +825,36 @@ public void invalidate(User user, Enum recoveryScenario, Enum recoveryStep) thro } } + @Override + public void invalidateWithRecoveryFlowId(String recoveryFlowId) throws IdentityRecoveryException { + + PreparedStatement prepStmt1 = null; + PreparedStatement prepStmt2 = null; + Connection connection = IdentityDatabaseUtil.getDBConnection(true); + try { + String sql1 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_BY_RECOVERY_FLOW_ID; + + prepStmt1 = connection.prepareStatement(sql1); + prepStmt1.setString(1, recoveryFlowId); + prepStmt1.execute(); + + String sql2 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_OTP_DATA_BY_RECOVERY_FLOW_ID; + + prepStmt2 = connection.prepareStatement(sql2); + prepStmt2.setString(1, recoveryFlowId); + prepStmt2.execute(); + + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw Utils.handleServerException(ERROR_CODE_UNEXPECTED, null, e); + } finally { + IdentityDatabaseUtil.closeStatement(prepStmt1); + IdentityDatabaseUtil.closeStatement(prepStmt2); + IdentityDatabaseUtil.closeConnection(connection); + } + } + @Override public void invalidateWithoutChangeTimeCreated(String oldCode, String code, Enum recoveryStep, String channelList) throws IdentityRecoveryException { @@ -759,6 +1047,39 @@ private boolean isCodeExpired(String tenantDomain, Enum recoveryScenario, Enum r return System.currentTimeMillis() > expiryTime; } + /** + * Checks whether the recovery flow id has expired or not. + * + * @param tenantDomain Tenant domain + * @param createdTimestamp Time stamp + * @param recoveryData Additional data for validate the code + * @return Whether the recovery flow id has expired or not + * @throws IdentityRecoveryServerException Error while reading the configs + */ + private boolean isRecoveryFlowIdExpired(String tenantDomain, long createdTimestamp, String recoveryData) + throws IdentityRecoveryServerException { + + int codeExpiryTime; + int allowedResendAttempts; + int recoveryFlowIdExpiryTime; + if (NotificationChannels.SMS_CHANNEL.getChannelType().equals(recoveryData)) { + codeExpiryTime = Integer.parseInt(Utils.getRecoveryConfigs( + IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_EXPIRY_TIME, tenantDomain)); + allowedResendAttempts = Integer.parseInt(Utils.getRecoveryConfigs( + IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, tenantDomain)); + recoveryFlowIdExpiryTime = codeExpiryTime * allowedResendAttempts; + } else { + recoveryFlowIdExpiryTime = Integer.parseInt( + Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME, tenantDomain)); + } + if (recoveryFlowIdExpiryTime < 1) { + recoveryFlowIdExpiryTime = IdentityRecoveryConstants.RECOVERY_FLOW_ID_DEFAULT_EXPIRY_TIME; + } + + long expiryTime = createdTimestamp + TimeUnit.MINUTES.toMillis(recoveryFlowIdExpiryTime); + return System.currentTimeMillis() > expiryTime; + } + /** * Get the expiry time of the recovery code given at username recovery and password recovery init. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java index bb22243be7..e088c297fa 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java @@ -22,10 +22,19 @@ import org.wso2.carbon.identity.application.common.model.User; import org.wso2.carbon.identity.recovery.IdentityRecoveryException; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; +import org.wso2.carbon.identity.recovery.model.UserRecoveryFlowData; public interface UserRecoveryDataStore { void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; + void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; + + void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; + + void updateOTPAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException; + + void updateOTPResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException; + /* * returns UserRecoveryData if the code is validated. Otherwise returns an exception. */ @@ -53,6 +62,10 @@ default UserRecoveryData load(String code, boolean skipExpiryValidation) throws throw new NotImplementedException("This functionality is not implemented"); } + UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recoveryStep) throws IdentityRecoveryException; + + UserRecoveryFlowData loadRecoveryFlowData(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; + UserRecoveryData loadWithoutCodeExpiryValidation(User user) throws IdentityRecoveryException; @@ -71,6 +84,9 @@ void invalidate(User user) throws void invalidate(User user, Enum recoveryScenario, Enum recoveryStep) throws IdentityRecoveryException; + void invalidateWithRecoveryFlowId(String recoveryFlowId) throws + IdentityRecoveryException; + /** * Delete all recovery data by tenant id * From 6a54c6e3096713d946d07087bfce9aae9a265a0e Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 29 Aug 2023 17:53:53 +0530 Subject: [PATCH 02/27] Rename new table --- .../recovery/IdentityRecoveryConstants.java | 18 +++++++------- .../impl/UserAccountRecoveryManager.java | 4 ++-- .../NotificationPasswordRecoveryManager.java | 2 +- .../recovery/store/JDBCRecoveryDataStore.java | 24 +++++++++---------- .../recovery/store/UserRecoveryDataStore.java | 4 ++-- 5 files changed, 26 insertions(+), 26 deletions(-) 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 ae4ffdf5ba..301a69cbf0 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 @@ -291,8 +291,8 @@ public enum ErrorMessages { ERROR_CODE_DISABLE_LITE_SIGN_UP("20060", "Lite sign up feature is disabled"), ERROR_CODE_ERROR_DELETING_RECOVERY_DATA("20061", "Error deleting user recovery data of the tenant: %s"), ERROR_CODE_ERROR_GETTING_CONNECTOR_CONFIG("20062", "Error while getting connector configurations"), - ERROR_CODE_STORING_RECOVERY_OTP_DATA("20063", "Error while storing recovery data"), - ERROR_CODE_UPDATING_RECOVERY_OTP_DATA("20064", "Error while updating recovery data"), + ERROR_CODE_STORING_RECOVERY_FLOW_DATA("20063", "Error while storing recovery data"), + ERROR_CODE_UPDATING_RECOVERY_FLOW_DATA("20064", "Error while updating recovery data"), ERROR_CODE_ERROR_RETRIVING_CLAIM("18004", "Error when retrieving the locale claim of user '%s' of '%s' domain."), ERROR_CODE_RECOVERY_DATA_NOT_FOUND_FOR_USER("18005", "Recovery data not found."), @@ -686,22 +686,22 @@ public static class SQLQueries { + "* FROM IDN_RECOVERY_DATA WHERE LOWER(USER_NAME)=LOWER(?) AND SCENARIO = ? AND USER_DOMAIN = ? " + "AND TENANT_ID = ? AND STEP = ?"; - public static final String STORE_RECOVERY_OTP_DATA = "INSERT INTO IDN_RECOVERY_OTP_DATA " - + "(RECOVERY_FLOW_ID, OTP, ATTEMPT, RESEND_COUNT, TIME_CREATED) VALUES (?,?,?,?,?)"; + public static final String STORE_RECOVERY_FLOW_DATA = "INSERT INTO IDN_RECOVERY_FLOW_DATA " + + "(RECOVERY_FLOW_ID, CODE, ATTEMPT, RESEND_COUNT, TIME_CREATED) VALUES (?,?,?,?,?)"; - public static final String UPDATE_RECOVERY_OTP_DATA = "UPDATE IDN_RECOVERY_OTP_DATA SET OTP = ? " + public static final String UPDATE_RECOVERY_FLOW_DATA = "UPDATE IDN_RECOVERY_FLOW_DATA SET CODE = ? " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String UPDATE_OTP_ATTEMPT = "UPDATE IDN_RECOVERY_OTP_DATA SET ATTEMPT = ? " + public static final String UPDATE_ATTEMPT = "UPDATE IDN_RECOVERY_FLOW_DATA SET ATTEMPT = ? " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String UPDATE_OTP_RESEND_COUNT = "UPDATE IDN_RECOVERY_OTP_DATA SET RESEND_COUNT = ? " + public static final String UPDATE_CODE_RESEND_COUNT = "UPDATE IDN_RECOVERY_FLOW_DATA SET RESEND_COUNT = ? " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * FROM IDN_RECOVERY_OTP_DATA " + public static final String LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * FROM IDN_RECOVERY_FLOW_DATA " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String INVALIDATE_OTP_DATA_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_OTP_DATA WHERE " + + public static final String INVALIDATE_FLOW_DATA_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_FLOW_DATA WHERE " + "RECOVERY_FLOW_ID = ?"; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index af8a031a1a..50b3c9e707 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -1010,7 +1010,7 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa public void updateRecoveryDataAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - userRecoveryDataStore.updateOTPAttempt(recoveryFlowId, attempt); + userRecoveryDataStore.updateAttempt(recoveryFlowId, attempt); } /** @@ -1022,7 +1022,7 @@ public void updateRecoveryDataAttempt(String recoveryFlowId, int attempt) throws public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - userRecoveryDataStore.updateOTPResendCount(recoveryFlowId, resendCount); + userRecoveryDataStore.updateCodeResendCount(recoveryFlowId, resendCount); } /** 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 cdd5d5c0e6..f377e336dc 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 @@ -667,7 +667,7 @@ public User updateUserPassword(String code, String confirmationCode, String pass IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), confirmationCode); } - userRecoveryDataStore.updateOTPAttempt(confirmationCode, attempt + 1); + userRecoveryDataStore.updateAttempt(confirmationCode, attempt + 1); throw Utils.handleClientException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode(), IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 4ffd2a8154..ea228882a3 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -128,7 +128,7 @@ public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryEx prepStmt1.setString(8, recoveryDataDO.getRemainingSetIds()); prepStmt1.setString(9, recoveryDataDO.getRecoveryFlowId()); - prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_OTP_DATA); + prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_FLOW_DATA); prepStmt2.setString(1, recoveryDataDO.getRecoveryFlowId()); prepStmt2.setString(2, null); prepStmt2.setInt(3, 0); @@ -143,7 +143,7 @@ public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryEx } catch (SQLException e) { IdentityDatabaseUtil.rollbackTransaction(connection); throw Utils.handleServerException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_STORING_RECOVERY_OTP_DATA, null, e); + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_STORING_RECOVERY_FLOW_DATA, null, e); } finally { IdentityDatabaseUtil.closeStatement(prepStmt1); IdentityDatabaseUtil.closeStatement(prepStmt2); @@ -170,7 +170,7 @@ public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws Identi prepStmt1.setString(8, recoveryDataDO.getRemainingSetIds()); prepStmt1.setString(9, recoveryDataDO.getRecoveryFlowId()); - prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_RECOVERY_OTP_DATA); + prepStmt2 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_RECOVERY_FLOW_DATA); prepStmt2.setString(1, recoveryDataDO.getSecret()); prepStmt2.setString(2, recoveryDataDO.getRecoveryFlowId()); @@ -189,12 +189,12 @@ public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws Identi } @Override - public void updateOTPAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { + public void updateAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_OTP_ATTEMPT); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_ATTEMPT); prepStmt.setInt(1, attempt); prepStmt.setString(2, recoveryFlowId); prepStmt.execute(); @@ -202,7 +202,7 @@ public void updateOTPAttempt(String recoveryFlowId, int attempt) throws Identity } catch (SQLException e) { IdentityDatabaseUtil.rollbackTransaction(connection); throw Utils.handleServerException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_OTP_DATA, null, e); + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_FLOW_DATA, null, e); } finally { IdentityDatabaseUtil.closeStatement(prepStmt); IdentityDatabaseUtil.closeConnection(connection); @@ -210,12 +210,12 @@ public void updateOTPAttempt(String recoveryFlowId, int attempt) throws Identity } @Override - public void updateOTPResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { + public void updateCodeResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_OTP_RESEND_COUNT); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_CODE_RESEND_COUNT); prepStmt.setInt(1, resendCount); prepStmt.setString(2, recoveryFlowId); prepStmt.execute(); @@ -223,7 +223,7 @@ public void updateOTPResendCount(String recoveryFlowId, int resendCount) throws } catch (SQLException e) { IdentityDatabaseUtil.rollbackTransaction(connection); throw Utils.handleServerException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_OTP_DATA, null, e); + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_UPDATING_RECOVERY_FLOW_DATA, null, e); } finally { IdentityDatabaseUtil.closeStatement(prepStmt); IdentityDatabaseUtil.closeConnection(connection); @@ -393,7 +393,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov Boolean isOperationSuccess = false; Enum description = ERROR_CODE_INVALID_FLOW_ID; try { - String sql1 = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID; + String sql1 = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID; prepStmt1 = connection.prepareStatement(sql1); prepStmt1.setString(1, recoveryFlowId); @@ -470,7 +470,7 @@ public UserRecoveryFlowData loadRecoveryFlowData(UserRecoveryData recoveryDataDO Connection connection = IdentityDatabaseUtil.getDBConnection(false); try { - String sql = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_OTP_DATA_FROM_RECOVERY_FLOW_ID; + String sql = IdentityRecoveryConstants.SQLQueries.LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID; prepStmt = connection.prepareStatement(sql); prepStmt.setString(1, recoveryDataDO.getRecoveryFlowId()); @@ -838,7 +838,7 @@ public void invalidateWithRecoveryFlowId(String recoveryFlowId) throws IdentityR prepStmt1.setString(1, recoveryFlowId); prepStmt1.execute(); - String sql2 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_OTP_DATA_BY_RECOVERY_FLOW_ID; + String sql2 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_FLOW_DATA_BY_RECOVERY_FLOW_ID; prepStmt2 = connection.prepareStatement(sql2); prepStmt2.setString(1, recoveryFlowId); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java index e088c297fa..aaffa5c10f 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java @@ -31,9 +31,9 @@ public interface UserRecoveryDataStore { void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; - void updateOTPAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException; + void updateAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException; - void updateOTPResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException; + void updateCodeResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException; /* * returns UserRecoveryData if the code is validated. Otherwise returns an exception. From 4eb29738680328124a20f5a18195a0f60239424e Mon Sep 17 00:00:00 2001 From: Rashmini Date: Thu, 31 Aug 2023 15:25:51 +0530 Subject: [PATCH 03/27] Update unit tests --- .../recovery/connector/RecoveryConfigImplTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java index 6782f5b845..007fa278cd 100644 --- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java +++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java @@ -122,6 +122,10 @@ public void testGetPropertyNameMapping() { "Recovery callback URL regex"); nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET, "Enable Auto Login After Password Reset"); + nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, + "Max failed attempts for OTP based recovery"); + nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, + "Max resend attempts for OTP based recovery"); Map nameMapping = recoveryConfigImpl.getPropertyNameMapping(); @@ -216,6 +220,8 @@ public void testGetDefaultPropertyValues() throws IdentityGovernanceException { String challengeQuestionAnswerRegex = IdentityRecoveryConstants.DEFAULT_REGEX; String enforceChallengeQuestionAnswerUniqueness = "false"; String enableAutoLoginAfterPasswordReset = "false"; + String recoveryOTPMaxFailedAttempts = "3"; + String recoveryOTPMaxResendAttempts = "5"; Map defaultPropertiesExpected = new HashMap<>(); defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_BASED_PW_RECOVERY, @@ -258,6 +264,10 @@ public void testGetDefaultPropertyValues() throws IdentityGovernanceException { ENFORCE_CHALLENGE_QUESTION_ANSWER_UNIQUENESS, enforceChallengeQuestionAnswerUniqueness); defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig. ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET, enableAutoLoginAfterPasswordReset); + defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, recoveryOTPMaxFailedAttempts); + defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS, recoveryOTPMaxResendAttempts); String tenantDomain = "admin"; // Here tenantDomain parameter is not used by method itself From 2cc465d0641dcf34e149842ef253d9157f6bf1d8 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Thu, 31 Aug 2023 15:49:45 +0530 Subject: [PATCH 04/27] Fix comments --- .../recovery/IdentityRecoveryConstants.java | 13 ++-- .../recovery/model/UserRecoveryFlowData.java | 71 +++++++++++++------ .../recovery/store/JDBCRecoveryDataStore.java | 6 +- 3 files changed, 62 insertions(+), 28 deletions(-) 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 301a69cbf0..7e891d5b52 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 @@ -291,8 +291,8 @@ public enum ErrorMessages { ERROR_CODE_DISABLE_LITE_SIGN_UP("20060", "Lite sign up feature is disabled"), ERROR_CODE_ERROR_DELETING_RECOVERY_DATA("20061", "Error deleting user recovery data of the tenant: %s"), ERROR_CODE_ERROR_GETTING_CONNECTOR_CONFIG("20062", "Error while getting connector configurations"), - ERROR_CODE_STORING_RECOVERY_FLOW_DATA("20063", "Error while storing recovery data"), - ERROR_CODE_UPDATING_RECOVERY_FLOW_DATA("20064", "Error while updating recovery data"), + ERROR_CODE_STORING_RECOVERY_FLOW_DATA("20063", "Error while storing recovery data."), + ERROR_CODE_UPDATING_RECOVERY_FLOW_DATA("20064", "Error while updating recovery data."), ERROR_CODE_ERROR_RETRIVING_CLAIM("18004", "Error when retrieving the locale claim of user '%s' of '%s' domain."), ERROR_CODE_RECOVERY_DATA_NOT_FOUND_FOR_USER("18005", "Recovery data not found."), @@ -613,17 +613,22 @@ public static class ConnectorConfig { public static final String ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET = "Recovery.AutoLogin.Enable"; public static final String SELF_REGISTRATION_AUTO_LOGIN = "SelfRegistration.AutoLogin.Enable"; public static final String SELF_REGISTRATION_AUTO_LOGIN_ALIAS_NAME = "SelfRegistration.AutoLogin.AliasName"; - public static final String RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS = "Recovery.Otp" + + public static final String RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS = "Recovery.OTP" + ".Password.MaxFailedAttempts"; - public static final String RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS = "Recovery.Otp" + + public static final String RECOVERY_OTP_PASSWORD_MAX_RESEND_ATTEMPTS = "Recovery.OTP" + ".Password.MaxResendAttempts"; } public static class SQLQueries { public static final String STORE_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA " + + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS)" + + "VALUES (?,?,?,?,?,?,?,?)"; + + public static final String STORE_ACCOUNT_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA " + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS, " + "RECOVERY_FLOW_ID) VALUES (?,?,?,?,?,?,?,?,?)"; + public static final String LOAD_RECOVERY_DATA = "SELECT " + "* FROM IDN_RECOVERY_DATA WHERE USER_NAME = ? AND USER_DOMAIN = ? AND TENANT_ID = ? AND CODE = ? AND " + "SCENARIO = ? AND STEP = ?"; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java index 04106972b5..f5d4b8fc81 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java @@ -24,71 +24,100 @@ * This object represents an entry of the identity metadata database. */ public class UserRecoveryFlowData { - private String recoveryId; + + private String recoveryFlowId; private Timestamp timeCreated; - private int attempts; + private int attempt; private int resendCount; - public UserRecoveryFlowData(String recoveryId) { + public UserRecoveryFlowData(String recoveryFlowId) { - this.recoveryId = recoveryId; + this.recoveryFlowId = recoveryFlowId; } - public UserRecoveryFlowData(String recoveryId, Timestamp timeCreated) { + public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated) { - this.recoveryId = recoveryId; + this.recoveryFlowId = recoveryFlowId; this.timeCreated = timeCreated; } - public UserRecoveryFlowData(String recoveryId, Timestamp timeCreated, int attempts, int resendCount) { + public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated, int attempt, int resendCount) { - this.recoveryId = recoveryId; + this.recoveryFlowId = recoveryFlowId; this.timeCreated = timeCreated; - this.attempts = attempts; + this.attempt = attempt; this.resendCount = resendCount; } + /** + * Get the time created. + * + * @return Created time. + */ public Timestamp getTimeCreated() { return timeCreated; } + /** + * Set the time created. + * + * @param timeCreated Created time. + */ public void setTimeCreated(Timestamp timeCreated) { this.timeCreated = timeCreated; } - public String getRecoveryId() { - return recoveryId; + /** + * Get the recovery flow id. + * + * @return Recovery Flow Id. + */ + public String getRecoveryFlowId() { + + return recoveryFlowId; } /** - * Set the recovery id. + * Set the recovery flow id. * - * @param recoveryId Recovery Id. + * @param recoveryFlowId Recovery Flow Id. */ - public void setRecoveryId(String recoveryId) { + public void setRecoveryFlowId(String recoveryFlowId) { - this.recoveryId = recoveryId; + this.recoveryFlowId = recoveryFlowId; } - public int getAttempts() { - return attempts; + /** + * Get the attempt. + * + * @return attempt. + */ + public int getAttempt() { + + return attempt; } /** - * Set the attempts. + * Set the attempt. * - * @param attempts OTP attempts. + * @param attempt OTP attempt. */ - public void setAttempts(int attempts) { + public void setAttempt(int attempt) { - this.attempts = attempts; + this.attempt = attempt; } + /** + * Get the resendCount. + * + * @return resendCount. + */ public int getResendCount() { + return resendCount; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index ea228882a3..865a4e5d01 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -86,7 +86,7 @@ public void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryExcept Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); prepStmt.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); @@ -116,7 +116,7 @@ public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryEx PreparedStatement prepStmt1 = null; PreparedStatement prepStmt2 = null; try { - prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA); + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); @@ -158,7 +158,7 @@ public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws Identi PreparedStatement prepStmt1 = null; PreparedStatement prepStmt2 = null; try { - prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA); + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); From 9aa50d3ab854594b151e0fa12acea84bdfcf0799 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 1 Sep 2023 14:51:46 +0530 Subject: [PATCH 05/27] Address review comments --- .../impl/password/PasswordRecoveryManagerImpl.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 66e9be765e..812de57b90 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -220,17 +220,20 @@ public PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String tenantDomain, Map properties) throws IdentityRecoveryException { - validateTenantDomain(tenantDomain); + validateTenantDomain(tenantDomain); UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); + // In the recovery scenarios which are not OTP based, the confirmation code is a combination of the + // recovery flow id and another UUID generated. Hence, we need to get the recovery flow id from the + // confirmation code. In the OTP based recovery scenario, the confirmation code is the recovery flow id. String[] ids = confirmationCode.split("\\."); String recoveryFlowId; String code; - if (ids.length != 2) { - recoveryFlowId = confirmationCode; - code = otp; - } else { + if (ids.length == 2) { recoveryFlowId = ids[0]; code = confirmationCode; + } else { + recoveryFlowId = confirmationCode; + code = otp; } // Get Recovery data. UserRecoveryData userRecoveryData = userAccountRecoveryManager From f95388297f777df26adbecccb14f7c02ccfe19f0 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Mon, 4 Sep 2023 16:22:37 +0530 Subject: [PATCH 06/27] Clear tables with recovery flow id --- .../password/NotificationPasswordRecoveryManager.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 f377e336dc..c9abf2498f 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 @@ -576,6 +576,8 @@ public User updateUserPassword(String code, String password, Property[] properti publishEvent(userRecoveryData.getUser(), null, code, password, properties, IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); validateTenantDomain(userRecoveryData.getUser()); + String recoveryFlowId = userRecoveryDataStore.loadWithoutCodeExpiryValidation(userRecoveryData.getUser()) + .getRecoveryFlowId(); // Validate recovery step. if (!RecoverySteps.UPDATE_PASSWORD.equals(userRecoveryData.getRecoveryStep())) { @@ -596,7 +598,11 @@ public User updateUserPassword(String code, String password, Property[] properti // Update the password. updateNewPassword(userRecoveryData.getUser(), password, domainQualifiedName, userRecoveryData, notificationsInternallyManaged); - userRecoveryDataStore.invalidate(userRecoveryData.getUser()); + if (recoveryFlowId != null) { + userRecoveryDataStore.invalidateWithRecoveryFlowId(recoveryFlowId); + } else { + userRecoveryDataStore.invalidate(userRecoveryData.getUser()); + } if (notificationsInternallyManaged && !NotificationChannels.EXTERNAL_CHANNEL.getChannelType().equals(notificationChannel)) { String emailTemplate = null; From ddf827bfbb4ab195357b120a62134c40997b0a83 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Thu, 7 Sep 2023 10:27:40 +0530 Subject: [PATCH 07/27] Update DB column name --- .../recovery/IdentityRecoveryConstants.java | 4 ++-- .../impl/UserAccountRecoveryManager.java | 4 ++-- .../password/PasswordRecoveryManagerImpl.java | 6 ++--- .../recovery/model/UserRecoveryData.java | 14 ++++++------ .../recovery/model/UserRecoveryFlowData.java | 22 +++++++++---------- .../NotificationPasswordRecoveryManager.java | 6 ++--- .../recovery/store/JDBCRecoveryDataStore.java | 16 +++++++------- .../recovery/store/UserRecoveryDataStore.java | 2 +- 8 files changed, 37 insertions(+), 37 deletions(-) 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 7e891d5b52..21d46aae99 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 @@ -692,12 +692,12 @@ public static class SQLQueries { "AND TENANT_ID = ? AND STEP = ?"; public static final String STORE_RECOVERY_FLOW_DATA = "INSERT INTO IDN_RECOVERY_FLOW_DATA " - + "(RECOVERY_FLOW_ID, CODE, ATTEMPT, RESEND_COUNT, TIME_CREATED) VALUES (?,?,?,?,?)"; + + "(RECOVERY_FLOW_ID, CODE, FAILED_ATTEMPTS, RESEND_COUNT, TIME_CREATED) VALUES (?,?,?,?,?)"; public static final String UPDATE_RECOVERY_FLOW_DATA = "UPDATE IDN_RECOVERY_FLOW_DATA SET CODE = ? " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String UPDATE_ATTEMPT = "UPDATE IDN_RECOVERY_FLOW_DATA SET ATTEMPT = ? " + public static final String UPDATE_FAILED_ATTEMPTS = "UPDATE IDN_RECOVERY_FLOW_DATA SET FAILED_ATTEMPTS = ? " + "WHERE RECOVERY_FLOW_ID = ?"; public static final String UPDATE_CODE_RESEND_COUNT = "UPDATE IDN_RECOVERY_FLOW_DATA SET RESEND_COUNT = ? " diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index 50b3c9e707..e9826b6498 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -1007,10 +1007,10 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa * @param recoveryFlowId Recovery Flow Id * @param attempt Current Attempt */ - public void updateRecoveryDataAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { + public void updateRecoveryDataAttempt(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - userRecoveryDataStore.updateAttempt(recoveryFlowId, attempt); + userRecoveryDataStore.updateFailedAttempts(recoveryFlowId, failedAttempts); } /** diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 812de57b90..41e9524228 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -238,7 +238,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String // Get Recovery data. UserRecoveryData userRecoveryData = userAccountRecoveryManager .getUserRecoveryDataFromFlowId(recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); - int attempt = userRecoveryData.getAttempt(); + int failedAttempts = userRecoveryData.getFailedAttempts(); if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { throw Utils.handleClientException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, @@ -252,7 +252,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String } return buildPasswordResetCodeDTO(code); } else { - if ((attempt + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); throw Utils.handleClientException( @@ -260,7 +260,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), recoveryFlowId); } - userAccountRecoveryManager.updateRecoveryDataAttempt(recoveryFlowId, attempt + 1); + userAccountRecoveryManager.updateRecoveryDataAttempt(recoveryFlowId, failedAttempts + 1); if (log.isDebugEnabled()) { log.debug("Invalid confirmation code for user: " + domainQualifiedName); } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java index 2fb875c4d0..cd91decebc 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java @@ -30,7 +30,7 @@ public class UserRecoveryData { private User user; private String recoveryFlowId; private String secret; - private int attempt; + private int failedAttempts; private int resendCount; private String remainingSetIds; private boolean codeExpired; @@ -94,13 +94,13 @@ public UserRecoveryData(User user, String recoveryFlowId, String secret, Enum re this.timeCreated = timeCreated; } - public UserRecoveryData(User user, String recoveryFlowId, String secret, int attempt, int resendCount, + public UserRecoveryData(User user, String recoveryFlowId, String secret, int failedAttempts, int resendCount, Enum recoveryScenario, Enum recoveryStep, String remainingSetIds, Timestamp timeCreated) { this.user = user; this.recoveryFlowId=recoveryFlowId; this.secret = secret; - this.attempt = attempt; + this.failedAttempts = failedAttempts; this.resendCount = resendCount; this.recoveryScenario = recoveryScenario; this.recoveryStep = recoveryStep; @@ -145,13 +145,13 @@ public User getUser() { return user; } - public int getAttempt() { - return attempt; + public int getFailedAttempts() { + return failedAttempts; } - public void setAttempt(int attempt) { + public void setFailedAttempts(int failedAttempts) { - this.attempt = attempt; + this.failedAttempts = failedAttempts; } public int getResendCount() { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java index f5d4b8fc81..475f27f206 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java @@ -29,7 +29,7 @@ public class UserRecoveryFlowData { private Timestamp timeCreated; - private int attempt; + private int failedAttempts; private int resendCount; @@ -43,11 +43,11 @@ public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated) { this.timeCreated = timeCreated; } - public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated, int attempt, int resendCount) { + public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated, int failedAttempts, int resendCount) { this.recoveryFlowId = recoveryFlowId; this.timeCreated = timeCreated; - this.attempt = attempt; + this.failedAttempts = failedAttempts; this.resendCount = resendCount; } @@ -92,23 +92,23 @@ public void setRecoveryFlowId(String recoveryFlowId) { } /** - * Get the attempt. + * Get the failed attempts. * - * @return attempt. + * @return failed attempts. */ - public int getAttempt() { + public int getFailedAttempts() { - return attempt; + return failedAttempts; } /** - * Set the attempt. + * Set the failed attempts. * - * @param attempt OTP attempt. + * @param failedAttempts OTP failed attempts. */ - public void setAttempt(int attempt) { + public void setFailedAttempts(int failedAttempts) { - this.attempt = attempt; + this.failedAttempts = failedAttempts; } /** 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 c9abf2498f..1476d0c4e1 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 @@ -662,10 +662,10 @@ public User updateUserPassword(String code, String confirmationCode, String pass publishEvent(userRecoveryData.getUser(), null, null, password, properties, IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); validateTenantDomain(userRecoveryData.getUser()); - int attempt = userRecoveryData.getAttempt(); + int failedAttempts = userRecoveryData.getFailedAttempts(); if (!StringUtils.equals(code, userRecoveryData.getSecret())) { - if ((attempt + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser().getTenantDomain()))) { userRecoveryDataStore.invalidateWithRecoveryFlowId(confirmationCode); throw Utils.handleClientException( @@ -673,7 +673,7 @@ public User updateUserPassword(String code, String confirmationCode, String pass IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), confirmationCode); } - userRecoveryDataStore.updateAttempt(confirmationCode, attempt + 1); + userRecoveryDataStore.updateFailedAttempts(confirmationCode, failedAttempts + 1); throw Utils.handleClientException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode(), IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 865a4e5d01..11d2504fa0 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -189,13 +189,13 @@ public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws Identi } @Override - public void updateAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException { + public void updateFailedAttempts(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_ATTEMPT); - prepStmt.setInt(1, attempt); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.UPDATE_FAILED_ATTEMPTS); + prepStmt.setInt(1, failedAttempts); prepStmt.setString(2, recoveryFlowId); prepStmt.execute(); IdentityDatabaseUtil.commitTransaction(connection); @@ -386,7 +386,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov User user = null; String code = null; - int attempt = 0; + int failedAttempts = 0; int resendCount =0; long createdTimeStamp = 0; UserRecoveryData userRecoveryData = null; @@ -400,7 +400,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov resultSet1 = prepStmt1.executeQuery(); if (resultSet1.next()) { - attempt = resultSet1.getInt("ATTEMPT"); + failedAttempts = resultSet1.getInt("FAILED_ATTEMPTS"); resendCount = resultSet1.getInt("RESEND_COUNT"); Timestamp timeCreated = resultSet1.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); @@ -426,7 +426,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov Timestamp secretCreatedTime = resultSet2.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); - userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, attempt, resendCount, + userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, failedAttempts, resendCount, recoveryScenario, recoveryStep, remainingSets, secretCreatedTime); long secretCreatedTimeStamp = secretCreatedTime.getTime(); boolean isCodeExpired = isCodeExpired(user.getTenantDomain(), userRecoveryData.getRecoveryScenario(), @@ -477,14 +477,14 @@ public UserRecoveryFlowData loadRecoveryFlowData(UserRecoveryData recoveryDataDO resultSet = prepStmt.executeQuery(); if (resultSet.next()) { - int attempt = resultSet.getInt("ATTEMPT"); + int failedAttempts = resultSet.getInt("FAILED_ATTEMPTS"); int resendCount = resultSet.getInt("RESEND_COUNT"); Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); long createdTimeStamp = timeCreated.getTime(); UserRecoveryFlowData userRecoveryFlowData = new UserRecoveryFlowData(recoveryDataDO.getRecoveryFlowId(), - timeCreated, attempt, resendCount); + timeCreated, failedAttempts, resendCount); boolean isRecoveryFlowIdExpired = isRecoveryFlowIdExpired(recoveryDataDO.getUser().getTenantDomain(), createdTimeStamp, recoveryDataDO.getRemainingSetIds()); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java index aaffa5c10f..0f1b0361f7 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/UserRecoveryDataStore.java @@ -31,7 +31,7 @@ public interface UserRecoveryDataStore { void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws IdentityRecoveryException; - void updateAttempt(String recoveryFlowId, int attempt) throws IdentityRecoveryException; + void updateFailedAttempts(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException; void updateCodeResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException; From 1f7ac6671dc03afe2dfe51196c89cfa8ff15b22f Mon Sep 17 00:00:00 2001 From: Rashmini Date: Thu, 7 Sep 2023 10:33:30 +0530 Subject: [PATCH 08/27] Update method name to match with the new column name --- .../internal/service/impl/UserAccountRecoveryManager.java | 4 ++-- .../service/impl/password/PasswordRecoveryManagerImpl.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index e9826b6498..c2ddaead56 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -1005,9 +1005,9 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa * Update recovery OTP attempt. * * @param recoveryFlowId Recovery Flow Id - * @param attempt Current Attempt + * @param failedAttempts Failed Attempts */ - public void updateRecoveryDataAttempt(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { + public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); userRecoveryDataStore.updateFailedAttempts(recoveryFlowId, failedAttempts); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 41e9524228..7323faeadc 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -260,7 +260,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), recoveryFlowId); } - userAccountRecoveryManager.updateRecoveryDataAttempt(recoveryFlowId, failedAttempts + 1); + userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts + 1); if (log.isDebugEnabled()) { log.debug("Invalid confirmation code for user: " + domainQualifiedName); } From ab489ffd47d3bb04acd840bdff99979c579d20aa Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 08:57:01 +0530 Subject: [PATCH 09/27] Fix comments --- .../recovery/IdentityRecoveryConstants.java | 8 +-- .../ResendConfirmationManager.java | 1 + .../recovery/dto/PasswordRecoverDTO.java | 4 +- .../recovery/dto/RecoveryChannelInfoDTO.java | 4 +- .../recovery/dto/RecoveryInformationDTO.java | 4 +- .../recovery/dto/ResendConfirmationDTO.java | 4 +- .../impl/UserAccountRecoveryManager.java | 21 ++++---- .../password/PasswordRecoveryManagerImpl.java | 53 ++++++++++--------- .../username/UsernameRecoveryManagerImpl.java | 2 +- .../recovery/model/UserRecoveryData.java | 2 + .../recovery/model/UserRecoveryFlowData.java | 3 +- .../NotificationPasswordRecoveryManager.java | 1 + .../password/PasswordRecoveryManager.java | 24 ++++----- .../recovery/store/JDBCRecoveryDataStore.java | 18 +++---- 14 files changed, 78 insertions(+), 71 deletions(-) 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 21d46aae99..98b7132c24 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 @@ -361,10 +361,10 @@ public enum ErrorMessages { ERROR_CODE_EXPIRED_RECOVERY_CODE("UAR-10013", "Invalid recovery code: '%s'"), ERROR_CODE_USER_ACCOUNT_RECOVERY_VALIDATION_FAILED("UAR-10014", "User account recovery validation failed for user account: '%s'"), - ERROR_CODE_INVALID_RECOVERY_FLOW_ID("UAR-10015", "Invalid confirmation code : '%s'"), - ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'"), + ERROR_CODE_INVALID_RECOVERY_FLOW_ID("UAR-10015", "Invalid confirmation code : '%s'."), + ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'."), ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " - + "recovery flow id : '%s'"), + + "recovery flow id : '%s'."), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), ERROR_CODE_ERROR_GETTING_USERSTORE_MANAGER("UAR-15002", "Error getting userstore manager"), ERROR_CODE_ERROR_RETRIEVING_USER_CLAIM("UAR-15003", "Error getting the claims: '%s' " @@ -625,7 +625,7 @@ public static class SQLQueries { + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS)" + "VALUES (?,?,?,?,?,?,?,?)"; - public static final String STORE_ACCOUNT_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA " + public static final String STORE_RECOVERY_DATA_WITH_FLOW_ID = "INSERT INTO IDN_RECOVERY_DATA " + "(USER_NAME, USER_DOMAIN, TENANT_ID, CODE, SCENARIO,STEP, TIME_CREATED, REMAINING_SETS, " + "RECOVERY_FLOW_ID) VALUES (?,?,?,?,?,?,?,?,?)"; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index c16664dd90..e4d78f752b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -351,6 +351,7 @@ private void invalidateResendConfirmationCode(String resendCode, String notifica * Add the notification channel recovery data to the store. * * @param secretKey RecoveryId + * @param recoveryFlowId Recovery flow ID * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object * @param recoveryScenario Recovery scenario * @param recoveryStep Recovery step diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java index 4ed8fbab2c..5c713f8b09 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/PasswordRecoverDTO.java @@ -116,7 +116,7 @@ public void setMessage(String message) { /** * Get the recovery flow id. * - * @return Recovery flow id + * @return Recovery flow id. */ public String getRecoveryFlowId() { @@ -126,7 +126,7 @@ public String getRecoveryFlowId() { /** * Set the recovery flow id. * - * @param recoveryFlowId Recovery flow id + * @param recoveryFlowId Recovery flow id. */ public void setRecoveryFlowId(String recoveryFlowId) { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java index 99f50fd11e..b04c0f19ac 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryChannelInfoDTO.java @@ -65,7 +65,7 @@ public void setUsername(String username) { /** * Get the recovery Flow id of the recovery flow. * - * @return Recovery Flow id of the recovery flow + * @return Recovery Flow id of the recovery flow. */ public String getRecoveryFlowId() { @@ -75,7 +75,7 @@ public String getRecoveryFlowId() { /** * Set the recovery Flow id of the recovery flow. * - * @param recoveryFlowId Recovery Flow Id + * @param recoveryFlowId Recovery Flow Id. */ public void setRecoveryFlowId(String recoveryFlowId) { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java index 937ba587c9..a237420e5b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/RecoveryInformationDTO.java @@ -140,7 +140,7 @@ public void setUsername(String userName) { /** * Get recovery flow id. * - * @return recoveryFlowId + * @return recoveryFlowId. */ public String getRecoveryFlowId() { @@ -150,7 +150,7 @@ public String getRecoveryFlowId() { /** * Set recovery flow id. * - * @param recoveryFlowId RecoveryFlowId + * @param recoveryFlowId RecoveryFlowId. */ public void setRecoveryFlowId(String recoveryFlowId) { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java index 77cf2ae5bf..91ed8f1b29 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/dto/ResendConfirmationDTO.java @@ -55,7 +55,7 @@ public class ResendConfirmationDTO { /** * Get recovery flow id. * - * @return Recovery flow id + * @return Recovery flow id. */ public String getRecoveryFlowId() { @@ -65,7 +65,7 @@ public String getRecoveryFlowId() { /** * Set recovery flow id. * - * @param recoveryFlowId Recovery Flow Id + * @param recoveryFlowId Recovery Flow Id. */ public void setRecoveryFlowId(String recoveryFlowId) { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index c2ddaead56..af2d848705 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -430,9 +430,10 @@ private List getInternalNotificationChannelList(String user * Prepare the response to be sent to the recovery APIs. * * @param username Username of the user + * @param recoveryFlowId Recovery flow ID. * @param recoveryCode Recovery code given to the user * @param notificationChannelDTOs List of NotificationChannelsResponseDTOs available for the user. - * @return RecoveryChannelInfoDTO object. + * @return RecoveryChannelInfoD TO object. */ private RecoveryChannelInfoDTO buildUserRecoveryInformationResponseDTO(String username, String recoveryFlowId, String recoveryCode, NotificationChannelDTO[] notificationChannelDTOs) { @@ -934,7 +935,7 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr * Get user recovery data using the recovery flow id. * * @param recoveryFlowId Recovery flow id of the user. - * @param step Recovery step + * @param step Recovery step. * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. */ public UserRecoveryData getUserRecoveryDataFromFlowId(String recoveryFlowId, RecoverySteps step) @@ -994,9 +995,8 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa throw e; } if (userRecoveryFlowData == null) { - throw Utils - .handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_RECOVERY_FLOW_DATA, - recoveryDataDO.getRecoveryFlowId()); + throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_RECOVERY_FLOW_DATA, + recoveryDataDO.getRecoveryFlowId()); } return userRecoveryFlowData; } @@ -1004,8 +1004,8 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa /** * Update recovery OTP attempt. * - * @param recoveryFlowId Recovery Flow Id - * @param failedAttempts Failed Attempts + * @param recoveryFlowId Recovery Flow Id. + * @param failedAttempts Failed Attempts. */ public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { @@ -1016,8 +1016,8 @@ public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAt /** * Update recovery OTP resend count. * - * @param recoveryFlowId Recovery Flow Id - * @param resendCount Current Resend Count + * @param recoveryFlowId Recovery Flow Id. + * @param resendCount Current Resend Count. */ public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { @@ -1028,7 +1028,7 @@ public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount /** * Invalidate the recovery Data. * - * @param recoveryFlowId Recovery Flow Id + * @param recoveryFlowId Recovery Flow Id. */ public void invalidateRecoveryData(String recoveryFlowId) throws IdentityRecoveryException { @@ -1041,6 +1041,7 @@ public void invalidateRecoveryData(String recoveryFlowId) throws IdentityRecover * * @param username Username * @param tenantDomain Tenant domain + * @param recoveryFlowId Recovery flow ID. * @param secretKey RecoveryId * @param scenario RecoveryScenario * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 7323faeadc..382b8e0e08 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -209,12 +209,12 @@ public PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain /** * Validate the code given for password recovery and return the password reset code. * - * @param otp One Time Password - * @param confirmationCode Confirmation code - * @param tenantDomain Tenant domain - * @param properties Meta properties in the confirmation request - * @return PasswordResetCodeDTO {@link PasswordResetCodeDTO} object which contains password reset code - * @throws IdentityRecoveryException Error while confirming password recovery + * @param otp One Time Password. + * @param confirmationCode Confirmation code. + * @param tenantDomain Tenant domain. + * @param properties Meta properties in the confirmation request. + * @return PasswordResetCodeDTO {@link PasswordResetCodeDTO} object which contains password reset code. + * @throws IdentityRecoveryException Error while confirming password recovery. */ @Override public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String tenantDomain, @@ -251,23 +251,22 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String log.debug("Valid confirmation code for user: " + domainQualifiedName); } return buildPasswordResetCodeDTO(code); - } else { - if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. - RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { - userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), - recoveryFlowId); - } - userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts + 1); - if (log.isDebugEnabled()) { - log.debug("Invalid confirmation code for user: " + domainQualifiedName); - } + } + if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { + userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + recoveryFlowId); + } + userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts + 1); + if (log.isDebugEnabled()) { + log.debug("Invalid confirmation code for user: " + domainQualifiedName); } + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); } /** @@ -317,12 +316,12 @@ public SuccessfulPasswordResetDTO reset(String resetCode, char[] password, Map properties) @@ -570,6 +569,7 @@ private PasswordResetCodeDTO buildPasswordResetCodeDTO(String resetCode) { * @param notificationChannel Notified channel * @param confirmationCode Confirmation code for confirm recovery * @param resendCode Code to resend recovery confirmation code + * @param recoveryFlowId Recovery flow ID. * @return PasswordRecoverDTO object */ private PasswordRecoverDTO buildPasswordRecoveryResponseDTO(String notificationChannel, String confirmationCode, @@ -749,6 +749,7 @@ private void invalidateRecoveryInfoSendCode(String resendCode, String notificati * Add the notification channel recovery data to the store. * * @param secretKey RecoveryId + * @param recoveryFlowId Recovery flow ID. * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object * @throws IdentityRecoveryServerException Error storing recovery data */ diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java index e6678d752b..507b9b8bcf 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java @@ -293,7 +293,7 @@ private void invalidateRecoveryCode(String recoveryCode) throws IdentityRecovery /** * Invalidate the recovery flow id. * - * @param recoveryFlowId Recovery flow id + * @param recoveryFlowId Recovery flow id. */ private void invalidateRecoveryFlowId(String recoveryFlowId) throws IdentityRecoveryException { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java index cd91decebc..fc30fded96 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryData.java @@ -146,6 +146,7 @@ public User getUser() { } public int getFailedAttempts() { + return failedAttempts; } @@ -155,6 +156,7 @@ public void setFailedAttempts(int failedAttempts) { } public int getResendCount() { + return resendCount; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java index 475f27f206..856e91c2eb 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/model/UserRecoveryFlowData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -37,6 +37,7 @@ public UserRecoveryFlowData(String recoveryFlowId) { this.recoveryFlowId = recoveryFlowId; } + public UserRecoveryFlowData(String recoveryFlowId, Timestamp timeCreated) { this.recoveryFlowId = recoveryFlowId; 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 1476d0c4e1..f8372f9c66 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 @@ -554,6 +554,7 @@ public void updatePassword(String code, String password, Property[] properties) */ public void updatePassword(String code, String confirmationCode, String password, Property[] properties) throws IdentityRecoveryException, IdentityEventException { + updateUserPassword(code, confirmationCode, password, properties); } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java index ef0af9a584..0a2ee598f3 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/services/password/PasswordRecoveryManager.java @@ -73,12 +73,12 @@ PasswordResetCodeDTO confirm(String confirmationCode, String tenantDomain, Map properties) throws IdentityRecoveryException; @@ -99,13 +99,13 @@ SuccessfulPasswordResetDTO reset(String resetCode, char[] password, Map properties) throws IdentityRecoveryException; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 11d2504fa0..657574bb79 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -86,7 +86,7 @@ public void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryExcept Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA_WITH_FLOW_ID); prepStmt.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); @@ -116,7 +116,7 @@ public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryEx PreparedStatement prepStmt1 = null; PreparedStatement prepStmt2 = null; try { - prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA_WITH_FLOW_ID); prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); @@ -158,7 +158,7 @@ public void storeConfirmationCode(UserRecoveryData recoveryDataDO) throws Identi PreparedStatement prepStmt1 = null; PreparedStatement prepStmt2 = null; try { - prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_ACCOUNT_RECOVERY_DATA); + prepStmt1 = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA_WITH_FLOW_ID); prepStmt1.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt1.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt1.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); @@ -387,7 +387,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov User user = null; String code = null; int failedAttempts = 0; - int resendCount =0; + int resendCount = 0; long createdTimeStamp = 0; UserRecoveryData userRecoveryData = null; Boolean isOperationSuccess = false; @@ -1050,11 +1050,11 @@ private boolean isCodeExpired(String tenantDomain, Enum recoveryScenario, Enum r /** * Checks whether the recovery flow id has expired or not. * - * @param tenantDomain Tenant domain - * @param createdTimestamp Time stamp - * @param recoveryData Additional data for validate the code - * @return Whether the recovery flow id has expired or not - * @throws IdentityRecoveryServerException Error while reading the configs + * @param tenantDomain Tenant domain. + * @param createdTimestamp Time stamp. + * @param recoveryData Additional data for validate the code. + * @return Whether the recovery flow id has expired or not. + * @throws IdentityRecoveryServerException Error while reading the configs. */ private boolean isRecoveryFlowIdExpired(String tenantDomain, long createdTimestamp, String recoveryData) throws IdentityRecoveryServerException { From f47cf74a7f650ef3c26d16d5e3a97be664e265e1 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 09:02:10 +0530 Subject: [PATCH 10/27] Fix minor issues --- .../recovery/confirmation/ResendConfirmationManager.java | 2 +- .../internal/service/impl/UserAccountRecoveryManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index e4d78f752b..fc20e9061a 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -351,7 +351,7 @@ private void invalidateResendConfirmationCode(String resendCode, String notifica * Add the notification channel recovery data to the store. * * @param secretKey RecoveryId - * @param recoveryFlowId Recovery flow ID + * @param recoveryFlowId Recovery flow ID. * @param recoveryData Data to be stored as mata which are needed to evaluate the recovery data object * @param recoveryScenario Recovery scenario * @param recoveryStep Recovery step diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index af2d848705..c650fce04a 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -433,7 +433,7 @@ private List getInternalNotificationChannelList(String user * @param recoveryFlowId Recovery flow ID. * @param recoveryCode Recovery code given to the user * @param notificationChannelDTOs List of NotificationChannelsResponseDTOs available for the user. - * @return RecoveryChannelInfoD TO object. + * @return RecoveryChannelInfoDTO object. */ private RecoveryChannelInfoDTO buildUserRecoveryInformationResponseDTO(String username, String recoveryFlowId, String recoveryCode, NotificationChannelDTO[] notificationChannelDTOs) { From bc26f3b539ca0f4b6b3c8a3b52fc7fcd619872ac Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 09:24:29 +0530 Subject: [PATCH 11/27] Add a constant for confirmation code separator --- .../carbon/identity/recovery/IdentityRecoveryConstants.java | 1 + .../recovery/confirmation/ResendConfirmationManager.java | 2 +- .../service/impl/password/PasswordRecoveryManagerImpl.java | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) 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 98b7132c24..351d4841d3 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 @@ -155,6 +155,7 @@ public class IdentityRecoveryConstants { public static final String NOTIFICATION_CHANNEL_PROPERTY_KEY = "notificationChannel"; public static final String VERIFIED_USER_PROPERTY_KEY = "verifiedUser"; public static final String MANAGE_NOTIFICATIONS_INTERNALLY_PROPERTY_KEY = "manageNotificationsInternally"; + public static final String CONFIRMATION_CODE_SEPARATOR = "."; // Recovery Scenarios. public static final String USER_NAME_RECOVERY = "UNR"; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index fc20e9061a..4cd252d1cc 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -180,7 +180,7 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese confirmationCode = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), recoveryScenario); if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, NotificationChannels.SMS_CHANNEL.getChannelType())) { - confirmationCode = recoveryFlowId + "." + confirmationCode; + confirmationCode = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + confirmationCode; } // Store new confirmation code. addRecoveryDataObject(confirmationCode, recoveryFlowId, notificationChannel, scenario, step, user); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 382b8e0e08..e41f0ae9fd 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -58,6 +58,7 @@ import java.util.ArrayList; import java.util.Map; import java.util.UUID; +import java.util.regex.Pattern; /** * Class that implements the PasswordRecoveryManager. @@ -225,7 +226,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String // In the recovery scenarios which are not OTP based, the confirmation code is a combination of the // recovery flow id and another UUID generated. Hence, we need to get the recovery flow id from the // confirmation code. In the OTP based recovery scenario, the confirmation code is the recovery flow id. - String[] ids = confirmationCode.split("\\."); + String[] ids = confirmationCode.split(Pattern.quote(IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR)); String recoveryFlowId; String code; if (ids.length == 2) { From c81f391eba454033abca6c65384b61c985de01b5 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 09:25:36 +0530 Subject: [PATCH 12/27] Fix minor issues --- .../recovery/confirmation/ResendConfirmationManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index 4cd252d1cc..ce9febbdb6 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -180,7 +180,8 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese confirmationCode = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), recoveryScenario); if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, NotificationChannels.SMS_CHANNEL.getChannelType())) { - confirmationCode = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + confirmationCode; + confirmationCode = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + + confirmationCode; } // Store new confirmation code. addRecoveryDataObject(confirmationCode, recoveryFlowId, notificationChannel, scenario, step, user); From 4800862fbd792c2a1f50b8f99dce0b3d0506816e Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 13:39:37 +0530 Subject: [PATCH 13/27] Fix minor issues --- .../carbon/identity/recovery/IdentityRecoveryConstants.java | 4 ++-- .../service/impl/password/PasswordRecoveryManagerImpl.java | 5 +++-- .../password/NotificationPasswordRecoveryManager.java | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) 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 351d4841d3..115bc6a682 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 @@ -704,8 +704,8 @@ public static class SQLQueries { public static final String UPDATE_CODE_RESEND_COUNT = "UPDATE IDN_RECOVERY_FLOW_DATA SET RESEND_COUNT = ? " + "WHERE RECOVERY_FLOW_ID = ?"; - public static final String LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * FROM IDN_RECOVERY_FLOW_DATA " - + "WHERE RECOVERY_FLOW_ID = ?"; + public static final String LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * " + + "FROM IDN_RECOVERY_FLOW_DATA WHERE RECOVERY_FLOW_ID = ?"; public static final String INVALIDATE_FLOW_DATA_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_FLOW_DATA WHERE " + "RECOVERY_FLOW_ID = ?"; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index e41f0ae9fd..93b0b8e211 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -253,7 +253,8 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String } return buildPasswordResetCodeDTO(code); } - if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + failedAttempts = failedAttempts + 1; + if (failedAttempts >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); throw Utils.handleClientException( @@ -261,7 +262,7 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), recoveryFlowId); } - userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts + 1); + userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts); if (log.isDebugEnabled()) { log.debug("Invalid confirmation code for user: " + domainQualifiedName); } 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 f8372f9c66..d7a8948a23 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 @@ -260,7 +260,7 @@ private UserRecoveryData generateNewConfirmationCode(User user, String notificat RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY.name()); if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, NotificationChannels.SMS_CHANNEL.getChannelType())) { - secretKey = recoveryFlowId + "." + secretKey; + secretKey = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + secretKey; } UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY, RecoverySteps.UPDATE_PASSWORD); From f4785bb995972c492954206e9cef40d86de7c6b9 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 16:50:06 +0530 Subject: [PATCH 14/27] Add changes related to the foreign key added for recovery flow id --- .../recovery/IdentityRecoveryConstants.java | 5 +---- .../recovery/store/JDBCRecoveryDataStore.java | 21 +++++++------------ 2 files changed, 8 insertions(+), 18 deletions(-) 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 115bc6a682..062cf03725 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 @@ -656,9 +656,6 @@ public static class SQLQueries { public static final String INVALIDATE_USER_CODE_BY_SCENARIO = "DELETE FROM IDN_RECOVERY_DATA WHERE " + "USER_NAME = ? AND SCENARIO = ? AND STEP = ? AND USER_DOMAIN = ? AND TENANT_ID =?"; - public static final String INVALIDATE_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_DATA WHERE " + - "RECOVERY_FLOW_ID = ?"; - public static final String UPDATE_CODE = "UPDATE IDN_RECOVERY_DATA SET CODE = ?, STEP = ?, REMAINING_SETS = ? " + "WHERE CODE = ?"; @@ -707,7 +704,7 @@ public static class SQLQueries { public static final String LOAD_RECOVERY_FLOW_DATA_FROM_RECOVERY_FLOW_ID = "SELECT * " + "FROM IDN_RECOVERY_FLOW_DATA WHERE RECOVERY_FLOW_ID = ?"; - public static final String INVALIDATE_FLOW_DATA_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_FLOW_DATA WHERE " + + public static final String INVALIDATE_BY_RECOVERY_FLOW_ID = "DELETE FROM IDN_RECOVERY_FLOW_DATA WHERE " + "RECOVERY_FLOW_ID = ?"; } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 657574bb79..2b88878455 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -136,8 +136,8 @@ public void storeInit(UserRecoveryData recoveryDataDO) throws IdentityRecoveryEx prepStmt2.setTimestamp(5, new Timestamp(new Date().getTime()), Calendar.getInstance(TimeZone.getTimeZone(UTC))); - prepStmt1.execute(); prepStmt2.execute(); + prepStmt1.execute(); IdentityDatabaseUtil.commitTransaction(connection); } catch (SQLException e) { @@ -828,29 +828,22 @@ public void invalidate(User user, Enum recoveryScenario, Enum recoveryStep) thro @Override public void invalidateWithRecoveryFlowId(String recoveryFlowId) throws IdentityRecoveryException { - PreparedStatement prepStmt1 = null; - PreparedStatement prepStmt2 = null; + PreparedStatement prepStmt = null; Connection connection = IdentityDatabaseUtil.getDBConnection(true); try { - String sql1 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_BY_RECOVERY_FLOW_ID; - - prepStmt1 = connection.prepareStatement(sql1); - prepStmt1.setString(1, recoveryFlowId); - prepStmt1.execute(); - String sql2 = IdentityRecoveryConstants.SQLQueries.INVALIDATE_FLOW_DATA_BY_RECOVERY_FLOW_ID; + String sql = IdentityRecoveryConstants.SQLQueries.INVALIDATE_BY_RECOVERY_FLOW_ID; - prepStmt2 = connection.prepareStatement(sql2); - prepStmt2.setString(1, recoveryFlowId); - prepStmt2.execute(); + prepStmt = connection.prepareStatement(sql); + prepStmt.setString(1, recoveryFlowId); + prepStmt.execute(); IdentityDatabaseUtil.commitTransaction(connection); } catch (SQLException e) { IdentityDatabaseUtil.rollbackTransaction(connection); throw Utils.handleServerException(ERROR_CODE_UNEXPECTED, null, e); } finally { - IdentityDatabaseUtil.closeStatement(prepStmt1); - IdentityDatabaseUtil.closeStatement(prepStmt2); + IdentityDatabaseUtil.closeStatement(prepStmt); IdentityDatabaseUtil.closeConnection(connection); } } From 5cea278e600b178bd42528eb13172badb90e238d Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 17:11:30 +0530 Subject: [PATCH 15/27] Add DB constants --- .../recovery/IdentityRecoveryConstants.java | 15 ++++++++ .../recovery/store/JDBCRecoveryDataStore.java | 34 ++++++++++--------- 2 files changed, 33 insertions(+), 16 deletions(-) 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 062cf03725..547fddf5e9 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 @@ -364,6 +364,7 @@ public enum ErrorMessages { "User account recovery validation failed for user account: '%s'"), ERROR_CODE_INVALID_RECOVERY_FLOW_ID("UAR-10015", "Invalid confirmation code : '%s'."), ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'."), + ERROR_CODE_API_DISABLED("UAR-10017", "Recovery API is disabled"), ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " + "recovery flow id : '%s'."), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), @@ -620,6 +621,20 @@ public static class ConnectorConfig { ".Password.MaxResendAttempts"; } + public static class DBConstants { + + public static final String USER_NAME = "USER_NAME"; + public static final String TENANT_ID = "TENANT_ID"; + public static final String USER_DOMAIN = "USER_DOMAIN"; + public static final String CODE = "CODE"; + public static final String SCENARIO = "SCENARIO"; + public static final String REMAINING_SETS = "REMAINING_SETS"; + public static final String RECOVERY_FLOW_ID = "RECOVERY_FLOW_ID"; + public static final String FAILED_ATTEMPTS = "FAILED_ATTEMPTS"; + public static final String RESEND_COUNT = "RESEND_COUNT"; + public static final String TIME_CREATED = "TIME_CREATED"; + } + public static class SQLQueries { public static final String STORE_RECOVERY_DATA = "INSERT INTO IDN_RECOVERY_DATA " diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index 2b88878455..f6bdcae812 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -334,7 +334,7 @@ public UserRecoveryData load(String code, boolean skipExpiryValidation) throws I Enum recoveryStep = RecoverySteps.valueOf(resultSet.getString("STEP")); Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); - String recoveryFlowId = resultSet.getString("RECOVERY_FLOW_ID"); + String recoveryFlowId = resultSet.getString(IdentityRecoveryConstants.DBConstants.RECOVERY_FLOW_ID); userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, recoveryScenario, recoveryStep, timeCreated); @@ -400,9 +400,9 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov resultSet1 = prepStmt1.executeQuery(); if (resultSet1.next()) { - failedAttempts = resultSet1.getInt("FAILED_ATTEMPTS"); - resendCount = resultSet1.getInt("RESEND_COUNT"); - Timestamp timeCreated = resultSet1.getTimestamp("TIME_CREATED", + failedAttempts = resultSet1.getInt(IdentityRecoveryConstants.DBConstants.FAILED_ATTEMPTS); + resendCount = resultSet1.getInt(IdentityRecoveryConstants.DBConstants.RESEND_COUNT); + Timestamp timeCreated = resultSet1.getTimestamp(IdentityRecoveryConstants.DBConstants.TIME_CREATED, Calendar.getInstance(TimeZone.getTimeZone(UTC))); createdTimeStamp = timeCreated.getTime(); } @@ -416,14 +416,16 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov if (resultSet2.next()) { user = new User(); - user.setUserName(resultSet2.getString("USER_NAME")); - user.setTenantDomain(IdentityTenantUtil.getTenantDomain(resultSet2.getInt("TENANT_ID"))); - user.setUserStoreDomain(resultSet2.getString("USER_DOMAIN")); - - code = resultSet2.getString("CODE"); - Enum recoveryScenario = RecoveryScenarios.valueOf(resultSet2.getString("SCENARIO")); - String remainingSets = resultSet2.getString("REMAINING_SETS"); - Timestamp secretCreatedTime = resultSet2.getTimestamp("TIME_CREATED", + user.setUserName(resultSet2.getString(IdentityRecoveryConstants.DBConstants.USER_NAME)); + user.setTenantDomain(IdentityTenantUtil.getTenantDomain(resultSet2.getInt( + IdentityRecoveryConstants.DBConstants.TENANT_ID))); + user.setUserStoreDomain(resultSet2.getString(IdentityRecoveryConstants.DBConstants.USER_DOMAIN)); + + code = resultSet2.getString(IdentityRecoveryConstants.DBConstants.CODE); + Enum recoveryScenario = RecoveryScenarios.valueOf(resultSet2.getString(IdentityRecoveryConstants. + DBConstants.SCENARIO)); + String remainingSets = resultSet2.getString(IdentityRecoveryConstants.DBConstants.REMAINING_SETS); + Timestamp secretCreatedTime = resultSet2.getTimestamp(IdentityRecoveryConstants.DBConstants.TIME_CREATED, Calendar.getInstance(TimeZone.getTimeZone(UTC))); userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, failedAttempts, resendCount, @@ -477,9 +479,9 @@ public UserRecoveryFlowData loadRecoveryFlowData(UserRecoveryData recoveryDataDO resultSet = prepStmt.executeQuery(); if (resultSet.next()) { - int failedAttempts = resultSet.getInt("FAILED_ATTEMPTS"); - int resendCount = resultSet.getInt("RESEND_COUNT"); - Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", + int failedAttempts = resultSet.getInt(IdentityRecoveryConstants.DBConstants.FAILED_ATTEMPTS); + int resendCount = resultSet.getInt(IdentityRecoveryConstants.DBConstants.RESEND_COUNT); + Timestamp timeCreated = resultSet.getTimestamp(IdentityRecoveryConstants.DBConstants.TIME_CREATED, Calendar.getInstance(TimeZone.getTimeZone(UTC))); long createdTimeStamp = timeCreated.getTime(); @@ -627,7 +629,7 @@ public UserRecoveryData loadWithoutCodeExpiryValidation(User user) throws Identi code = resultSet.getString("CODE"); Timestamp timeCreated = resultSet.getTimestamp("TIME_CREATED", Calendar.getInstance(TimeZone.getTimeZone(UTC))); - String recoveryFlowId = resultSet.getString("RECOVERY_FLOW_ID"); + String recoveryFlowId = resultSet.getString(IdentityRecoveryConstants.DBConstants.RECOVERY_FLOW_ID); userRecoveryData = new UserRecoveryData(user, recoveryFlowId, code, scenario, step, timeCreated); From cbe3ddb4bd51daf887ec16489c7a082e079778a2 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 17:12:35 +0530 Subject: [PATCH 16/27] Remove unwanted line --- .../wso2/carbon/identity/recovery/IdentityRecoveryConstants.java | 1 - 1 file changed, 1 deletion(-) 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 547fddf5e9..651e3f689f 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 @@ -364,7 +364,6 @@ public enum ErrorMessages { "User account recovery validation failed for user account: '%s'"), ERROR_CODE_INVALID_RECOVERY_FLOW_ID("UAR-10015", "Invalid confirmation code : '%s'."), ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'."), - ERROR_CODE_API_DISABLED("UAR-10017", "Recovery API is disabled"), ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " + "recovery flow id : '%s'."), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), From 3c3418f066ee4b132dbb82bd78e789882f0edebb Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 8 Sep 2023 17:19:38 +0530 Subject: [PATCH 17/27] Fix formatting --- .../carbon/identity/recovery/store/JDBCRecoveryDataStore.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index f6bdcae812..ab487f2e9e 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -86,7 +86,8 @@ public void store(UserRecoveryData recoveryDataDO) throws IdentityRecoveryExcept Connection connection = IdentityDatabaseUtil.getDBConnection(true); PreparedStatement prepStmt = null; try { - prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries.STORE_RECOVERY_DATA_WITH_FLOW_ID); + prepStmt = connection.prepareStatement(IdentityRecoveryConstants.SQLQueries. + STORE_RECOVERY_DATA_WITH_FLOW_ID); prepStmt.setString(1, recoveryDataDO.getUser().getUserName()); prepStmt.setString(2, recoveryDataDO.getUser().getUserStoreDomain().toUpperCase()); prepStmt.setInt(3, IdentityTenantUtil.getTenantId(recoveryDataDO.getUser().getTenantDomain())); From 4db817319d0a708978f22ece2e567574694fb81e Mon Sep 17 00:00:00 2001 From: Rashmini Date: Mon, 11 Sep 2023 16:56:01 +0530 Subject: [PATCH 18/27] Update recovery flow id and secret concat logic --- .../recovery/IdentityRecoveryConstants.java | 1 + .../confirmation/ResendConfirmationManager.java | 7 ++----- .../password/PasswordRecoveryManagerImpl.java | 5 +++++ .../NotificationPasswordRecoveryManager.java | 5 +---- .../recovery/store/JDBCRecoveryDataStore.java | 2 +- .../carbon/identity/recovery/util/Utils.java | 17 +++++++++++++++++ 6 files changed, 27 insertions(+), 10 deletions(-) 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 651e3f689f..0d124d855d 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 @@ -366,6 +366,7 @@ public enum ErrorMessages { ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'."), ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " + "recovery flow id : '%s'."), + ERROR_CODE_NO_OTP_IN_REQUEST("UAR-10019", "No OTP found."), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), ERROR_CODE_ERROR_GETTING_USERSTORE_MANAGER("UAR-15002", "Error getting userstore manager"), ERROR_CODE_ERROR_RETRIEVING_USER_CLAIM("UAR-15003", "Error getting the claims: '%s' " diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java index ce9febbdb6..62236b267b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/confirmation/ResendConfirmationManager.java @@ -178,11 +178,8 @@ public ResendConfirmationDTO resendConfirmation(String tenantDomain, String rese } else { userRecoveryDataStore.invalidate(user); confirmationCode = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), recoveryScenario); - if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, - NotificationChannels.SMS_CHANNEL.getChannelType())) { - confirmationCode = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + - confirmationCode; - } + confirmationCode = Utils.concatRecoveryFlowIdWithSecretKey(recoveryFlowId, notificationChannel, + confirmationCode); // Store new confirmation code. addRecoveryDataObject(confirmationCode, recoveryFlowId, notificationChannel, scenario, step, user); } diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 93b0b8e211..54a8f65bef 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -235,6 +235,11 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String } else { recoveryFlowId = confirmationCode; code = otp; + if (code == null) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_OTP_IN_REQUEST.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_OTP_IN_REQUEST.getMessage(), null); + } } // Get Recovery data. UserRecoveryData userRecoveryData = userAccountRecoveryManager 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 d7a8948a23..36aef2548b 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 @@ -258,10 +258,7 @@ private UserRecoveryData generateNewConfirmationCode(User user, String notificat userRecoveryDataStore.invalidate(user); String secretKey = Utils.generateSecretKey(notificationChannel, user.getTenantDomain(), RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY.name()); - if (recoveryFlowId != null && !StringUtils.equals(notificationChannel, - NotificationChannels.SMS_CHANNEL.getChannelType())) { - secretKey = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + secretKey; - } + secretKey = Utils.concatRecoveryFlowIdWithSecretKey(recoveryFlowId, notificationChannel, secretKey); UserRecoveryData recoveryDataDO = new UserRecoveryData(user, recoveryFlowId, secretKey, RecoveryScenarios.NOTIFICATION_BASED_PW_RECOVERY, RecoverySteps.UPDATE_PASSWORD); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java index ab487f2e9e..060445a32b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/store/JDBCRecoveryDataStore.java @@ -437,7 +437,7 @@ public UserRecoveryData loadFromRecoveryFlowId(String recoveryFlowId, Enum recov if (isCodeExpired) { isOperationSuccess = false; description = ERROR_CODE_EXPIRED_CODE; - throw Utils.handleClientException(ERROR_CODE_EXPIRED_CODE, code); + throw Utils.handleClientException(ERROR_CODE_EXPIRED_CODE, null); } boolean isRecoveryFlowIdExpired = isRecoveryFlowIdExpired(user.getTenantDomain(), createdTimeStamp, userRecoveryData.getRemainingSetIds()); diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/util/Utils.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/util/Utils.java index 3755826e9e..268a5b12a4 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/util/Utils.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/util/Utils.java @@ -1183,6 +1183,23 @@ public static String generateSecretKey(String channel, String tenantDomain, Stri } } + /** + * Concatenate recovery flow id with the generated secret key if the notification channel is email. + * + * @param recoveryFlowId Recovery flow id. + * @param notificationChannel Recovery notification channel. + * @param secretKey Secret Key. + * @return Secret key. + */ + public static String concatRecoveryFlowIdWithSecretKey(String recoveryFlowId, String notificationChannel, + String secretKey) { + if (recoveryFlowId != null && StringUtils.equals(notificationChannel, + NotificationChannels.EMAIL_CHANNEL.getChannelType())) { + secretKey = recoveryFlowId + IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR + secretKey; + } + return secretKey; + } + /** * Return user account state. * From 9aa3aa42077eae1dca4b73d4d62d07b8f55d28b3 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 12 Sep 2023 13:36:26 +0530 Subject: [PATCH 19/27] Fix compilation failure --- .../password/NotificationPasswordRecoveryManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 4ce7165d47..44792699c1 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 @@ -609,7 +609,9 @@ public User updateUserPassword(String code, String password, Property[] properti String emailTemplate = null; if (isAskPasswordFlow(userRecoveryData) && isAskPasswordEmailTemplateTypeExists(userRecoveryData.getUser().getTenantDomain())) { - emailTemplate = IdentityRecoveryConstants.ACCOUNT_ACTIVATION_SUCCESS; + if (isNotificationSendOnAccountActivation) { + emailTemplate = IdentityRecoveryConstants.ACCOUNT_ACTIVATION_SUCCESS; + } } else if (isNotificationSendWhenSuccess) { emailTemplate = IdentityRecoveryConstants.NOTIFICATION_TYPE_PASSWORD_RESET_SUCCESS; } @@ -688,6 +690,9 @@ public User updateUserPassword(String code, String confirmationCode, String pass boolean isNotificationSendWhenSuccess = Boolean.parseBoolean(Utils.getRecoveryConfigs( IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS, userRecoveryData.getUser().getTenantDomain())); + boolean isNotificationSendOnAccountActivation = Boolean.parseBoolean(Utils.getRecoveryConfigs( + IdentityRecoveryConstants.ConnectorConfig.EMAIL_VERIFICATION_NOTIFICATION_ACCOUNT_ACTIVATION, + userRecoveryData.getUser().getTenantDomain())); String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), userRecoveryData.getUser().getUserStoreDomain()); From 4c8c9f64b52364e581f4f4f1890afe0f11577c08 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 12 Sep 2023 13:53:28 +0530 Subject: [PATCH 20/27] Fix comments --- .../internal/service/impl/UserAccountRecoveryManager.java | 8 +++++++- .../impl/password/PasswordRecoveryManagerImpl.java | 6 +++--- .../impl/username/UsernameRecoveryManagerImpl.java | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index c650fce04a..844bc80b40 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -936,6 +936,7 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr * * @param recoveryFlowId Recovery flow id of the user. * @param step Recovery step. + * @return UserRecoveryData Data associated with the provided recoveryFlowId. * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. */ public UserRecoveryData getUserRecoveryDataFromFlowId(String recoveryFlowId, RecoverySteps step) @@ -973,6 +974,7 @@ public UserRecoveryData getUserRecoveryDataFromFlowId(String recoveryFlowId, Rec * Get user recovery flow data using the recovery flow id. * * @param recoveryDataDO User Recovery Data object. + * @return UserRecoveryFlowData Data associated with the provided UserRecoveryData. * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. */ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDataDO) @@ -1006,8 +1008,10 @@ public UserRecoveryFlowData loadUserRecoveryFlowData(UserRecoveryData recoveryDa * * @param recoveryFlowId Recovery Flow Id. * @param failedAttempts Failed Attempts. + * @throws IdentityRecoveryException If an error occurred while updating the recovery flow data. */ - public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAttempts) throws IdentityRecoveryException { + public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAttempts) + throws IdentityRecoveryException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); userRecoveryDataStore.updateFailedAttempts(recoveryFlowId, failedAttempts); @@ -1018,6 +1022,7 @@ public void updateRecoveryDataFailedAttempts(String recoveryFlowId, int failedAt * * @param recoveryFlowId Recovery Flow Id. * @param resendCount Current Resend Count. + * @throws IdentityRecoveryException If an error occurred while updating the recovery flow data. */ public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount) throws IdentityRecoveryException { @@ -1029,6 +1034,7 @@ public void updateRecoveryDataResendCount(String recoveryFlowId, int resendCount * Invalidate the recovery Data. * * @param recoveryFlowId Recovery Flow Id. + * @throws IdentityRecoveryException If an error occurred while invalidating recovery data. */ public void invalidateRecoveryData(String recoveryFlowId) throws IdentityRecoveryException { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 54a8f65bef..a0c105246a 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -223,9 +223,9 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String validateTenantDomain(tenantDomain); UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); - // In the recovery scenarios which are not OTP based, the confirmation code is a combination of the - // recovery flow id and another UUID generated. Hence, we need to get the recovery flow id from the - // confirmation code. In the OTP based recovery scenario, the confirmation code is the recovery flow id. + /* In the recovery scenarios which are not OTP based, the confirmation code is a combination of the + recovery flow id and another UUID generated. Hence, we need to get the recovery flow id from the + confirmation code. In the OTP based recovery scenario, the confirmation code is the recovery flow id. */ String[] ids = confirmationCode.split(Pattern.quote(IdentityRecoveryConstants.CONFIRMATION_CODE_SEPARATOR)); String recoveryFlowId; String code; diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java index 507b9b8bcf..5fd5ebcf9a 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java @@ -294,6 +294,7 @@ private void invalidateRecoveryCode(String recoveryCode) throws IdentityRecovery * Invalidate the recovery flow id. * * @param recoveryFlowId Recovery flow id. + * @throws IdentityRecoveryException If an error occurred while invalidating recovery data. */ private void invalidateRecoveryFlowId(String recoveryFlowId) throws IdentityRecoveryException { From d2bf3964ac28202661889e79621b818fa8e13451 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 15 Sep 2023 09:45:25 +0530 Subject: [PATCH 21/27] Add fallback logic for already initiated email link based recovery flows --- .../recovery/IdentityRecoveryConstants.java | 1 - .../impl/UserAccountRecoveryManager.java | 52 +++++++++++++ .../password/PasswordRecoveryManagerImpl.java | 77 +++++++++++-------- .../NotificationPasswordRecoveryManager.java | 64 ++++++++++----- 4 files changed, 141 insertions(+), 53 deletions(-) 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 037d21f852..3e7c7700fb 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 @@ -376,7 +376,6 @@ public enum ErrorMessages { ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID("UAR-10016", "Expired confirmation code : '%s'."), ERROR_CODE_NO_RECOVERY_FLOW_DATA("UAR-10018", "No recovery flow data found for " + "recovery flow id : '%s'."), - ERROR_CODE_NO_OTP_IN_REQUEST("UAR-10019", "No OTP found."), ERROR_CODE_ERROR_STORING_RECOVERY_DATA("UAR-15001", "Error storing user recovery data"), ERROR_CODE_ERROR_GETTING_USERSTORE_MANAGER("UAR-15002", "Error getting userstore manager"), ERROR_CODE_ERROR_RETRIEVING_USER_CLAIM("UAR-15003", "Error getting the claims: '%s' " diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index 844bc80b40..f77c96be37 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -931,6 +931,58 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr return recoveryData; } + /** + * This method is to retrieve user recovery data using the confirmation code when there's no recovery flow id. + * This is added as a fallback logic to handle the already initiated email link based recovery flows which do not + * have recovery flow ids, which were initiated before moving to the Recovery V2 API. + * This shouldn't be used for any other purpose and should be kept for sometime. + * + * @param code Code given for recovery + * @param recoveryFlowId Recovery flow id of the user. + * @param step Recovery step + * @return UserRecoveryData Data associated with the provided code. + * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. + */ + @Deprecated + public UserRecoveryData getUserRecoveryDataFromConfirmationCode(String code, String recoveryFlowId, + RecoverySteps step) + throws IdentityRecoveryException { + + UserRecoveryData recoveryData; + UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); + try { + // Retrieve recovery data bound to the recoveryId. + recoveryData = userRecoveryDataStore.load(code); + } catch (IdentityRecoveryException e) { + // Map code expired error to new error codes for user account recovery. + if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode().equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); + } else if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_CODE.getCode() + .equals(e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID.getCode()); + } else { + e.setErrorCode(Utils.prependOperationScenarioToErrorCode(e.getErrorCode(), + IdentityRecoveryConstants.USER_ACCOUNT_RECOVERY)); + } + throw e; + } + if (recoveryData == null) { + throw Utils + .handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_ACCOUNT_RECOVERY_DATA, + recoveryFlowId); + } + if (!StringUtils.equals(recoveryData.getRemainingSetIds(), + NotificationChannels.EMAIL_CHANNEL.getChannelType())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID, recoveryFlowId); + } + if (!step.equals(recoveryData.getRecoveryStep())) { + throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE, + recoveryFlowId); + } + return recoveryData; + } + /** * Get user recovery data using the recovery flow id. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index a0c105246a..0a4b7aaa5b 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -235,45 +235,58 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String } else { recoveryFlowId = confirmationCode; code = otp; - if (code == null) { - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_OTP_IN_REQUEST.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_OTP_IN_REQUEST.getMessage(), null); - } } // Get Recovery data. - UserRecoveryData userRecoveryData = userAccountRecoveryManager - .getUserRecoveryDataFromFlowId(recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); - int failedAttempts = userRecoveryData.getFailedAttempts(); - if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { + try { + UserRecoveryData userRecoveryData = userAccountRecoveryManager + .getUserRecoveryDataFromFlowId(recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); + int failedAttempts = userRecoveryData.getFailedAttempts(); + if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, + tenantDomain); + } + String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), + userRecoveryData.getUser().getUserStoreDomain()); + if (StringUtils.equals(code, userRecoveryData.getSecret())) { + if (log.isDebugEnabled()) { + log.debug("Valid confirmation code for user: " + domainQualifiedName); + } + return buildPasswordResetCodeDTO(code); + } + failedAttempts = failedAttempts + 1; + if (failedAttempts >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { + userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + recoveryFlowId); + } + userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts); + if (log.isDebugEnabled()) { + log.debug("Invalid confirmation code for user: " + domainQualifiedName); + } throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, - tenantDomain); - } - String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), - userRecoveryData.getUser().getUserStoreDomain()); - if (StringUtils.equals(code, userRecoveryData.getSecret())) { + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); + } catch (IdentityRecoveryException e) { + // This is a fallback logic to support already initiated email link based recovery flows using the + // recovery V1 API, which do not have recovery flow ids. + UserRecoveryData userRecoveryData = userAccountRecoveryManager + .getUserRecoveryDataFromConfirmationCode(recoveryFlowId, recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); + if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, + tenantDomain); + } + String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), + userRecoveryData.getUser().getUserStoreDomain()); if (log.isDebugEnabled()) { log.debug("Valid confirmation code for user: " + domainQualifiedName); } - return buildPasswordResetCodeDTO(code); - } - failedAttempts = failedAttempts + 1; - if (failedAttempts >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. - RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, tenantDomain))) { - userAccountRecoveryManager.invalidateRecoveryData(recoveryFlowId); - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), - recoveryFlowId); - } - userAccountRecoveryManager.updateRecoveryDataFailedAttempts(recoveryFlowId, failedAttempts); - if (log.isDebugEnabled()) { - log.debug("Invalid confirmation code for user: " + domainQualifiedName); + return buildPasswordResetCodeDTO(recoveryFlowId); } - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); } /** 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 44792699c1..4d93ce374e 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 @@ -50,6 +50,7 @@ import org.wso2.carbon.identity.recovery.RecoverySteps; import org.wso2.carbon.identity.recovery.bean.NotificationResponseBean; import org.wso2.carbon.identity.recovery.internal.IdentityRecoveryServiceDataHolder; +import org.wso2.carbon.identity.recovery.internal.service.impl.UserAccountRecoveryManager; import org.wso2.carbon.identity.recovery.model.Property; import org.wso2.carbon.identity.recovery.model.UserRecoveryData; import org.wso2.carbon.identity.recovery.store.JDBCRecoveryDataStore; @@ -659,27 +660,46 @@ public User updateUserPassword(String code, String confirmationCode, String pass throws IdentityRecoveryException, IdentityEventException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - UserRecoveryData userRecoveryData = userRecoveryDataStore.loadFromRecoveryFlowId(confirmationCode, - RecoverySteps.UPDATE_PASSWORD); - validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); - publishEvent(userRecoveryData.getUser(), null, null, password, properties, - IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); - validateTenantDomain(userRecoveryData.getUser()); - int failedAttempts = userRecoveryData.getFailedAttempts(); - - if (!StringUtils.equals(code, userRecoveryData.getSecret())) { - if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. - RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser().getTenantDomain()))) { - userRecoveryDataStore.invalidateWithRecoveryFlowId(confirmationCode); + UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); + UserRecoveryData userRecoveryData; + try { + userRecoveryData = userRecoveryDataStore.loadFromRecoveryFlowId(confirmationCode, + RecoverySteps.UPDATE_PASSWORD); + validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); + publishEvent(userRecoveryData.getUser(), null, null, password, properties, + IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); + validateTenantDomain(userRecoveryData.getUser()); + int failedAttempts = userRecoveryData.getFailedAttempts(); + + if (!StringUtils.equals(code, userRecoveryData.getSecret())) { + if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. + RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser().getTenantDomain()))) { + userRecoveryDataStore.invalidateWithRecoveryFlowId(confirmationCode); + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), + confirmationCode); + } + userRecoveryDataStore.updateFailedAttempts(confirmationCode, failedAttempts + 1); throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getMessage(), - confirmationCode); + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode(), + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); + } + } catch (IdentityRecoveryException e) { + // This is a fallback logic to support already initiated email link based recovery flows using the + // recovery V1 API, which do not have recovery flow ids. + userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode(code, confirmationCode, + RecoverySteps.UPDATE_PASSWORD); + validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); + publishEvent(userRecoveryData.getUser(), null, code, password, properties, + IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); + validateTenantDomain(userRecoveryData.getUser()); + + // Validate recovery step. + if (!RecoverySteps.UPDATE_PASSWORD.equals(userRecoveryData.getRecoveryStep())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE, code); } - userRecoveryDataStore.updateFailedAttempts(confirmationCode, failedAttempts + 1); - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode(), - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); } // Get the notification channel. @@ -699,7 +719,11 @@ public User updateUserPassword(String code, String confirmationCode, String pass // Update the password. updateNewPassword(userRecoveryData.getUser(), password, domainQualifiedName, userRecoveryData, notificationsInternallyManaged); - userRecoveryDataStore.invalidateWithRecoveryFlowId(userRecoveryData.getRecoveryFlowId()); + if (userRecoveryData.getRecoveryFlowId() != null) { + userRecoveryDataStore.invalidateWithRecoveryFlowId(userRecoveryData.getRecoveryFlowId()); + } else { + userRecoveryDataStore.invalidate(userRecoveryData.getUser()); + } if (notificationsInternallyManaged && !NotificationChannels.EXTERNAL_CHANNEL.getChannelType().equals(notificationChannel)) { String emailTemplate = null; From 6162e8ab45e59dfd18cb9c1b2105fb62af7dbf16 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 15 Sep 2023 09:49:29 +0530 Subject: [PATCH 22/27] Fix formatting --- .../internal/service/impl/UserAccountRecoveryManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index f77c96be37..219266f764 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -937,9 +937,9 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr * have recovery flow ids, which were initiated before moving to the Recovery V2 API. * This shouldn't be used for any other purpose and should be kept for sometime. * - * @param code Code given for recovery + * @param code Code given for recovery. * @param recoveryFlowId Recovery flow id of the user. - * @param step Recovery step + * @param step Recovery step. * @return UserRecoveryData Data associated with the provided code. * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. */ From 8cc27322854d0812d4c6e1b6a255f1609c36989a Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 15 Sep 2023 09:51:51 +0530 Subject: [PATCH 23/27] Fix formatting --- .../service/impl/password/PasswordRecoveryManagerImpl.java | 4 ++-- .../password/NotificationPasswordRecoveryManager.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 0a4b7aaa5b..238b351fcf 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -273,8 +273,8 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String } catch (IdentityRecoveryException e) { // This is a fallback logic to support already initiated email link based recovery flows using the // recovery V1 API, which do not have recovery flow ids. - UserRecoveryData userRecoveryData = userAccountRecoveryManager - .getUserRecoveryDataFromConfirmationCode(recoveryFlowId, recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); + UserRecoveryData userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode( + recoveryFlowId, recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { throw Utils.handleClientException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, 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 4d93ce374e..b4055e3f08 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 @@ -688,8 +688,8 @@ public User updateUserPassword(String code, String confirmationCode, String pass } catch (IdentityRecoveryException e) { // This is a fallback logic to support already initiated email link based recovery flows using the // recovery V1 API, which do not have recovery flow ids. - userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode(code, confirmationCode, - RecoverySteps.UPDATE_PASSWORD); + userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode(code, + confirmationCode, RecoverySteps.UPDATE_PASSWORD); validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); publishEvent(userRecoveryData.getUser(), null, code, password, properties, IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); From 32a535adb3d20c9db084bc68b91e1a07f7238834 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Fri, 15 Sep 2023 11:19:50 +0530 Subject: [PATCH 24/27] Fix formatting --- .../password/NotificationPasswordRecoveryManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 b4055e3f08..771d61730f 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 @@ -672,8 +672,9 @@ public User updateUserPassword(String code, String confirmationCode, String pass int failedAttempts = userRecoveryData.getFailedAttempts(); if (!StringUtils.equals(code, userRecoveryData.getSecret())) { - if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants.ConnectorConfig. - RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser().getTenantDomain()))) { + if ((failedAttempts + 1) >= Integer.parseInt(Utils.getRecoveryConfigs(IdentityRecoveryConstants. + ConnectorConfig.RECOVERY_OTP_PASSWORD_MAX_FAILED_ATTEMPTS, userRecoveryData.getUser(). + getTenantDomain()))) { userRecoveryDataStore.invalidateWithRecoveryFlowId(confirmationCode); throw Utils.handleClientException( IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode(), From 76dd41990655bc12db86bc979dfc6cc5d2343e23 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 19 Sep 2023 10:27:46 +0530 Subject: [PATCH 25/27] Refactor fallback logic --- .../impl/UserAccountRecoveryManager.java | 52 --------------- .../password/PasswordRecoveryManagerImpl.java | 63 +++++++++++++----- .../NotificationPasswordRecoveryManager.java | 64 ++++++++++++++----- 3 files changed, 96 insertions(+), 83 deletions(-) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java index 219266f764..844bc80b40 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java @@ -931,58 +931,6 @@ public UserRecoveryData getUserRecoveryData(String code, RecoverySteps step) thr return recoveryData; } - /** - * This method is to retrieve user recovery data using the confirmation code when there's no recovery flow id. - * This is added as a fallback logic to handle the already initiated email link based recovery flows which do not - * have recovery flow ids, which were initiated before moving to the Recovery V2 API. - * This shouldn't be used for any other purpose and should be kept for sometime. - * - * @param code Code given for recovery. - * @param recoveryFlowId Recovery flow id of the user. - * @param step Recovery step. - * @return UserRecoveryData Data associated with the provided code. - * @throws IdentityRecoveryException If an error occurred while validating the recoveryId. - */ - @Deprecated - public UserRecoveryData getUserRecoveryDataFromConfirmationCode(String code, String recoveryFlowId, - RecoverySteps step) - throws IdentityRecoveryException { - - UserRecoveryData recoveryData; - UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - try { - // Retrieve recovery data bound to the recoveryId. - recoveryData = userRecoveryDataStore.load(code); - } catch (IdentityRecoveryException e) { - // Map code expired error to new error codes for user account recovery. - if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getCode().equals(e.getErrorCode())) { - e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); - } else if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_CODE.getCode() - .equals(e.getErrorCode())) { - e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_EXPIRED_RECOVERY_FLOW_ID.getCode()); - } else { - e.setErrorCode(Utils.prependOperationScenarioToErrorCode(e.getErrorCode(), - IdentityRecoveryConstants.USER_ACCOUNT_RECOVERY)); - } - throw e; - } - if (recoveryData == null) { - throw Utils - .handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_ACCOUNT_RECOVERY_DATA, - recoveryFlowId); - } - if (!StringUtils.equals(recoveryData.getRemainingSetIds(), - NotificationChannels.EMAIL_CHANNEL.getChannelType())) { - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID, recoveryFlowId); - } - if (!step.equals(recoveryData.getRecoveryStep())) { - throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE, - recoveryFlowId); - } - return recoveryData; - } - /** * Get user recovery data using the recovery flow id. * diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 238b351fcf..0d9094d735 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -236,7 +236,6 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String recoveryFlowId = confirmationCode; code = otp; } - // Get Recovery data. try { UserRecoveryData userRecoveryData = userAccountRecoveryManager .getUserRecoveryDataFromFlowId(recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); @@ -271,21 +270,9 @@ public PasswordResetCodeDTO confirm(String otp, String confirmationCode, String IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode(), IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); } catch (IdentityRecoveryException e) { - // This is a fallback logic to support already initiated email link based recovery flows using the - // recovery V1 API, which do not have recovery flow ids. - UserRecoveryData userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode( - recoveryFlowId, recoveryFlowId, RecoverySteps.UPDATE_PASSWORD); - if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, - tenantDomain); - } - String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), - userRecoveryData.getUser().getUserStoreDomain()); - if (log.isDebugEnabled()) { - log.debug("Valid confirmation code for user: " + domainQualifiedName); - } - return buildPasswordResetCodeDTO(recoveryFlowId); + /* This is a fallback logic to support already initiated email link based recovery flows using the + recovery V1 API, which do not have recovery flow ids. */ + return validateConfirmationCode(userAccountRecoveryManager, recoveryFlowId, tenantDomain); } } @@ -874,4 +861,48 @@ private boolean isMinNoOfRecoveryQuestionsAnswered(String username, String tenan return isMinNoOfRecoveryQuestionsAnswered; } + /** + * This method is to validate the confirmation code when there's no recovery flow id. This is added as a fallback + * logic to handle the already initiated email link based recovery flows which do not have recovery flow ids, + * which were initiated before moving to the Recovery V2 API. This shouldn't be used for any other purpose and + * should be kept for sometime. + * + * @param userAccountRecoveryManager UserAccountRecoveryManager. + * @param confirmationCode Confirmation code. + * @param tenantDomain Tenant domain. + * @return PasswordResetCodeDTO {@link PasswordResetCodeDTO} object which contains password reset code. + * @throws IdentityRecoveryException Error while confirming password recovery. + */ + @Deprecated + private PasswordResetCodeDTO validateConfirmationCode(UserAccountRecoveryManager userAccountRecoveryManager, + String confirmationCode, String tenantDomain) + throws IdentityRecoveryException { + UserRecoveryData userRecoveryData; + try { + userRecoveryData = userAccountRecoveryManager.getUserRecoveryData(confirmationCode, + RecoverySteps.UPDATE_PASSWORD); + } catch (IdentityRecoveryException e) { + if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode().equals( + e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); + } + throw e; + } + if (!StringUtils.equals(userRecoveryData.getRemainingSetIds(), + NotificationChannels.EMAIL_CHANNEL.getChannelType())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID, confirmationCode); + } + if (!tenantDomain.equals(userRecoveryData.getUser().getTenantDomain())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_USER_TENANT_DOMAIN_MISS_MATCH_WITH_CONTEXT, + tenantDomain); + } + String domainQualifiedName = IdentityUtil.addDomainToName(userRecoveryData.getUser().getUserName(), + userRecoveryData.getUser().getUserStoreDomain()); + if (log.isDebugEnabled()) { + log.debug("Valid confirmation code for user: " + domainQualifiedName); + } + return buildPasswordResetCodeDTO(confirmationCode); + } } 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 771d61730f..c087cc44ee 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 @@ -660,7 +660,6 @@ public User updateUserPassword(String code, String confirmationCode, String pass throws IdentityRecoveryException, IdentityEventException { UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance(); - UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); UserRecoveryData userRecoveryData; try { userRecoveryData = userRecoveryDataStore.loadFromRecoveryFlowId(confirmationCode, @@ -687,20 +686,9 @@ public User updateUserPassword(String code, String confirmationCode, String pass IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE.getMessage(), code); } } catch (IdentityRecoveryException e) { - // This is a fallback logic to support already initiated email link based recovery flows using the - // recovery V1 API, which do not have recovery flow ids. - userRecoveryData = userAccountRecoveryManager.getUserRecoveryDataFromConfirmationCode(code, - confirmationCode, RecoverySteps.UPDATE_PASSWORD); - validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); - publishEvent(userRecoveryData.getUser(), null, code, password, properties, - IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); - validateTenantDomain(userRecoveryData.getUser()); - - // Validate recovery step. - if (!RecoverySteps.UPDATE_PASSWORD.equals(userRecoveryData.getRecoveryStep())) { - throw Utils.handleClientException( - IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE, code); - } + /* This is a fallback logic to support already initiated email link based recovery flows using the + recovery V1 API, which do not have recovery flow ids. */ + userRecoveryData = validateUserRecoveryDataFromCode(code, confirmationCode, password, properties); } // Get the notification channel. @@ -766,6 +754,52 @@ public User updateUserPassword(String code, String confirmationCode, String pass return userRecoveryData.getUser(); } + /** + * This method is to validate user recovery data using the reset code when there's no recovery flow id. + * This is added as a fallback logic to handle the already initiated email link based recovery flows which do not + * have recovery flow ids, which were initiated before moving to the Recovery V2 API. + * This shouldn't be used for any other purpose and should be kept for sometime. + * + * @param userAccountRecoveryManager UserAccountRecoveryManager. + * @param code Password Reset code. + * @param confirmationCode Confirmation code. + * @param password New password. + * @param properties Properties. + * @return UserRecoveryData. + * @throws IdentityRecoveryException Error while updating the password. + */ + @Deprecated + private UserRecoveryData validateUserRecoveryDataFromCode(String code, String confirmationCode, String password, + Property[] properties) throws IdentityRecoveryException { + UserRecoveryData userRecoveryData; + UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); + try { + userRecoveryData = userAccountRecoveryManager.getUserRecoveryData(code, RecoverySteps.UPDATE_PASSWORD); + } catch (IdentityRecoveryException e) { + if (IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_CODE.getCode().equals( + e.getErrorCode())) { + e.setErrorCode(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID.getCode()); + } + throw e; + } + if (!StringUtils.equals(userRecoveryData.getRemainingSetIds(), + NotificationChannels.EMAIL_CHANNEL.getChannelType())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_RECOVERY_FLOW_ID, confirmationCode); + } + validateCallback(properties, userRecoveryData.getUser().getTenantDomain()); + publishEvent(userRecoveryData.getUser(), null, code, password, properties, + IdentityEventConstants.Event.PRE_ADD_NEW_PASSWORD, userRecoveryData); + validateTenantDomain(userRecoveryData.getUser()); + + // Validate recovery step. + if (!RecoverySteps.UPDATE_PASSWORD.equals(userRecoveryData.getRecoveryStep())) { + throw Utils.handleClientException( + IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CODE, code); + } + return userRecoveryData; + } + /** * Update the new password of the user. * From 178ac2d1218318950a3292984bd49f8bdfa521c3 Mon Sep 17 00:00:00 2001 From: Rashmini Date: Tue, 19 Sep 2023 10:29:52 +0530 Subject: [PATCH 26/27] Update method comment --- .../recovery/password/NotificationPasswordRecoveryManager.java | 1 - 1 file changed, 1 deletion(-) 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 c087cc44ee..d539e10c0c 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 @@ -760,7 +760,6 @@ public User updateUserPassword(String code, String confirmationCode, String pass * have recovery flow ids, which were initiated before moving to the Recovery V2 API. * This shouldn't be used for any other purpose and should be kept for sometime. * - * @param userAccountRecoveryManager UserAccountRecoveryManager. * @param code Password Reset code. * @param confirmationCode Confirmation code. * @param password New password. From a51615594bf0639bf926e754d8fb08754fcafede Mon Sep 17 00:00:00 2001 From: Rashmini Date: Wed, 20 Sep 2023 07:24:21 +0530 Subject: [PATCH 27/27] Fix comments --- .../service/impl/password/PasswordRecoveryManagerImpl.java | 1 + .../recovery/password/NotificationPasswordRecoveryManager.java | 1 + 2 files changed, 2 insertions(+) diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java index 0d9094d735..da23fb6fc8 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/password/PasswordRecoveryManagerImpl.java @@ -877,6 +877,7 @@ private boolean isMinNoOfRecoveryQuestionsAnswered(String username, String tenan private PasswordResetCodeDTO validateConfirmationCode(UserAccountRecoveryManager userAccountRecoveryManager, String confirmationCode, String tenantDomain) throws IdentityRecoveryException { + UserRecoveryData userRecoveryData; try { userRecoveryData = userAccountRecoveryManager.getUserRecoveryData(confirmationCode, 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 d539e10c0c..8117a42f80 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 @@ -770,6 +770,7 @@ public User updateUserPassword(String code, String confirmationCode, String pass @Deprecated private UserRecoveryData validateUserRecoveryDataFromCode(String code, String confirmationCode, String password, Property[] properties) throws IdentityRecoveryException { + UserRecoveryData userRecoveryData; UserAccountRecoveryManager userAccountRecoveryManager = UserAccountRecoveryManager.getInstance(); try {