diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandler.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandler.java index fc33313e8..fe557c9a6 100644 --- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandler.java +++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandler.java @@ -96,7 +96,8 @@ public void handleEvent(Event event) throws IdentityEventException { claims = new HashMap<>(); } - boolean supportMultipleMobileNumbers = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleMobileNumbers = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); boolean enable = isMobileVerificationOnUpdateEnabled(user.getTenantDomain()); @@ -121,30 +122,6 @@ public void handleEvent(Event event) throws IdentityEventException { } claims.remove(IdentityRecoveryConstants.VERIFY_MOBILE_CLAIM); claims.remove(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); - - if (supportMultipleMobileNumbers) { - if (claims.containsKey(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM) && - !claims.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM).isEmpty()) { - String mobileNumber = claims.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM); - List exisitingAllNumbersList = Utils.getMultiValuedClaim(userStoreManager, user, - IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); - List updatedAllNumbersList = - claims.containsKey(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM) - ? getListOfMobileNumbersFromString( - claims.get(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM)) - : exisitingAllNumbersList; - if (!updatedAllNumbersList.contains(mobileNumber)) { - updatedAllNumbersList.add(mobileNumber); - claims.put(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM, - String.join(FrameworkUtils.getMultiAttributeSeparator(), updatedAllNumbersList)); - } - } - } else { - // Multiple mobile numbers per user support is disabled. - log.debug("Supporting multiple mobile numbers per user is disabled."); - claims.remove(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); - claims.remove(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); - } return; } @@ -326,7 +303,15 @@ private void preSetUserClaimOnMobileNumberUpdate(Map claims, Use Utils.unsetThreadLocalToSkipSendingSmsOtpVerificationOnUpdate(); } - boolean supportMultipleMobileNumbers = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleMobileNumbers = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); + + // Update multiple mobile numbers only if they’re in the claims map. + // This avoids issues with updating the primary mobile number due to user store limitations on multiple + // mobile numbers. + boolean shouldUpdateMultiMobilesRelatedClaims = + claims.containsKey(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM) || + claims.containsKey(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); String multiAttributeSeparator = FrameworkUtils.getMultiAttributeSeparator(); String mobileNumber = claims.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM); @@ -364,10 +349,12 @@ private void preSetUserClaimOnMobileNumberUpdate(Map claims, Use updatedVerifiedNumbersList.removeIf(number -> !updatedAllNumbersList.contains(number)); } - claims.put(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM, - String.join(multiAttributeSeparator, updatedAllNumbersList)); - claims.put(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, - String.join(multiAttributeSeparator, updatedVerifiedNumbersList)); + if (shouldUpdateMultiMobilesRelatedClaims) { + claims.put(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM, + String.join(multiAttributeSeparator, updatedAllNumbersList)); + claims.put(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, + String.join(multiAttributeSeparator, updatedVerifiedNumbersList)); + } } else { updatedAllNumbersList = new ArrayList<>(); claims.remove(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); @@ -410,7 +397,7 @@ private void preSetUserClaimOnMobileNumberUpdate(Map claims, Use .SkipMobileNumberVerificationOnUpdateStates.SKIP_ON_EXISTING_MOBILE_NUM.toString()); invalidatePendingMobileVerification(user, userStoreManager, claims); - if (supportMultipleMobileNumbers) { + if (supportMultipleMobileNumbers && shouldUpdateMultiMobilesRelatedClaims) { if (!updatedVerifiedNumbersList.contains(existingMobileNumber)) { updatedVerifiedNumbersList.add(existingMobileNumber); claims.put(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, 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 8555927cf..921de5c69 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 @@ -99,7 +99,8 @@ public void handleEvent(Event event) throws IdentityEventException { claims = new HashMap<>(); } - boolean supportMultipleEmails = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleEmails = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); boolean enable = false; @@ -131,30 +132,7 @@ public void handleEvent(Event event) throws IdentityEventException { } invalidatePendingEmailVerification(user, userStoreManager, claims); } - - if (supportMultipleEmails) { - // Drop the verified email addresses claim as verification on update is not enabled. - claims.remove(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); - - if (claims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM) && - !claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM).isEmpty()) { - - String email = claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); - List existingAllEmailAddresses = Utils.getMultiValuedClaim(userStoreManager, user, - IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); - - List updatedAllEmailAddresses = claims.containsKey( - IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM) - ? getListOfEmailAddressesFromString( - claims.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)) - : existingAllEmailAddresses; - if (!updatedAllEmailAddresses.contains(email)) { - updatedAllEmailAddresses.add(email); - claims.put(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, - String.join(FrameworkUtils.getMultiAttributeSeparator(), updatedAllEmailAddresses)); - } - } - } + claims.remove(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); claims.remove(IdentityRecoveryConstants.VERIFY_EMAIL_CLIAM); } } @@ -568,7 +546,15 @@ private void preSetUserClaimsOnEmailUpdate(Map claims, UserStore Utils.unsetThreadLocalToSkipSendingEmailVerificationOnUpdate(); } - boolean supportMultipleEmails = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleEmails = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); + // Update multiple email address related claims only if they’re in the claims map. + // This avoids issues with updating the primary email address due to user store limitations on multiple + // email addresses. + boolean shouldUpdateMultiMobilesRelatedClaims = + claims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM) || + claims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + String multiAttributeSeparator = FrameworkUtils.getMultiAttributeSeparator(); String emailAddress = claims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); @@ -611,10 +597,12 @@ private void preSetUserClaimsOnEmailUpdate(Map claims, UserStore final List tempUpdatedAllEmailAddresses = new ArrayList<>(updatedAllEmailAddresses); updatedVerifiedEmailAddresses.removeIf(number -> !tempUpdatedAllEmailAddresses.contains(number)); - claims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, - StringUtils.join(updatedVerifiedEmailAddresses, multiAttributeSeparator)); - claims.put(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, - StringUtils.join(updatedAllEmailAddresses, multiAttributeSeparator)); + if (shouldUpdateMultiMobilesRelatedClaims) { + claims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, + StringUtils.join(updatedVerifiedEmailAddresses, multiAttributeSeparator)); + claims.put(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, + StringUtils.join(updatedAllEmailAddresses, multiAttributeSeparator)); + } } else { /* email addresses and verified email addresses should not be updated when support for multiple email @@ -655,7 +643,7 @@ private void preSetUserClaimsOnEmailUpdate(Map claims, UserStore .SkipEmailVerificationOnUpdateStates.SKIP_ON_EXISTING_EMAIL.toString()); invalidatePendingEmailVerification(user, userStoreManager, claims); - if (supportMultipleEmails) { + if (supportMultipleEmails && shouldUpdateMultiMobilesRelatedClaims) { if (!updatedVerifiedEmailAddresses.contains(existingEmail)) { updatedVerifiedEmailAddresses.add(existingEmail); claims.put(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, 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 ca4e14e0d..6577ac269 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 @@ -768,7 +768,8 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi HashMap userClaims = getClaimsListToUpdate(user, verifiedChannelType, externallyVerifiedClaim, recoveryData.getRecoveryScenario().toString()); - boolean supportMultipleEmailsAndMobileNumbers = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleEmailsAndMobileNumbers = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); String multiAttributeSeparator = FrameworkUtils.getMultiAttributeSeparator(); if (RecoverySteps.VERIFY_EMAIL.equals(recoveryData.getRecoveryStep())) { @@ -776,7 +777,10 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi if (StringUtils.isNotBlank(pendingEmailClaimValue)) { eventProperties.put(IdentityEventConstants.EventProperty.VERIFIED_EMAIL, pendingEmailClaimValue); userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM, StringUtils.EMPTY); - if (supportMultipleEmailsAndMobileNumbers) { + // Only update verified email addresses claim if the recovery scenario is + // EMAIL_VERIFICATION_ON_VERIFIED_LIST_UPDATE. + if (RecoveryScenarios.EMAIL_VERIFICATION_ON_VERIFIED_LIST_UPDATE.equals( + recoveryData.getRecoveryScenario()) && supportMultipleEmailsAndMobileNumbers) { try { List verifiedEmails = Utils.getMultiValuedClaim(userStoreManager, user, IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); @@ -796,9 +800,7 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi throw new IdentityRecoveryServerException("Error occurred while obtaining existing claim " + "value for the user : " + user.getUserName(), e); } - } - if (!RecoveryScenarios.EMAIL_VERIFICATION_ON_VERIFIED_LIST_UPDATE - .equals(recoveryData.getRecoveryScenario())) { + } else { userClaims.put(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM, pendingEmailClaimValue); } // Todo passes when email address is properly set here. @@ -809,7 +811,8 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi if (RecoverySteps.VERIFY_MOBILE_NUMBER.equals(recoveryData.getRecoveryStep())) { String pendingMobileClaimValue = recoveryData.getRemainingSetIds(); if (StringUtils.isNotBlank(pendingMobileClaimValue)) { - if (supportMultipleEmailsAndMobileNumbers) { + if (RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE.equals( + recoveryData.getRecoveryScenario()) && supportMultipleEmailsAndMobileNumbers) { try { List existingVerifiedMobileNumbersList = Utils.getMultiValuedClaim(userStoreManager, user, IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); @@ -835,9 +838,7 @@ private UserRecoveryData validateSelfRegistrationCode(String code, String verifi throw new IdentityRecoveryServerException("Error occurred while obtaining existing claim " + "value for the user : " + user.getUserName(), e); } - } - if (!RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE - .equals(recoveryData.getRecoveryScenario())) { + } else { userClaims.put(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM, pendingMobileClaimValue); } userClaims.put(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM, StringUtils.EMPTY); @@ -990,7 +991,8 @@ public void confirmVerificationCodeMe(String code, Map propertie UserStoreManager userStoreManager = getUserStoreManager(user); HashMap userClaims = new HashMap<>(); - boolean supportMultipleEmailsAndMobileNumbers = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); + boolean supportMultipleEmailsAndMobileNumbers = + Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(user.getTenantDomain(), user.getUserStoreDomain()); String pendingMobileNumberClaimValue = recoveryData.getRemainingSetIds(); if (StringUtils.isNotBlank(pendingMobileNumberClaimValue)) { @@ -998,7 +1000,8 @@ public void confirmVerificationCodeMe(String code, Map propertie Verifying whether user is trying to add a mobile number to http://wso2.org/claims/verifedMobileNumbers claim. */ - if (supportMultipleEmailsAndMobileNumbers) { + if (RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE.equals( + recoveryData.getRecoveryScenario()) && supportMultipleEmailsAndMobileNumbers) { try { String multiAttributeSeparator = FrameworkUtils.getMultiAttributeSeparator(); List existingVerifiedMobileNumbersList = Utils.getMultiValuedClaim(userStoreManager, @@ -1025,9 +1028,7 @@ public void confirmVerificationCodeMe(String code, Map propertie throw new IdentityRecoveryServerException("Error occurred while obtaining existing claim " + "value for the user : " + user.getUserName(), e); } - } - if (!RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE - .equals(recoveryData.getRecoveryScenario())) { + } else { userClaims.put(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM, pendingMobileNumberClaimValue); } userClaims.put(NotificationChannels.SMS_CHANNEL.getVerifiedClaimUrl(), Boolean.TRUE.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 07cd7ae89..1a3e977fb 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 @@ -36,6 +36,9 @@ import org.wso2.carbon.identity.auth.attribute.handler.model.ValidationResult; import org.wso2.carbon.identity.base.IdentityException; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; +import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.event.IdentityEventConstants; @@ -1393,14 +1396,72 @@ public static boolean isUseVerifyClaimEnabled() { } /** - * Check whether the supporting multiple email addresses and mobile numbers per user is enabled. + * Check whether the supporting multiple email addresses and mobile numbers per user feature is enabled. * + * @param tenantDomain Tenant domain. + * @param userStoreDomain User store domain. * @return True if the config is set to true, false otherwise. */ - public static boolean isMultiEmailsAndMobileNumbersPerUserEnabled() { + public static boolean isMultiEmailsAndMobileNumbersPerUserEnabled(String tenantDomain, String userStoreDomain) { - return Boolean.parseBoolean(IdentityUtil.getProperty( - IdentityRecoveryConstants.ConnectorConfig.SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER)); + if (!Boolean.parseBoolean(IdentityUtil.getProperty( + IdentityRecoveryConstants.ConnectorConfig.SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER))) { + return false; + } + + if (StringUtils.isBlank(tenantDomain) || StringUtils.isBlank(userStoreDomain)) { + return false; + } + + try { + List localClaims = + IdentityRecoveryServiceDataHolder.getInstance().getClaimMetadataManagementService() + .getLocalClaims(tenantDomain); + + List requiredClaims = Arrays.asList( + IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, + IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM, + IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, + IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + + // Check if all required claims are valid for the user store. + return requiredClaims.stream().allMatch(claimUri -> + isClaimSupportedForUserStore(localClaims, claimUri, userStoreDomain)); + } catch (ClaimMetadataException e) { + log.error("Error while retrieving multiple emails and mobiles config.", e); + return false; + } + } + + /** + * Check if a claim is supported and not excluded for a specific user store. + * + * @param localClaims List of local claims. + * @param claimUri URI of the claim to check. + * @param userStoreDomain User store domain to validate against. + * @return True if claim is supported and not excluded. + */ + private static boolean isClaimSupportedForUserStore(List localClaims, String claimUri, + String userStoreDomain) { + + return localClaims.stream() + .filter(claim -> claimUri.equals(claim.getClaimURI())) + .anyMatch(claim -> { + Map properties = claim.getClaimProperties(); + + // Check if claim is supported by default. + boolean isSupported = Boolean.parseBoolean( + properties.getOrDefault(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY, + Boolean.FALSE.toString())); + + // Check if user store is not in excluded list. + String excludedUserStoreDomains = properties.get(ClaimConstants.EXCLUDED_USER_STORES_PROPERTY); + boolean isNotExcluded = StringUtils.isBlank(excludedUserStoreDomains) || + !Arrays.asList(excludedUserStoreDomains.toUpperCase().split(",")) + .contains(userStoreDomain.toUpperCase()); + + return isSupported && isNotExcluded; + }); } /** diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandlerTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandlerTest.java index 3d407f715..10cad17af 100644 --- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandlerTest.java +++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/MobileNumberVerificationHandlerTest.java @@ -211,40 +211,23 @@ public void testHandleEventVerificationDisabledMultiAttributeEnabled() List allMobileNumbers = new ArrayList<>(Arrays.asList(EXISTING_NUMBER_1, EXISTING_NUMBER_2)); mockExistingNumbersList(allMobileNumbers); - // Expectation: New mobile number should be added to the mobile numbers claim. + // Expectation: New mobile number should be added to the mobile number claim. mobileNumberVerificationHandler.handleEvent(event); Map userClaims = getUserClaimsFromEvent(event); Assert.assertTrue(StringUtils.contains( - userClaims.get(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM), NEW_MOBILE_NUMBER)); + userClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM), NEW_MOBILE_NUMBER)); - // Case 2: Send mobile numbers claim with mobile number claim. + // Case 2: Updated verified mobile numbers list. Event event2 = createEvent(IdentityEventConstants.Event.PRE_SET_USER_CLAIM, null, - null, EXISTING_NUMBER_1, NEW_MOBILE_NUMBER); - mockVerificationPendingMobileNumber(); - mockUtilMethods(false, true, false); - - // New primary mobile number is not included in existing all mobile numbers list. - mockExistingNumbersList(null); - - // Expectation: New mobile number should be added to the mobile numbers claim. - mobileNumberVerificationHandler.handleEvent(event2); - Map userClaims2 = getUserClaimsFromEvent(event2); - Assert.assertTrue(StringUtils.contains( - userClaims2.get(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM), NEW_MOBILE_NUMBER)); - Assert.assertTrue(StringUtils.contains( - userClaims2.get(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM), EXISTING_NUMBER_1)); - - // Case 3: Updated verified mobile numbers list. - Event event3 = createEvent(IdentityEventConstants.Event.PRE_SET_USER_CLAIM, null, NEW_MOBILE_NUMBER, null, null); mockVerificationPendingMobileNumber(); // Expectation: Verified mobile number claim should be removed from user claims. - mobileNumberVerificationHandler.handleEvent(event3); - verify(userRecoveryDataStore, times(3)).invalidate(any(), + mobileNumberVerificationHandler.handleEvent(event2); + verify(userRecoveryDataStore, times(2)).invalidate(any(), eq(RecoveryScenarios.MOBILE_VERIFICATION_ON_UPDATE), eq(RecoverySteps.VERIFY_MOBILE_NUMBER)); - Map userClaims3 = getUserClaimsFromEvent(event3); + Map userClaims3 = getUserClaimsFromEvent(event2); Assert.assertFalse(userClaims3.containsKey(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM)); } @@ -383,6 +366,21 @@ public void testUpdatePrimaryMobileNotInVerifiedList() throws Exception { Map userClaims = getUserClaimsFromEvent(event); Assert.assertEquals(userClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM), NEW_MOBILE_NUMBER); + // Multiple mobile numbers related claims should only be updated when they are present in the request. + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM)); + + // Case 2: Send mobileNumbers claim in the request. + Event event2 = createEvent(IdentityEventConstants.Event.PRE_SET_USER_CLAIMS, null, + null, EXISTING_NUMBER_1, NEW_MOBILE_NUMBER); + mockExistingVerifiedNumbersList(new ArrayList<>(Arrays.asList(EXISTING_NUMBER_1))); + mockExistingNumbersList(new ArrayList<>(Arrays.asList(EXISTING_NUMBER_1))); + mockVerificationPendingMobileNumber(); + + mobileNumberVerificationHandler.handleEvent(event2); + Map userClaims2 = getUserClaimsFromEvent(event2); + Assert.assertEquals(userClaims2.get(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM), + NEW_MOBILE_NUMBER); + Assert.assertTrue(userClaims2.containsKey(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM)); } @Test(description = "Verification enabled, Multi-attribute enabled, Update primary mobile in verified list") @@ -575,8 +573,8 @@ private static Map getUserClaimsFromEvent(Event event2) { private void mockUtilMethods(boolean mobileVerificationEnabled, boolean multiAttributeEnabled, boolean useVerifyClaimEnabled) { - mockedUtils.when( - Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(multiAttributeEnabled); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(multiAttributeEnabled); mockedUtils.when(Utils::isUseVerifyClaimEnabled).thenReturn(useVerifyClaimEnabled); mockedUtils.when(() -> Utils.getConnectorConfig( eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_NUM_VERIFICATION_ON_UPDATE), diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandlerTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandlerTest.java index aaf196c91..2dcbb53ce 100644 --- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandlerTest.java +++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/handler/UserEmailVerificationHandlerTest.java @@ -218,8 +218,9 @@ public void testHandleEventPreSetUserClaimsVerificationDisabledMultiEnabled() userEmailVerificationHandler.handleEvent(event); Map userClaims = getUserClaimsFromEvent(event); - Assert.assertTrue(StringUtils.contains( - userClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM), NEW_EMAIL)); + Assert.assertEquals(userClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM), NEW_EMAIL); + // Multiple email addresses related claims should only be updated when they are present in the request. + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); // Case 2 : Send email addresses claim with event. String emailsClaim = String.format("%s,%s", EXISTING_EMAIL_1, EXISTING_EMAIL_2); @@ -231,8 +232,8 @@ public void testHandleEventPreSetUserClaimsVerificationDisabledMultiEnabled() userEmailVerificationHandler.handleEvent(event2); Map userClaims2 = getUserClaimsFromEvent(event2); - Assert.assertTrue(StringUtils.contains( - userClaims2.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM), NEW_EMAIL)); + Assert.assertEquals(userClaims2.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM), NEW_EMAIL); + Assert.assertEquals(userClaims2.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM), emailsClaim); } @Test(description = "Verification - Enabled, Multi attribute - Disabled") @@ -306,7 +307,6 @@ public void testHandleEventPreSetUserClaimsVerificationEnabledMultiEnabledC1() /* Try to change the primary email, new Email is not in the existing verified email address list. - Expected: IdentityEventClientException should be thrown. */ Event event = createEvent(IdentityEventConstants.Event.PRE_SET_USER_CLAIMS, IdentityRecoveryConstants.FALSE, null, null, NEW_EMAIL); @@ -322,6 +322,8 @@ public void testHandleEventPreSetUserClaimsVerificationEnabledMultiEnabledC1() userEmailVerificationHandler.handleEvent(event); Map userClaims = getUserClaimsFromEvent(event); Assert.assertEquals(userClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM), NEW_EMAIL); + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); } @Test(description = "Verification - Enabled, Multi attribute - Enabled, Change primary email which is already " + @@ -347,6 +349,9 @@ public void testHandleEventPreSetUserClaimsVerificationEnabledMultiEnabledC2() t mockedUtils.verify(() -> Utils.setThreadLocalToSkipSendingEmailVerificationOnUpdate( eq(IdentityRecoveryConstants.SkipEmailVerificationOnUpdateStates .SKIP_ON_ALREADY_VERIFIED_EMAIL_ADDRESSES.toString()))); + Map userClaims = getUserClaimsFromEvent(event); + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + Assert.assertFalse(userClaims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); } @Test(description = "Verification - Enabled, Multi attribute - Enabled, Update verified list with new email") @@ -762,8 +767,8 @@ private void mockPendingVerificationEmail(String pendingEmail) throws UserStoreE private void mockUtilMethods(boolean emailVerificationEnabled, boolean multiAttributeEnabled, boolean userVerifyClaimEnabled, boolean notificationOnEmailUpdate) { - mockedUtils.when( - Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(multiAttributeEnabled); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(multiAttributeEnabled); mockedUtils.when(Utils::isUseVerifyClaimEnabled).thenReturn(userVerifyClaimEnabled); mockGetConnectorConfig(IdentityRecoveryConstants.ConnectorConfig.ENABLE_EMAIL_VERIFICATION_ON_UPDATE, emailVerificationEnabled); diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManagerTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManagerTest.java index 8005c2aae..9d50cf949 100644 --- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManagerTest.java +++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/signup/UserSelfRegistrationManagerTest.java @@ -49,6 +49,10 @@ import org.wso2.carbon.identity.auth.attribute.handler.exception.AuthAttributeHandlerException; import org.wso2.carbon.identity.auth.attribute.handler.model.ValidationResult; import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.claim.metadata.mgt.ClaimMetadataManagementService; +import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; import org.wso2.carbon.identity.common.testng.WithCarbonHome; import org.wso2.carbon.identity.consent.mgt.services.ConsentUtilityService; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; @@ -97,9 +101,11 @@ import org.wso2.carbon.utils.multitenancy.MultitenantUtils; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import static org.mockito.ArgumentMatchers.any; @@ -111,6 +117,7 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -627,7 +634,7 @@ public void testAttributeVerificationFailures(String scenario, Property[] proper @Test public void testConfirmVerificationCodeMe() - throws IdentityRecoveryException, UserStoreException { + throws IdentityRecoveryException, UserStoreException, ClaimMetadataException { // Case 1: Multiple email and mobile per user is enabled. String verificationPendingMobileNumber = "0700000000"; @@ -658,7 +665,6 @@ public void testConfirmVerificationCodeMe() assertEquals(updatedVerificationPendingMobile, StringUtils.EMPTY); assertEquals(updatedPrimaryMobile, verificationPendingMobileNumber); - assertTrue(StringUtils.contains(updatedVerifiedMobileNumbers, verificationPendingMobileNumber)); // Case 2: Multiple email and mobile per user is disabled. mockMultiAttributeEnabled(false); @@ -691,7 +697,7 @@ public void testConfirmVerificationCodeMe() @Test public void testConfirmVerificationCodeMeVerificationOnVerifiedListUpdate() - throws IdentityRecoveryException, UserStoreException { + throws IdentityRecoveryException, UserStoreException, ClaimMetadataException { // Case 1: Recovery Scenario - MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE. String verificationPendingMobileNumber = "0700000000"; @@ -705,8 +711,7 @@ public void testConfirmVerificationCodeMeVerificationOnVerifiedListUpdate() when(privilegedCarbonContext.getTenantDomain()).thenReturn(TEST_TENANT_DOMAIN_NAME); mockMultiAttributeEnabled(true); - mockGetUserClaimValue(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM, verificationPendingMobileNumber); - mockGetUserClaimValue(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, verificationPendingMobileNumber); + mockGetUserClaimValue(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, ""); userSelfRegistrationManager.confirmVerificationCodeMe(TEST_CODE, new HashMap<>()); @@ -720,29 +725,45 @@ public void testConfirmVerificationCodeMeVerificationOnVerifiedListUpdate() assertEquals(updatedVerificationPendingMobile, StringUtils.EMPTY); reset(userStoreManager); + // Case 2: Multiple email and mobile per user is disabled. + mockMultiAttributeEnabled(false); + ArgumentCaptor> claimsCaptor2 = ArgumentCaptor.forClass(Map.class); + + userSelfRegistrationManager.confirmVerificationCodeMe(TEST_CODE, new HashMap<>()); + + verify(userStoreManager, atLeastOnce()).setUserClaimValues(anyString(), claimsCaptor2.capture(), isNull()); + Map capturedClaims2 = claimsCaptor2.getValue(); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM), + StringUtils.EMPTY); + assertEquals(capturedClaims2.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM), + verificationPendingMobileNumber); + assertFalse(capturedClaims2.containsKey(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM)); - // Case 2: When pending mobile number claim value is null. + reset(userStoreManager); + reset(userRecoveryDataStore); + // Case 3: When pending mobile number claim value is null. UserRecoveryData userRecoveryData2 = new UserRecoveryData(user, TEST_RECOVERY_DATA_STORE_SECRET, RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE, RecoverySteps.VERIFY_MOBILE_NUMBER); + userRecoveryData2.setRemainingSetIds(null); when(userRecoveryDataStore.load(eq(TEST_CODE))).thenReturn(userRecoveryData2); userSelfRegistrationManager.confirmVerificationCodeMe(TEST_CODE, new HashMap<>()); - ArgumentCaptor> claimsCaptor2 = ArgumentCaptor.forClass(Map.class); - verify(userStoreManager).setUserClaimValues(anyString(), claimsCaptor2.capture(), isNull()); - assertFalse(claimsCaptor2.getValue().containsKey(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM)); - assertFalse(claimsCaptor2.getValue().containsKey(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM)); + ArgumentCaptor> claimsCaptor3 = ArgumentCaptor.forClass(Map.class); + verify(userStoreManager).setUserClaimValues(anyString(), claimsCaptor3.capture(), isNull()); + assertFalse(claimsCaptor3.getValue().containsKey(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM)); + assertFalse(claimsCaptor3.getValue().containsKey(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM)); } @Test(expectedExceptions = IdentityRecoveryServerException.class) public void testConfirmVerificationCodeMeUserStoreException() - throws IdentityRecoveryException, UserStoreException { + throws IdentityRecoveryException, UserStoreException, ClaimMetadataException { // Case 3: Throws user store exception while getting user claim values. String verificationPendingMobileNumber = "0700000000"; User user = getUser(); UserRecoveryData userRecoveryData = new UserRecoveryData(user, TEST_RECOVERY_DATA_STORE_SECRET, - RecoveryScenarios.MOBILE_VERIFICATION_ON_UPDATE, RecoverySteps.VERIFY_MOBILE_NUMBER); + RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE, RecoverySteps.VERIFY_MOBILE_NUMBER); userRecoveryData.setRemainingSetIds(verificationPendingMobileNumber); when(userRecoveryDataStore.load(eq(TEST_CODE))).thenReturn(userRecoveryData); @@ -757,14 +778,15 @@ public void testConfirmVerificationCodeMeUserStoreException() } @Test - public void testGetConfirmedSelfRegisteredUserVerifyEmail() - throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException { + public void testGetConfirmedSelfRegisteredUserEmailVerificationOnUpdate() + throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException, ClaimMetadataException { String verifiedChannelType = NotificationChannels.EMAIL_CHANNEL.getChannelType(); String verifiedChannelClaim = "http://wso2.org/claims/emailaddress"; String verificationPendingEmail = "pasindu@gmail.com"; Map metaProperties = new HashMap<>(); + // Case 1: Multiple email and mobile per user is enabled - EMAIL_VERIFICATION_ON_UPDATE. User user = getUser(); UserRecoveryData userRecoveryData = new UserRecoveryData(user, TEST_RECOVERY_DATA_STORE_SECRET, RecoveryScenarios.EMAIL_VERIFICATION_ON_UPDATE, RecoverySteps.VERIFY_EMAIL); @@ -794,14 +816,17 @@ public void testGetConfirmedSelfRegisteredUserVerifyEmail() verify(userStoreManager, atLeastOnce()).setUserClaimValues(anyString(), claimsCaptor.capture(), isNull()); Map capturedClaims = claimsCaptor.getValue(); - String updatedVerifiedEmailAddresses = - capturedClaims.get(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + String updatedEmailAddressClaim = + capturedClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); String verificationPendingEmailAddress = capturedClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM); - assertTrue(StringUtils.contains(updatedVerifiedEmailAddresses, verificationPendingEmail)); + assertTrue(StringUtils.contains(updatedEmailAddressClaim, verificationPendingEmail)); assertEquals(verificationPendingEmailAddress, StringUtils.EMPTY); + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); + reset(userStoreManager); // Case 2: Multiple email and mobile per user is disabled. mockMultiAttributeEnabled(false); @@ -809,12 +834,14 @@ public void testGetConfirmedSelfRegisteredUserVerifyEmail() metaProperties); ArgumentCaptor> claimsCaptor2 = ArgumentCaptor.forClass(Map.class); - verify(userStoreManager, atLeastOnce()).setUserClaimValues(anyString(), claimsCaptor2.capture(), isNull()); + verify(userStoreManager).setUserClaimValues(anyString(), claimsCaptor2.capture(), isNull()); Map capturedClaims2 = claimsCaptor2.getValue(); String emailAddressClaim = capturedClaims2.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); assertEquals(emailAddressClaim, verificationPendingEmail); + assertFalse(capturedClaims2.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + assertFalse(capturedClaims2.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); // Case 3 : Throws user store exception while getting user store manager. when(userRealm.getUserStoreManager()).thenThrow(new org.wso2.carbon.user.core.UserStoreException()); @@ -827,9 +854,73 @@ public void testGetConfirmedSelfRegisteredUserVerifyEmail() } } + @Test + public void testGetConfirmedSelfRegisteredUserEmailVerificationOnVerifiedListUpdate() + throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException, ClaimMetadataException { + + String verifiedChannelType = NotificationChannels.EMAIL_CHANNEL.getChannelType(); + String verifiedChannelClaim = "http://wso2.org/claims/emailaddress"; + String verificationPendingEmail = "pasindu@gmail.com"; + Map metaProperties = new HashMap<>(); + + // Case 1: Multiple email and mobile per user is enabled - EMAIL_VERIFICATION_ON_UPDATE. + User user = getUser(); + UserRecoveryData userRecoveryData = new UserRecoveryData(user, TEST_RECOVERY_DATA_STORE_SECRET, + RecoveryScenarios.EMAIL_VERIFICATION_ON_VERIFIED_LIST_UPDATE, RecoverySteps.VERIFY_EMAIL); + // Setting verification pending email claim value. + userRecoveryData.setRemainingSetIds(verificationPendingEmail); + + when(userRecoveryDataStore.load(eq(TEST_CODE))).thenReturn(userRecoveryData); + when(userRecoveryDataStore.load(eq(TEST_CODE), anyBoolean())).thenReturn(userRecoveryData); + when(privilegedCarbonContext.getUsername()).thenReturn(TEST_USER_NAME); + when(privilegedCarbonContext.getTenantDomain()).thenReturn(TEST_TENANT_DOMAIN_NAME); + + mockMultiAttributeEnabled(true); + mockGetUserClaimValue(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM, StringUtils.EMPTY); + mockGetUserClaimValue(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM, StringUtils.EMPTY); + + org.wso2.carbon.identity.application.common.model.Property property = + new org.wso2.carbon.identity.application.common.model.Property(); + org.wso2.carbon.identity.application.common.model.Property[] testProperties = + new org.wso2.carbon.identity.application.common.model.Property[]{property}; + + when(identityGovernanceService.getConfiguration(any(), anyString())).thenReturn(testProperties); + + userSelfRegistrationManager.getConfirmedSelfRegisteredUser(TEST_CODE, verifiedChannelType, verifiedChannelClaim, + metaProperties); + + ArgumentCaptor> claimsCaptor = ArgumentCaptor.forClass(Map.class); + verify(userStoreManager).setUserClaimValues(anyString(), claimsCaptor.capture(), isNull()); + + Map capturedClaims = claimsCaptor.getValue(); + + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM)); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESS_PENDING_VALUE_CLAIM), StringUtils.EMPTY); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM), + verificationPendingEmail); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM), + verificationPendingEmail); + + reset(userStoreManager); + // Case 2: Multiple email and mobile per user is disabled. + mockMultiAttributeEnabled(false); + + userSelfRegistrationManager.getConfirmedSelfRegisteredUser(TEST_CODE, verifiedChannelType, verifiedChannelClaim, + metaProperties); + + ArgumentCaptor> claimsCaptor2 = ArgumentCaptor.forClass(Map.class); + verify(userStoreManager).setUserClaimValues(anyString(), claimsCaptor2.capture(), isNull()); + + Map capturedClaims2 = claimsCaptor2.getValue(); + String emailAddressClaim = + capturedClaims2.get(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM); + assertEquals(emailAddressClaim, verificationPendingEmail); + assertFalse(capturedClaims2.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + assertFalse(capturedClaims2.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); + } @Test - public void testGetConfirmedSelfRegisteredUserVerifyMobile() + public void testGetConfirmedSelfRegisteredUserMobileVerificationOnUpdate() throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException { String verifiedChannelType = NotificationChannels.SMS_CHANNEL.getChannelType(); @@ -859,7 +950,8 @@ public void testGetConfirmedSelfRegisteredUserVerifyMobile() when(identityGovernanceService.getConfiguration(any(), anyString())).thenReturn(testProperties); try (MockedStatic mockedUtils = mockStatic(Utils.class)) { - mockedUtils.when(Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(true); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(true); mockedUtils.when(() -> Utils.getConnectorConfig( eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_VERIFICATION_BY_PRIVILEGED_USER), anyString())) @@ -875,21 +967,21 @@ public void testGetConfirmedSelfRegisteredUserVerifyMobile() verify(userStoreManager, atLeastOnce()).setUserClaimValues(anyString(), claimsCaptor.capture(), isNull()); Map capturedClaims = claimsCaptor.getValue(); - String updatedVerifiedMobileNumbers = - capturedClaims.get(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); String verificationPendingMobileNumberClaim = capturedClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM); String updatedMobileNumberClaimValue = capturedClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_CLAIM); - assertTrue(StringUtils.contains(updatedVerifiedMobileNumbers, verificationPendingMobileNumber)); assertEquals(verificationPendingMobileNumberClaim, StringUtils.EMPTY); assertEquals(updatedMobileNumberClaimValue, verificationPendingMobileNumber); + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM)); + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM)); // Case 2: External Verified Channel type. verifiedChannelType = NotificationChannels.EXTERNAL_CHANNEL.getChannelType(); try (MockedStatic mockedUtils = mockStatic(Utils.class)) { - mockedUtils.when(Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(true); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(true); mockedUtils.when(() -> Utils.getConnectorConfig( eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_VERIFICATION_BY_PRIVILEGED_USER), anyString())) @@ -911,7 +1003,8 @@ public void testGetConfirmedSelfRegisteredUserVerifyMobile() // Case 3: Throws user store exception while getting user claim values. try (MockedStatic mockedUtils = mockStatic(Utils.class)) { - mockedUtils.when(Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(true); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(true); mockedUtils.when(() -> Utils.getConnectorConfig( eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_VERIFICATION_BY_PRIVILEGED_USER), anyString())) @@ -935,7 +1028,8 @@ public void testGetConfirmedSelfRegisteredUserVerifyMobile() verifiedChannelType = NotificationChannels.SMS_CHANNEL.getChannelType(); verifiedChannelClaim = "http://wso2.org/claims/invalid"; try (MockedStatic mockedUtils = mockStatic(Utils.class)) { - mockedUtils.when(Utils::isMultiEmailsAndMobileNumbersPerUserEnabled).thenReturn(true); + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(true); mockedUtils.when(() -> Utils.getConnectorConfig( eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_VERIFICATION_BY_PRIVILEGED_USER), anyString())) @@ -951,9 +1045,67 @@ public void testGetConfirmedSelfRegisteredUserVerifyMobile() } @Test - public void testGetConfirmedSelfRegisteredUserConfirmSignUp() + public void testGetConfirmedSelfRegisteredUserMobileVerificationOnVerifiedListUpdate() throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException { + String verifiedChannelType = NotificationChannels.SMS_CHANNEL.getChannelType(); + String verifiedChannelClaim = "http://wso2.org/claims/mobile"; + String verificationPendingMobileNumber = "077888888"; + Map metaProperties = new HashMap<>(); + + User user = getUser(); + UserRecoveryData userRecoveryData = new UserRecoveryData(user, TEST_RECOVERY_DATA_STORE_SECRET, + RecoveryScenarios.MOBILE_VERIFICATION_ON_VERIFIED_LIST_UPDATE, RecoverySteps.VERIFY_MOBILE_NUMBER); + // Setting verification pending email claim value. + userRecoveryData.setRemainingSetIds(verificationPendingMobileNumber); + + when(userRecoveryDataStore.load(eq(TEST_CODE))).thenReturn(userRecoveryData); + when(userRecoveryDataStore.load(eq(TEST_CODE), anyBoolean())).thenReturn(userRecoveryData); + when(privilegedCarbonContext.getUsername()).thenReturn(TEST_USER_NAME); + when(privilegedCarbonContext.getTenantDomain()).thenReturn(TEST_TENANT_DOMAIN_NAME); + + mockGetUserClaimValue(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM, StringUtils.EMPTY); + mockGetUserClaimValue(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM, StringUtils.EMPTY); + + org.wso2.carbon.identity.application.common.model.Property property = + new org.wso2.carbon.identity.application.common.model.Property(); + org.wso2.carbon.identity.application.common.model.Property[] testProperties = + new org.wso2.carbon.identity.application.common.model.Property[]{property}; + + when(identityGovernanceService.getConfiguration(any(), anyString())).thenReturn(testProperties); + + try (MockedStatic mockedUtils = mockStatic(Utils.class)) { + mockedUtils.when(() -> Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(anyString(), anyString())) + .thenReturn(true); + mockedUtils.when(() -> Utils.getConnectorConfig( + eq(IdentityRecoveryConstants.ConnectorConfig.ENABLE_MOBILE_VERIFICATION_BY_PRIVILEGED_USER), + anyString())) + .thenReturn("true"); + mockedUtils.when(() -> Utils.getSignUpConfigs(eq(IdentityRecoveryConstants.ConnectorConfig + .SELF_REGISTRATION_NOTIFY_ACCOUNT_CONFIRMATION), + anyString())).thenReturn("true"); + userSelfRegistrationManager.getConfirmedSelfRegisteredUser(TEST_CODE, verifiedChannelType, + verifiedChannelClaim, metaProperties); + } + + ArgumentCaptor> claimsCaptor = ArgumentCaptor.forClass(Map.class); + verify(userStoreManager, atLeastOnce()).setUserClaimValues(anyString(), claimsCaptor.capture(), isNull()); + + Map capturedClaims = claimsCaptor.getValue(); + + assertEquals(capturedClaims.get(IdentityRecoveryConstants.MOBILE_NUMBER_PENDING_VALUE_CLAIM), + StringUtils.EMPTY); + assertFalse(capturedClaims.containsKey(IdentityRecoveryConstants.EMAIL_ADDRESS_CLAIM)); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM), + verificationPendingMobileNumber); + assertEquals(capturedClaims.get(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM), + verificationPendingMobileNumber); + } + + @Test + public void testGetConfirmedSelfRegisteredUserConfirmSignUp() + throws IdentityRecoveryException, UserStoreException, IdentityGovernanceException, ClaimMetadataException { + String verifiedChannelType = NotificationChannels.EMAIL_CHANNEL.getChannelType(); String verifiedChannelClaim = "http://wso2.org/claims/emailaddress"; String verificationPendingEmail = "pasindu@gmail.com"; @@ -1562,11 +1714,37 @@ private User getUser() { return user; } - private void mockMultiAttributeEnabled(Boolean isEnabled) { + private void mockMultiAttributeEnabled(Boolean isEnabled) throws ClaimMetadataException { mockedIdentityUtil.when(() -> IdentityUtil.getProperty( eq(IdentityRecoveryConstants.ConnectorConfig.SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER))) .thenReturn(isEnabled.toString()); + if (!isEnabled) return; + // Mock ClaimMetadataManagementService. + ClaimMetadataManagementService claimMetadataManagementService = mock(ClaimMetadataManagementService.class); + when(identityRecoveryServiceDataHolder.getClaimMetadataManagementService()) + .thenReturn(claimMetadataManagementService); + + List localClaims = new ArrayList<>(); + Map claimProperties = new HashMap<>(); + claimProperties.put(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY, Boolean.TRUE.toString()); + + LocalClaim mobileNumbersClaim = new LocalClaim(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); + mobileNumbersClaim.setClaimProperties(claimProperties); + LocalClaim verifiedMobileNumbersClaim = new LocalClaim(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); + verifiedMobileNumbersClaim.setClaimProperties(claimProperties); + LocalClaim emailAddressesClaim = new LocalClaim(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + emailAddressesClaim.setClaimProperties(claimProperties); + LocalClaim verifiedEmailAddressesClaim = + new LocalClaim(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + verifiedEmailAddressesClaim.setClaimProperties(claimProperties); + + localClaims.add(verifiedMobileNumbersClaim); + localClaims.add(mobileNumbersClaim); + localClaims.add(emailAddressesClaim); + localClaims.add(verifiedEmailAddressesClaim); + + doReturn(localClaims).when(claimMetadataManagementService).getLocalClaims(TEST_TENANT_DOMAIN_NAME); } private void mockGetUserClaimValue(String claimUri, String claimValue) throws UserStoreException { diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/util/UtilsTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/util/UtilsTest.java index d5a6c4f24..d96df396f 100644 --- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/util/UtilsTest.java +++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/util/UtilsTest.java @@ -33,6 +33,11 @@ import org.wso2.carbon.identity.auth.attribute.handler.exception.AuthAttributeHandlerException; import org.wso2.carbon.identity.auth.attribute.handler.model.ValidationFailureReason; import org.wso2.carbon.identity.auth.attribute.handler.model.ValidationResult; +import org.wso2.carbon.identity.claim.metadata.mgt.ClaimMetadataManagementService; +import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; +import org.wso2.carbon.identity.claim.metadata.mgt.model.AttributeMapping; +import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.event.IdentityEventConstants; @@ -125,6 +130,8 @@ public class UtilsTest { private AbstractUserStoreManager abstractUserStoreManager; @Mock private IdentityEventService identityEventService; + @Mock + private ClaimMetadataManagementService claimMetadataManagementService; private static MockedStatic mockedStaticIdentityTenantUtil; private static MockedStatic mockedStaticUserStoreManager; @@ -182,6 +189,8 @@ public void setUp() throws org.wso2.carbon.user.api.UserStoreException { when(identityRecoveryServiceDataHolder.getIdentityGovernanceService()).thenReturn(identityGovernanceService); when(identityRecoveryServiceDataHolder.getAccountLockService()).thenReturn(accountLockService); when(identityRecoveryServiceDataHolder.getIdentityEventService()).thenReturn(identityEventService); + when(identityRecoveryServiceDataHolder.getClaimMetadataManagementService()).thenReturn( + claimMetadataManagementService); when(realmService.getTenantUserRealm(TENANT_ID)).thenReturn(userRealm); when(realmService.getBootstrapRealm()).thenReturn(userRealm); @@ -1347,15 +1356,86 @@ public void testGetMultiValuedClaim() throws IdentityEventException, org.wso2.ca } @Test - public void testIsMultiEmailsAndMobileNumbersPerUserEnabled() { + public void testIsMultiEmailsAndMobileNumbersPerUserEnabled() throws Exception { + + // Mock ClaimMetadataManagementService + ClaimMetadataManagementService claimMetadataManagementService = mock(ClaimMetadataManagementService.class); + when(identityRecoveryServiceDataHolder.getClaimMetadataManagementService()) + .thenReturn(claimMetadataManagementService); + + // Case 1: When support_multi_emails_and_mobile_numbers_per_user config is false. + mockedStaticIdentityUtil.when(() -> IdentityUtil.getProperty( + IdentityRecoveryConstants.ConnectorConfig.SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER)) + .thenReturn("false"); + + boolean isEnabled = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(TENANT_DOMAIN, USER_STORE_DOMAIN); + assertFalse(isEnabled); - mockedStaticIdentityUtil.when(() -> IdentityUtil.getProperty(IdentityRecoveryConstants.ConnectorConfig - .SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER)) + // Case 2: When support_multi_emails_and_mobile_numbers_per_user config is true. + mockedStaticIdentityUtil.when(() -> IdentityUtil.getProperty( + IdentityRecoveryConstants.ConnectorConfig.SUPPORT_MULTI_EMAILS_AND_MOBILE_NUMBERS_PER_USER)) .thenReturn("true"); - boolean result = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(); - assertEquals(result, true); + + Map claimProperties2 = new HashMap<>(); + claimProperties2.put(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY, Boolean.TRUE.toString()); + when(claimMetadataManagementService.getLocalClaims(TENANT_DOMAIN)).thenReturn( + returnMultiEmailAndMobileRelatedLocalClaims(claimProperties2)); + + isEnabled = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(TENANT_DOMAIN, USER_STORE_DOMAIN); + assertTrue(isEnabled); + + // Case 3: When support by default is disabled for feature related claims. + Map claimProperties3 = new HashMap<>(); + claimProperties3.put(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY, Boolean.FALSE.toString()); + when(claimMetadataManagementService.getLocalClaims(TENANT_DOMAIN)).thenReturn( + returnMultiEmailAndMobileRelatedLocalClaims(claimProperties3)); + + isEnabled = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(TENANT_DOMAIN, USER_STORE_DOMAIN); + assertFalse(isEnabled); + + // Case 4: When user store domain is excluded for feature related claims. + Map claimProperties4 = new HashMap<>(); + claimProperties4.put(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY, Boolean.TRUE.toString()); + claimProperties4.put(ClaimConstants.EXCLUDED_USER_STORES_PROPERTY, USER_STORE_DOMAIN); + when(claimMetadataManagementService.getLocalClaims(TENANT_DOMAIN)).thenReturn( + returnMultiEmailAndMobileRelatedLocalClaims(claimProperties4)); + + isEnabled = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(TENANT_DOMAIN, USER_STORE_DOMAIN); + assertFalse(isEnabled); + + // Case 5: When ClaimMetadataException is thrown. + when(claimMetadataManagementService.getLocalClaims(TENANT_DOMAIN)) + .thenThrow(new ClaimMetadataException("Test exception")); + + isEnabled = Utils.isMultiEmailsAndMobileNumbersPerUserEnabled(TENANT_DOMAIN, USER_STORE_DOMAIN); + assertFalse(isEnabled); } + private static List returnMultiEmailAndMobileRelatedLocalClaims(Map claimProperties) { + + List localClaims = new ArrayList<>(); + + LocalClaim mobileNumbersClaim = new LocalClaim(IdentityRecoveryConstants.MOBILE_NUMBERS_CLAIM); + mobileNumbersClaim.setClaimProperties(claimProperties); + + LocalClaim verifiedMobileNumbersClaim = new LocalClaim(IdentityRecoveryConstants.VERIFIED_MOBILE_NUMBERS_CLAIM); + verifiedMobileNumbersClaim.setClaimProperties(claimProperties); + + LocalClaim emailAddressesClaim = new LocalClaim(IdentityRecoveryConstants.EMAIL_ADDRESSES_CLAIM); + emailAddressesClaim.setClaimProperties(claimProperties); + + LocalClaim verifiedEmailAddressesClaim = + new LocalClaim(IdentityRecoveryConstants.VERIFIED_EMAIL_ADDRESSES_CLAIM); + verifiedEmailAddressesClaim.setClaimProperties(claimProperties); + + localClaims.add(verifiedMobileNumbersClaim); + localClaims.add(mobileNumbersClaim); + localClaims.add(emailAddressesClaim); + localClaims.add(verifiedEmailAddressesClaim); + + return localClaims; + }; + private static User getUser() { User user = new User(); diff --git a/pom.xml b/pom.xml index a254752b2..68c8139d6 100644 --- a/pom.xml +++ b/pom.xml @@ -700,7 +700,7 @@ [1.0.1, 2.0.0) - 7.5.72 + 7.5.109 [7.3.6, 8.0.0)