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..b3324cf8d4 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 @@ -271,21 +271,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 +862,47 @@ 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..44844a2c9f 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 @@ -49,6 +49,7 @@ import org.wso2.carbon.identity.recovery.RecoveryScenarios; import org.wso2.carbon.identity.recovery.RecoverySteps; import org.wso2.carbon.identity.recovery.bean.NotificationResponseBean; +import org.wso2.carbon.identity.recovery.dto.PasswordResetCodeDTO; 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; @@ -687,20 +688,10 @@ 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(userAccountRecoveryManager, code, confirmationCode, + password, properties); } // Get the notification channel. @@ -766,6 +757,51 @@ 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(UserAccountRecoveryManager userAccountRecoveryManager, String code, + String confirmationCode, String password, Property[] properties) + throws IdentityRecoveryException { + UserRecoveryData userRecoveryData; + 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. *