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 01191ed205..1bad8fecb7 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 @@ -121,6 +121,8 @@ public class IdentityRecoveryConstants { public static final String USER_ROLES_CLAIM = "http://wso2.org/claims/roles"; public static final String EMAIL_ADDRESS_CLAIM = "http://wso2.org/claims/emailaddress"; public static final String MOBILE_NUMBER_CLAIM = "http://wso2.org/claims/mobile"; + public static final String EMAIL_ADDRESSES_CLAIM = "http://wso2.org/claims/emailAddresses"; + public static final String VERIFIED_EMAIL_ADDRESSES_CLAIM = "http://wso2.org/claims/verifiedEmailAddresses"; public static final String DEFAULT_CHALLENGE_QUESTION_SEPARATOR = "!"; public static final String ACCOUNT_STATE_CLAIM_URI = "http://wso2.org/claims/identity/accountState"; public static final String PENDING_SELF_REGISTRATION = "PENDING_SR"; @@ -210,6 +212,9 @@ public class IdentityRecoveryConstants { public static final String ACCOUNT_STATUS_DISABLED = "password.recovery.failed.account.disabled"; public static final String IGNORE_IF_TEMPLATE_NOT_FOUND = "ignoreIfTemplateNotFound"; + public static final String SUPPORT_MULTIPLE_EMAILS_AND_MOBILE_NUMBERS_PER_USER = + "SupportMultipleEmailsAndMobileNumberPerUser.Enabled"; + private IdentityRecoveryConstants() { } @@ -440,9 +445,16 @@ public enum ErrorMessages { // UEV - User Email Verification. ERROR_CODE_VERIFICATION_EMAIL_NOT_FOUND("UEV-10001", "Email address not found for email verification"), + ERROR_CODE_EMAIL_VERIFICATION_NOT_ENABLED("UEV-10002", "Email verification is not enabled"), + ERROR_CODE_VERIFY_MULTIPLE_EMAILS("UEV-10003", "Unable to verify multiple email addresses " + + "simultaneously"), + ERROR_CODE_SUPPORT_MULTIPLE_EMAILS_NOT_ENABLED("UEV-10004", "Support for multiple email addresses " + + "per user is not enabled"), + ERROR_CODE_CANNOT_INITIATE_VERIFICATION_FOR_EMAIL_ADDRESS("UEV-10005", "Cannot initiate verification for email" + + " address claim as support for multiple email addresses per user is enabled."), + + INVALID_PASSWORD_RECOVERY_REQUEST("APR-10000", "Invalid Password Recovery Request"), - INVALID_PASSWORD_RECOVERY_REQUEST("APR-10000", "Invalid Password Recovery Request") - , // Idle User Account Identification related Error messages. ERROR_RETRIEVING_ASSOCIATED_USER("UMM-65005", "Error retrieving the associated user for the user: %s in the tenant %s."); @@ -858,7 +870,12 @@ public enum SkipEmailVerificationOnUpdateStates { /* State maintained to skip triggering an email verification, when the email address was updated by user during the Email OTP flow at the first login where the email address is not previously set. At the moment email address was already verified during the email OTP verification. So no need to verify it again. */ - SKIP_ON_EMAIL_OTP_FLOW + SKIP_ON_EMAIL_OTP_FLOW, + + /* State maintained to skip triggering an SMS OTP verification, when the email address to be updated is included + in the verifiedEmailAddresses claim, which has been already verified. + */ + SKIP_ON_ALREADY_VERIFIED_EMAIL_ADDRESSES } /** diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandler.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandler.java index 221cc523ea..1c5cb1ddae 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandler.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandler.java @@ -50,14 +50,17 @@ import org.wso2.carbon.user.core.UserStoreManager; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; +import java.util.stream.Collectors; -import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_VERIFICATION_EMAIL_NOT_FOUND; +import static org.wso2.carbon.identity.recovery.IdentityRecoveryConstants.ErrorMessages.*; public class UserEmailVerificationHandler extends AbstractEventHandler { @@ -89,6 +92,9 @@ public void handleEvent(Event event) throws IdentityEventException { Map claims = (Map) eventProperties.get(IdentityEventConstants.EventProperty .USER_CLAIMS); + boolean supportMultipleEmails = Boolean.parseBoolean(IdentityUtil + .getProperty(IdentityRecoveryConstants.SUPPORT_MULTIPLE_EMAILS_AND_MOBILE_NUMBERS_PER_USER)); + boolean enable = false; if (IdentityEventConstants.Event.PRE_ADD_USER.equals(eventName) || IdentityEventConstants.Event.POST_ADD_USER.equals(eventName)) { @@ -96,8 +102,7 @@ public void handleEvent(Event event) throws IdentityEventException { .ENABLE_EMAIL_VERIFICATION, user.getTenantDomain())); } else if (IdentityEventConstants.Event.PRE_SET_USER_CLAIMS.equals(eventName) || IdentityEventConstants.Event.POST_SET_USER_CLAIMS.equals(eventName)) { - enable = Boolean.parseBoolean(Utils.getConnectorConfig(IdentityRecoveryConstants.ConnectorConfig - .ENABLE_EMAIL_VERIFICATION_ON_UPDATE, user.getTenantDomain())); + enable = isEmailVerificationOnUpdateEnabled(user.getTenantDomain()); if (!enable) { /* We need to empty 'EMAIL_ADDRESS_PENDING_VALUE_CLAIM' because having a value in that claim implies a verification is pending. But verification is not enabled anymore. */ @@ -109,7 +114,21 @@ public void handleEvent(Event event) throws IdentityEventException { } invalidatePendingEmailVerification(user, userStoreManager, claims); } + + if (claims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)) { + throw new IdentityEventClientException(ERROR_CODE_EMAIL_VERIFICATION_NOT_ENABLED.getCode(), + ERROR_CODE_EMAIL_VERIFICATION_NOT_ENABLED.getMessage()); + } claims.remove(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM); + } else if (supportMultipleEmails) { + List verifiedEmails = Utils.getExistingClaimValue(userStoreManager, user, + IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + if (claims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM) && + !verifiedEmails.contains(claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM))) { + throw new IdentityEventClientException(ERROR_CODE_CANNOT_INITIATE_VERIFICATION_FOR_EMAIL_ADDRESS + .getCode(), ERROR_CODE_CANNOT_INITIATE_VERIFICATION_FOR_EMAIL_ADDRESS.getMessage()); + + } } } @@ -137,7 +156,8 @@ public void handleEvent(Event event) throws IdentityEventException { if (claims == null || claims.isEmpty()) { // Not required to handle in this handler. return; - } else if (claims.containsKey(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM) && Boolean.parseBoolean(claims.get(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM))) { + } else if (claims.containsKey(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM) && + Boolean.parseBoolean(claims.get(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM))) { if (!claims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM) || StringUtils.isBlank(claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM))) { throw new IdentityEventClientException(ERROR_CODE_VERIFICATION_EMAIL_NOT_FOUND.getCode(), @@ -148,8 +168,10 @@ public void handleEvent(Event event) throws IdentityEventException { claim.setValue(claims.get(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM)); Utils.setEmailVerifyTemporaryClaim(claim); claims.remove(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM); - Utils.publishRecoveryEvent(eventProperties, IdentityEventConstants.Event.PRE_VERIFY_EMAIL_CLAIM, null); - } else if (claims.containsKey(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM) && Boolean.parseBoolean(claims.get(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM))) { + Utils.publishRecoveryEvent(eventProperties, IdentityEventConstants.Event.PRE_VERIFY_EMAIL_CLAIM, + null); + } else if (claims.containsKey(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM) && + Boolean.parseBoolean(claims.get(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM))) { Claim claim = new Claim(); claim.setClaimUri(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM); claim.setValue(claims.get(IdentityRecoveryConstants.ASK_PASSWORD_CLAIM)); @@ -247,6 +269,12 @@ public void handleEvent(Event event) throws IdentityEventException { } } + private boolean isEmailVerificationOnUpdateEnabled(String tenantDomain) throws IdentityEventException { + + return Boolean.parseBoolean(Utils.getConnectorConfig(IdentityRecoveryConstants.ConnectorConfig + .ENABLE_EMAIL_VERIFICATION_ON_UPDATE, tenantDomain)); + } + @Override public void init(InitConfig configuration) throws IdentityRuntimeException { @@ -297,7 +325,8 @@ protected void initNotification(User user, Enum recoveryScenario, Enum recoveryS /** * This method sets a random value for the credentials, if the ask password flow is enabled. - * @param credentials Credentials object + * + * @param credentials Credentials object */ private void setRandomValueForCredentials(Object credentials) { @@ -469,19 +498,15 @@ protected User getUser(Map eventProperties, UserStoreManager userStoreManager) { private void preSetUserClaimsOnEmailUpdate(Map claims, UserStoreManager userStoreManager, User user) throws IdentityEventException { - if (IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates.SKIP_ON_CONFIRM.toString().equals - (Utils.getThreadLocalToSkipSendingEmailVerificationOnUpdate())) { - // Not required to handle in this handler. + if (MapUtils.isEmpty(claims)) { + Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants. + SkipEmailVerificationOnUpdateStates.SKIP_ON_INAPPLICABLE_CLAIMS.toString()); return; } - /* - Within the Email OTP flow, the email address is updated in the user profile after successfully verifying the - OTP. Therefore, the email is already verified & no need to verify it again. - */ - if (IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates.SKIP_ON_EMAIL_OTP_FLOW.toString().equals + if (IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates.SKIP_ON_CONFIRM.toString().equals (Utils.getThreadLocalToSkipSendingEmailVerificationOnUpdate())) { - invalidatePendingEmailVerification(user, userStoreManager, claims); + // Not required to handle in this handler. return; } @@ -496,49 +521,159 @@ private void preSetUserClaimsOnEmailUpdate(Map claims, UserStore return; } - String emailAddress = claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); + boolean supportMultipleEmails = Boolean.parseBoolean(IdentityUtil + .getProperty(IdentityRecoveryConstants.SUPPORT_MULTIPLE_EMAILS_AND_MOBILE_NUMBERS_PER_USER)); - if (StringUtils.isNotBlank(emailAddress)) { + String emailAddress = null; + List existingVerifiedEmailAddresses = Utils.getExistingClaimValue(userStoreManager, user, + IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); - String existingEmail; - String username = user.getUserName(); - try { - existingEmail = userStoreManager.getUserClaimValue(username, - IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM, null); - } catch (UserStoreException e) { - String error = String.format("Error occurred while retrieving existing email address for user: %s in " + - "domain : %s", username, user.getTenantDomain()); - throw new IdentityEventException(error, e); + // Handle email addresses and verified email addresses claims. + if (supportMultipleEmails) { + + List updatedVerifiedEmailAddresses = claims.containsKey(IdentityRecoveryConstants. + VERIFIED_EMAIL_ADDRESSES_CLAIM) ? getListOfEmailAddressesFromString(claims.get( + IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)) : existingVerifiedEmailAddresses; + + List existingAllEmailAddresses = Utils.getExistingClaimValue(userStoreManager, user, + IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + List updatedAllEmailAddresses = claims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM) ? + getListOfEmailAddressesFromString(claims.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)) : + existingAllEmailAddresses; + + // Find the verification pending email address and remove it from verified email addresses list in the payload. + if (updatedVerifiedEmailAddresses != null) { + emailAddress = getVerificationPendingEmailAddress(existingVerifiedEmailAddresses, + updatedVerifiedEmailAddresses); + updatedVerifiedEmailAddresses.remove(emailAddress); } - if (emailAddress.equals(existingEmail)) { + /* + Find the removed numbers from the existing email addresses list and remove them from the verified email + addresses list, as verified email addresses list should not contain email addresses that are not in the email + addresses list. + */ + if (!updatedAllEmailAddresses.isEmpty()) { + for (String existingEmailAddress : existingAllEmailAddresses) { + if (!updatedAllEmailAddresses.contains(existingEmailAddress) && + updatedVerifiedEmailAddresses != null) { + updatedVerifiedEmailAddresses.remove(existingEmailAddress); + } + } + } + claims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, StringUtils.join( + updatedVerifiedEmailAddresses, ",")); + claims.put(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, StringUtils.join( + updatedAllEmailAddresses, ",")); + } else { + // email addresses and verified email addresses should not be updated when support for multiple email + // addresses is disabled. + claims.remove(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + claims.remove(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + + emailAddress = claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); + } + if (emailAddress != null) { + + /* + Within the Email OTP flow, the email address is updated in the user profile after successfully verifying the + OTP. Therefore, the email is already verified & no need to verify it again. + */ + if (IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates.SKIP_ON_EMAIL_OTP_FLOW.toString().equals + (Utils.getThreadLocalToSkipSendingEmailVerificationOnUpdate())) { + invalidatePendingEmailVerification(user, userStoreManager, claims); + return; + } + + if (existingVerifiedEmailAddresses != null && existingVerifiedEmailAddresses.contains(emailAddress)) { if (log.isDebugEnabled()) { log.debug(String.format("The email address to be updated: %s is same as the existing email " + "address for user: %s in domain %s. Hence an email verification will not be " + - "triggered.", emailAddress, username, user.getTenantDomain())); + "triggered.", emailAddress, user.getUserName(), user.getTenantDomain())); } Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants - .SkipEmailVerificationOnUpdateStates.SKIP_ON_EXISTING_EMAIL.toString()); + .SkipEmailVerificationOnUpdateStates.SKIP_ON_ALREADY_VERIFIED_EMAIL_ADDRESSES.toString()); invalidatePendingEmailVerification(user, userStoreManager, claims); return; + } else { + String existingEmail; + existingEmail = getEmailClaimValue(user, userStoreManager); + + if (emailAddress.equals(existingEmail)) { + if (log.isDebugEnabled()) { + log.debug(String.format("The email address to be updated: %s is already verified and contains" + + " in the verified email addresses list. Hence an email verification will not be " + + "triggered.", emailAddress)); + } + Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants + .SkipEmailVerificationOnUpdateStates.SKIP_ON_EXISTING_EMAIL.toString()); + invalidatePendingEmailVerification(user, userStoreManager, claims); + + if (supportMultipleEmails) { + if (existingVerifiedEmailAddresses!= null && + !existingVerifiedEmailAddresses.contains(emailAddress)) { + existingVerifiedEmailAddresses.add(emailAddress); + claims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, StringUtils.join( + existingVerifiedEmailAddresses, ",")); + } + } + return; + } } - /* - When 'UseVerifyClaim' is enabled, the verification should happen only if the 'verifyEmail' temporary - claim exists as 'true' in the claim list. If 'UseVerifyClaim' is disabled, no need to check for - 'verifyEmail' claim. - */ + + /* + When 'UseVerifyClaim' is enabled, the verification should happen only if the 'verifyEmail' temporary + claim exists as 'true' in the claim list. If 'UseVerifyClaim' is disabled, no need to check for + 'verifyEmail' claim. + */ if (Utils.isUseVerifyClaimEnabled() && !isVerifyEmailClaimAvailable(claims)) { Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants .SkipEmailVerificationOnUpdateStates.SKIP_ON_INAPPLICABLE_CLAIMS.toString()); invalidatePendingEmailVerification(user, userStoreManager, claims); return; } - claims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM, emailAddress); claims.remove(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); - } else { - Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants - .SkipEmailVerificationOnUpdateStates.SKIP_ON_INAPPLICABLE_CLAIMS.toString()); } + claims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM, emailAddress); + } + + /** + * Get the email address that is pending verification. + * + * @param existingVerifiedEmailAddresses List of existing verified email addresses. + * @param updatedVerifiedEmailAddresses List of updated verified email addresses. + * @return email address that is pending verification. + */ + private String getVerificationPendingEmailAddress(List existingVerifiedEmailAddresses, + List updatedVerifiedEmailAddresses) throws + IdentityEventException { + + String emailAddress = null; + for (String verificationPendingEmailAddress : updatedVerifiedEmailAddresses) { + if (existingVerifiedEmailAddresses.stream().noneMatch(email -> + email.trim().equalsIgnoreCase(verificationPendingEmailAddress.trim()))) { + if (emailAddress == null) { + emailAddress = verificationPendingEmailAddress; + } else { + throw new IdentityEventClientException(IdentityRecoveryConstants.ErrorMessages. + ERROR_CODE_VERIFY_MULTIPLE_EMAILS.getCode(), IdentityRecoveryConstants. + ErrorMessages.ERROR_CODE_VERIFY_MULTIPLE_EMAILS.getMessage()); + } + } + } + return emailAddress; + } + + /** + * Convert comma separated list of email addresses to a list. + * + * @param emails Comma separated list of mobile numbers. + * @return List of email addresses. + */ + private List getListOfEmailAddressesFromString(String emails) { + + return emails != null ? new LinkedList<>(Arrays.asList(emails.split(","))).stream().map(String::trim) + .collect(Collectors.toList()) : new ArrayList<>(); } private void postSetUserClaimsOnEmailUpdate(User user, UserStoreManager userStoreManager) throws @@ -549,11 +684,13 @@ private void postSetUserClaimsOnEmailUpdate(User user, UserStoreManager userStor if (!IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates.SKIP_ON_CONFIRM.toString().equals (skipEmailVerificationOnUpdateState) && !IdentityRecoveryConstants. SkipEmailVerificationOnUpdateStates.SKIP_ON_EXISTING_EMAIL.toString().equals - (skipEmailVerificationOnUpdateState) && !IdentityRecoveryConstants + (skipEmailVerificationOnUpdateState) && !IdentityRecoveryConstants .SkipEmailVerificationOnUpdateStates.SKIP_ON_INAPPLICABLE_CLAIMS.toString().equals (skipEmailVerificationOnUpdateState) && !IdentityRecoveryConstants .SkipEmailVerificationOnUpdateStates.SKIP_ON_EMAIL_OTP_FLOW.toString().equals - (skipEmailVerificationOnUpdateState)) { + (skipEmailVerificationOnUpdateState) && !IdentityRecoveryConstants. + SkipEmailVerificationOnUpdateStates.SKIP_ON_ALREADY_VERIFIED_EMAIL_ADDRESSES.toString().equals( + skipEmailVerificationOnUpdateState)) { String pendingVerificationEmailClaimValue = getPendingVerificationEmailValue(userStoreManager, user); @@ -599,13 +736,13 @@ private String getPendingVerificationEmailValue(UserStoreManager userStoreManage /** * Invalidate pending email verification. * - * @param user User. - * @param userStoreManager User store manager. - * @param claims User claims. + * @param user User. + * @param userStoreManager User store manager. + * @param claims User claims. * @throws IdentityEventException */ private void invalidatePendingEmailVerification(User user, UserStoreManager userStoreManager, - Map claims ) throws IdentityEventException { + Map claims) throws IdentityEventException { if (StringUtils.isNotBlank(getPendingVerificationEmailValue(userStoreManager, user))) { claims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM, StringUtils.EMPTY); @@ -623,7 +760,7 @@ private void invalidatePendingEmailVerification(User user, UserStoreManager user /** * Check if the claims contain the temporary claim 'verifyEmail' and it is set to true. * - * @param claims User claims. + * @param claims User claims. * @return True if 'verifyEmail' claim is available as true, false otherwise. */ private boolean isVerifyEmailClaimAvailable(Map claims) { @@ -645,7 +782,7 @@ private boolean isVerifyEmailClaimAvailable(Map claims) { * @throws IdentityEventException IdentityEventException. */ private void sendNotificationToExistingEmailOnEmailUpdate(User user, UserStoreManager userStoreManager, - String newEmailAddress, String templateType) throws IdentityEventException { + String newEmailAddress, String templateType) throws IdentityEventException { boolean enable = Boolean.parseBoolean(Utils.getConnectorConfig(IdentityRecoveryConstants.ConnectorConfig .ENABLE_NOTIFICATION_ON_EMAIL_UPDATE, user.getTenantDomain())); if (!enable) { diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManager.java index ec1825784a..44b1bed291 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManager.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManager.java @@ -101,13 +101,7 @@ import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Optional; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -759,12 +753,39 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi HashMap userClaims = getClaimsListToUpdate(user, verifiedChannelType, externallyVerifiedClaim, recoveryData.getRecoveryScenario().toString()); + boolean supportMultipleEmailsAndMobileNumbers = Boolean.parseBoolean(IdentityUtil + .getProperty(IdentityRecoveryConstants.SUPPORT_MULTIPLE_EMAILS_AND_MOBILE_NUMBERS_PER_USER)); + if (RecoverySteps.VERIFY_EMAIL.equals(recoveryData.getRecoveryStep())) { String pendingEmailClaimValue = recoveryData.getRemainingSetIds(); if (StringUtils.isNotBlank(pendingEmailClaimValue)) { eventProperties.put(IdentityEventConstants.EventProperty.VERIFIED_EMAIL, pendingEmailClaimValue); userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM, StringUtils.EMPTY); - userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM, pendingEmailClaimValue); //todo?? + if (supportMultipleEmailsAndMobileNumbers) { + try { + List verifiedEmails = Utils.getExistingClaimValue( + (org.wso2.carbon.user.core.UserStoreManager) eventProperties.get( + IdentityEventConstants.EventProperty.USER_STORE_MANAGER), user, + IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + verifiedEmails.add(pendingEmailClaimValue); + userClaims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, StringUtils.join( + verifiedEmails, ",")); + + List allEmails = Utils.getExistingClaimValue( + (org.wso2.carbon.user.core.UserStoreManager) eventProperties.get( + IdentityEventConstants.EventProperty.USER_STORE_MANAGER), user, + IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + if (!allEmails.contains(pendingEmailClaimValue)) { + allEmails.add(pendingEmailClaimValue); + userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, StringUtils.join( + allEmails, ",")) ; + } + } catch (IdentityEventException e) { + log.error("Error occurred while obtaining claim for the user "); + } + } else { + userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM, pendingEmailClaimValue); + } // Todo passes when email address is properly set here. Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate(IdentityRecoveryConstants .SkipEmailVerificationOnUpdateStates.SKIP_ON_CONFIRM.toString()); 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 9948fcf49e..72fa75d74d 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 @@ -83,7 +83,9 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; @@ -1676,4 +1678,28 @@ private static String convertFailureReasonsToString(List getExistingClaimValue(org.wso2.carbon.user.core.UserStoreManager userStoreManager, + User user, String claimURI) throws IdentityEventException { + + List existingClaimValue; + try { + existingClaimValue = userStoreManager.getUserClaimValue(user.getUserName(), claimURI, null) != null ? + new LinkedList<>(Arrays.asList(userStoreManager.getUserClaimValue(user.getUserName(), claimURI, + null).split(","))) : new ArrayList<>(); + } catch (org.wso2.carbon.user.core.UserStoreException e) { + throw new IdentityEventException("Error occurred while retrieving claim value of " + claimURI + + " for user: " + user.toFullQualifiedUsername(), e); + } + return existingClaimValue; + } + }