diff --git a/.github/workflows/coverage-generator.yml b/.github/workflows/coverage-generator.yml
new file mode 100644
index 0000000000..88e3db054c
--- /dev/null
+++ b/.github/workflows/coverage-generator.yml
@@ -0,0 +1,177 @@
+name: Code Coverage Generator
+
+on:
+ workflow_dispatch:
+ schedule:
+ # Daily 22:00 UTC (3.30 AM SL time).
+ - cron: '00 22 * * *'
+
+jobs:
+ build-source:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Adopt JDK 11
+ uses: actions/setup-java@v4
+ with:
+ java-version: 11
+ distribution: "adopt"
+
+ - name: Build with Maven
+ run: |
+ mvn clean install -U -B -Dmaven.test.skip=true
+
+ - name: Cache source code
+ uses: actions/cache@v4
+ with:
+ path: .
+ key: ${{ runner.os }}-source-${{ github.sha }}
+
+ oidc-conformance-report:
+ needs: build-source
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Restore source code
+ uses: actions/cache@v4
+ with:
+ path: .
+ key: ${{ runner.os }}-source-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-source-
+
+ - name: Get the latest Jacoco report URL
+ id: get-artifact-url-oidc
+ run: |
+ GITHUB_API_URL="https://api.github.com"
+ OWNER="wso2"
+ REPO="product-is"
+ WORKFLOW_ID="oidc-conformance-test.yml"
+ GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"
+
+ # Get the latest successful workflow run
+ WORKFLOW_RUNS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs?status=success&per_page=1")
+ RUN_ID=$(echo $WORKFLOW_RUNS | jq -r '.workflow_runs[0].id')
+
+ if [ "$RUN_ID" == "null" ]; then
+ echo "No successful workflow runs found"
+ exit 1
+ fi
+
+ # Get the artifacts for the workflow run
+ ARTIFACTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/runs/$RUN_ID/artifacts")
+ ARTIFACT_URL=$(echo $ARTIFACTS | jq -r '.artifacts[] | select(.name == "jacoco-xml") | .archive_download_url')
+
+ if [ "$ARTIFACT_URL" == "null" ]; then
+ echo "Artifact not found"
+ exit 1
+ fi
+
+ echo "::set-output name=artifact-url::$ARTIFACT_URL"
+
+ - name: Download latest Jacoco report
+ run: |
+ curl -L -o artifact-oidc.zip \
+ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+ ${{ steps.get-artifact-url-oidc.outputs.artifact-url }}
+
+ - name: Unzip Jacoco report
+ run: |
+ unzip artifact-oidc.zip -d ./artifacts-oidc
+
+ - name: Upload coverage reports to Codecov for OIDC
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./artifacts-oidc/jacoco.xml
+ flags: conformance-oidc
+ disable_search: true
+
+ fapi-conformance-report:
+ needs: build-source
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Restore source code
+ uses: actions/cache@v4
+ with:
+ path: .
+ key: ${{ runner.os }}-source-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-source-
+
+ - name: Get the latest Jacoco report URL
+ id: get-artifact-url-fapi
+ run: |
+ GITHUB_API_URL="https://api.github.com"
+ OWNER="wso2"
+ REPO="product-is"
+ WORKFLOW_ID="fapi-oidc-conformance-test.yml"
+ GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"
+
+ # Get the latest successful workflow run
+ WORKFLOW_RUNS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_ID/runs?status=success&per_page=1")
+ RUN_ID=$(echo $WORKFLOW_RUNS | jq -r '.workflow_runs[0].id')
+
+ if [ "$RUN_ID" == "null" ]; then
+ echo "No successful workflow runs found"
+ exit 1
+ fi
+
+ # Get the artifacts for the workflow run
+ ARTIFACTS=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "$GITHUB_API_URL/repos/$OWNER/$REPO/actions/runs/$RUN_ID/artifacts")
+ ARTIFACT_URL=$(echo $ARTIFACTS | jq -r '.artifacts[] | select(.name == "jacoco-xml") | .archive_download_url')
+
+ if [ "$ARTIFACT_URL" == "null" ]; then
+ echo "Artifact not found"
+ exit 1
+ fi
+
+ echo "::set-output name=artifact-url::$ARTIFACT_URL"
+
+ - name: Download the latest Jacoco report
+ run: |
+ curl -L -o artifact-fapi.zip \
+ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+ ${{ steps.get-artifact-url-fapi.outputs.artifact-url }}
+
+ - name: Unzip Jacoco report
+ run: |
+ unzip artifact-fapi.zip -d ./artifacts-fapi
+
+ - name: Upload coverage reports to Codecov for FAPI
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./artifacts-fapi/jacoco.xml
+ flags: conformance-fapi
+ disable_search: true
+
+ integration-test-report:
+ needs: build-source
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Restore source code
+ uses: actions/cache@v4
+ with:
+ path: .
+ key: ${{ runner.os }}-source-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-source-
+
+ - name: Download integration Jacoco XML report
+ run: |
+ mkdir artifacts-integration
+ curl -L -o ./artifacts-integration/jacoco.xml https://wso2.org/jenkins/job/products/job/product-is/lastSuccessfulBuild/artifact/modules/integration/tests-integration/tests-backend/target/jacoco/coverage/jacoco.xml
+
+ - name: Upload coverage reports to Codecov for integration tests
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: ./artifacts-integration/jacoco.xml
+ flags: integration
+ disable_search: true
diff --git a/.github/workflows/pr-builder.yml b/.github/workflows/pr-builder.yml
index de1ee27082..5d77980d65 100644
--- a/.github/workflows/pr-builder.yml
+++ b/.github/workflows/pr-builder.yml
@@ -50,3 +50,4 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
files : target/site/jacoco/jacoco.xml
+ flags: unit
diff --git a/codecov.yml b/codecov.yml
index 9571a5f2ad..96f5d2dc79 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,16 +1,24 @@
-
codecov:
require_ci_to_pass: yes
notify:
wait_for_ci: yes
+ max_report_age: false
+
coverage:
status:
- project:
- default:
- enabled: yes
- threshold: null
- target: auto
- patch:
- default:
- target: 80%
- threshold: 40%
+ project: off
+ patch: off
+
+flag_management:
+ default_rules:
+ carryforward: true
+ individual_flags:
+ - name: unit
+ statuses:
+ - type: project
+ target: auto
+ threshold: null
+ - type: patch
+ target: 80%
+ threshold: 40%
+
diff --git a/components/org.wso2.carbon.identity.account.suspension.notification.task/pom.xml b/components/org.wso2.carbon.identity.account.suspension.notification.task/pom.xml
index a8cc5d3050..85189a9ff8 100644
--- a/components/org.wso2.carbon.identity.account.suspension.notification.task/pom.xml
+++ b/components/org.wso2.carbon.identity.account.suspension.notification.task/pom.xml
@@ -20,7 +20,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.api.user.governance/pom.xml b/components/org.wso2.carbon.identity.api.user.governance/pom.xml
index c6351f7ecc..f7ba6c75ec 100644
--- a/components/org.wso2.carbon.identity.api.user.governance/pom.xml
+++ b/components/org.wso2.carbon.identity.api.user.governance/pom.xml
@@ -23,12 +23,12 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../..
org.wso2.carbon.identity.api.user.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
jar
WSO2 Carbon - User Rest Governance API
WSO2 Carbon - User Rest Governance API
diff --git a/components/org.wso2.carbon.identity.api.user.recovery/pom.xml b/components/org.wso2.carbon.identity.api.user.recovery/pom.xml
index c74c50207c..ce8740005e 100644
--- a/components/org.wso2.carbon.identity.api.user.recovery/pom.xml
+++ b/components/org.wso2.carbon.identity.api.user.recovery/pom.xml
@@ -23,12 +23,12 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../..
org.wso2.carbon.identity.api.user.recovery
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
jar
WSO2 Carbon - Identity Management Recovery Rest API
WSO2 Carbon - Identity Management Recovery Rest API
diff --git a/components/org.wso2.carbon.identity.auth.attribute.handler/pom.xml b/components/org.wso2.carbon.identity.auth.attribute.handler/pom.xml
index 83612167ad..de06d2bff4 100644
--- a/components/org.wso2.carbon.identity.auth.attribute.handler/pom.xml
+++ b/components/org.wso2.carbon.identity.auth.attribute.handler/pom.xml
@@ -22,7 +22,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.captcha/pom.xml b/components/org.wso2.carbon.identity.captcha/pom.xml
index acc76f7bbe..aced03305d 100644
--- a/components/org.wso2.carbon.identity.captcha/pom.xml
+++ b/components/org.wso2.carbon.identity.captcha/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.governance/pom.xml b/components/org.wso2.carbon.identity.governance/pom.xml
index ecf0df70d7..827456593b 100644
--- a/components/org.wso2.carbon.identity.governance/pom.xml
+++ b/components/org.wso2.carbon.identity.governance/pom.xml
@@ -18,7 +18,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.governance/src/main/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImpl.java b/components/org.wso2.carbon.identity.governance/src/main/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImpl.java
index 1c320016e2..63b51ed0ed 100644
--- a/components/org.wso2.carbon.identity.governance/src/main/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImpl.java
+++ b/components/org.wso2.carbon.identity.governance/src/main/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImpl.java
@@ -56,6 +56,9 @@ public class IdentityGovernanceServiceImpl implements IdentityGovernanceService
private static final String EMAIL_LINK_PASSWORD_RECOVERY_PROPERTY
= "Recovery.Notification.Password.emailLink.Enable";
private static final String SMS_OTP_PASSWORD_RECOVERY_PROPERTY = "Recovery.Notification.Password.smsOtp.Enable";
+ private static final String USERNAME_RECOVERY_ENABLE = "Recovery.Notification.Username.Enable";
+ private static final String USERNAME_RECOVERY_EMAIL_ENABLE = "Recovery.Notification.Username.Email.Enable";
+ private static final String USERNAME_RECOVERY_SMS_ENABLE = "Recovery.Notification.Username.SMS.Enable";
private static final String FALSE_STRING = "false";
public void updateConfiguration(String tenantDomain, Map configurationDetails)
@@ -70,6 +73,7 @@ public void updateConfiguration(String tenantDomain, Map configu
updateEmailOTPNumericPropertyValue(configurationDetails);
IdPManagementUtil.validatePasswordRecoveryPropertyValues(configurationDetails);
updatePasswordRecoveryPropertyValues(configurationDetails, identityMgtProperties);
+ updateUsernameRecoveryPropertyValues(configurationDetails, identityMgtProperties);
for (IdentityProviderProperty identityMgtProperty : identityMgtProperties) {
IdentityProviderProperty prop = new IdentityProviderProperty();
String key = identityMgtProperty.getName();
@@ -407,4 +411,63 @@ private void updatePasswordRecoveryPropertyValues(Map configurat
}
}
}
+
+ /**
+ * This method updates the username recovery property values based on the new configurations.
+ *
+ * @param configurationDetails Updating configuration details of the resident identity provider.
+ * @param identityMgtProperties Identity management properties of the resident identity provider.
+ */
+ private void updateUsernameRecoveryPropertyValues(Map configurationDetails,
+ IdentityProviderProperty[] identityMgtProperties) {
+
+ if (configurationDetails.containsKey(USERNAME_RECOVERY_ENABLE) ||
+ configurationDetails.containsKey(USERNAME_RECOVERY_EMAIL_ENABLE) ||
+ configurationDetails.containsKey(USERNAME_RECOVERY_SMS_ENABLE)) {
+
+ String usernameRecoveryProp = configurationDetails.get(USERNAME_RECOVERY_ENABLE);
+ String usernameRecoveryEmailProp = configurationDetails.get(USERNAME_RECOVERY_EMAIL_ENABLE);
+ String usernameRecoverySmsProp = configurationDetails.get(USERNAME_RECOVERY_SMS_ENABLE);
+
+ boolean usernameRecoveryProperty = Boolean.parseBoolean(usernameRecoveryProp);
+ boolean usernameRecoveryEmailProperty = Boolean.parseBoolean(usernameRecoveryEmailProp);
+ boolean usernameRecoverySmsProperty = Boolean.parseBoolean(usernameRecoverySmsProp);
+
+ if(usernameRecoveryProperty) {
+ configurationDetails.put(USERNAME_RECOVERY_EMAIL_ENABLE,
+ String.valueOf(usernameRecoveryEmailProperty ||
+ StringUtils.isBlank(usernameRecoveryEmailProp)));
+ configurationDetails.put(USERNAME_RECOVERY_SMS_ENABLE,
+ String.valueOf(usernameRecoverySmsProperty ||
+ StringUtils.isBlank(usernameRecoverySmsProp)));
+ } else if (StringUtils.isBlank(usernameRecoveryProp)) {
+ // Connector is not explicitly enabled or disabled. The connector state is derived from new and existing
+ // configurations.
+ boolean isUsernameEmailRecoveryCurrentlyEnabled = false;
+ boolean isUsernameSmsRecoveryCurrentlyEnabled = false;
+ for (IdentityProviderProperty identityMgtProperty : identityMgtProperties) {
+ if (USERNAME_RECOVERY_EMAIL_ENABLE.equals(identityMgtProperty.getName())) {
+ isUsernameEmailRecoveryCurrentlyEnabled = Boolean.parseBoolean(identityMgtProperty.getValue());
+ } else if (USERNAME_RECOVERY_SMS_ENABLE.equals(identityMgtProperty.getName())) {
+ isUsernameSmsRecoveryCurrentlyEnabled = Boolean.parseBoolean(identityMgtProperty.getValue());
+ }
+ }
+ boolean enableUsernameEmailRecovery = usernameRecoveryEmailProperty ||
+ ( StringUtils.isBlank(usernameRecoveryEmailProp) &&
+ isUsernameEmailRecoveryCurrentlyEnabled );
+ boolean enableUsernameSmsRecovery = usernameRecoverySmsProperty ||
+ ( StringUtils.isBlank(usernameRecoverySmsProp) &&
+ isUsernameSmsRecoveryCurrentlyEnabled );
+ configurationDetails.put(USERNAME_RECOVERY_EMAIL_ENABLE,
+ String.valueOf(enableUsernameEmailRecovery));
+ configurationDetails.put(USERNAME_RECOVERY_SMS_ENABLE,
+ String.valueOf(enableUsernameSmsRecovery));
+ configurationDetails.put(USERNAME_RECOVERY_ENABLE,
+ String.valueOf(enableUsernameEmailRecovery || enableUsernameSmsRecovery));
+ } else {
+ configurationDetails.put(USERNAME_RECOVERY_EMAIL_ENABLE, FALSE_STRING);
+ configurationDetails.put(USERNAME_RECOVERY_SMS_ENABLE, FALSE_STRING);
+ }
+ }
+ }
}
diff --git a/components/org.wso2.carbon.identity.governance/src/test/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImplTest.java b/components/org.wso2.carbon.identity.governance/src/test/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImplTest.java
new file mode 100644
index 0000000000..d080d8a5e1
--- /dev/null
+++ b/components/org.wso2.carbon.identity.governance/src/test/java/org/wso2/carbon/identity/governance/IdentityGovernanceServiceImplTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.org)
+ *
+ * WSO2 Inc. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.wso2.carbon.identity.governance;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig;
+import org.wso2.carbon.identity.application.common.model.IdentityProvider;
+import org.wso2.carbon.identity.application.common.model.IdentityProviderProperty;
+import org.wso2.carbon.identity.governance.internal.IdentityMgtServiceDataHolder;
+import org.wso2.carbon.idp.mgt.IdentityProviderManagementException;
+import org.wso2.carbon.idp.mgt.IdpManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.AssertJUnit.assertEquals;
+
+public class IdentityGovernanceServiceImplTest {
+
+ // Constants.
+ private static final String TENANT_DOMAIN = "carbon.super";
+ private static final String TRUE_STRING = "true";
+ private static final String FALSE_STRING = "false";
+ private static final String USERNAME_RECOVERY_ENABLE = "Recovery.Notification.Username.Enable";
+ private static final String USERNAME_RECOVERY_EMAIL_ENABLE = "Recovery.Notification.Username.Email.Enable";
+ private static final String USERNAME_RECOVERY_SMS_ENABLE = "Recovery.Notification.Username.SMS.Enable";
+
+ @Mock
+ IdentityMgtServiceDataHolder identityMgtServiceDataHolder;
+
+ @Mock
+ IdpManager idpManager;
+
+ @Mock
+ IdentityProvider identityProvider;
+
+ MockedStatic identityMgtServiceDataHolderMockedStatic;
+
+ private IdentityGovernanceServiceImpl identityGovernanceService;
+
+ @BeforeMethod
+ public void setup() throws IdentityProviderManagementException {
+
+ MockitoAnnotations.openMocks(this);
+ identityMgtServiceDataHolderMockedStatic = mockStatic(IdentityMgtServiceDataHolder.class);
+ identityMgtServiceDataHolderMockedStatic.when(IdentityMgtServiceDataHolder::
+ getInstance).thenReturn(identityMgtServiceDataHolder);
+ when(identityMgtServiceDataHolder.getIdpManager()).thenReturn(idpManager);
+ when(idpManager.getResidentIdP(TENANT_DOMAIN)).thenReturn(identityProvider);
+
+ FederatedAuthenticatorConfig[] authenticatorConfigs = new FederatedAuthenticatorConfig[0];
+ when(identityProvider.getFederatedAuthenticatorConfigs()).thenReturn(authenticatorConfigs);
+
+ identityGovernanceService = new IdentityGovernanceServiceImpl();
+ }
+
+ @AfterMethod
+ public void tearDown() {
+
+ identityMgtServiceDataHolderMockedStatic.close();
+ }
+
+ @Test(dataProvider = "updateConfigurations")
+ public void testUpdateConfiguration(Map configurationDetails,
+ IdentityProviderProperty[] identityProviderProperties,
+ Map expected) throws IdentityGovernanceException {
+
+ when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties);
+
+ identityGovernanceService.updateConfiguration(TENANT_DOMAIN, configurationDetails);
+
+ // Capture the arguments passed to setIdpProperties
+ ArgumentCaptor
+ argumentCaptor = ArgumentCaptor.forClass(IdentityProviderProperty[].class);
+ verify(identityProvider).setIdpProperties(argumentCaptor.capture());
+
+ // Assert
+ IdentityProviderProperty[] capturedProperties = argumentCaptor.getValue();
+ for (IdentityProviderProperty capturedProperty : capturedProperties) {
+ assertEquals(expected.get(capturedProperty.getName()), capturedProperty.getValue());
+ }
+
+ }
+
+ @DataProvider(name = "updateConfigurations")
+ public Object[][] buildConfigurations() {
+
+ // Only email config is true. Preconditions: all the configs false.
+ Map usernameConfig1 = new HashMap<>();
+ usernameConfig1.put(USERNAME_RECOVERY_EMAIL_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties1 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected1 = getExpectedPropertyValues(true, true, false);
+
+ // Only sms config is true. Preconditions: all the configs false.
+ Map usernameConfig2 = new HashMap<>();
+ usernameConfig2.put(USERNAME_RECOVERY_SMS_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties2 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected2 = getExpectedPropertyValues(true, false, true);
+
+ // Only sms is false. Preconditions: sms and username is true.
+ Map usernameConfig3 = new HashMap<>();
+ usernameConfig3.put(USERNAME_RECOVERY_SMS_ENABLE, FALSE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties3 = getIdentityProviderProperties(
+ true, false, true);
+ Map expected3 = getExpectedPropertyValues(false, false, false);
+
+ // Only email is false. Preconditions: email and username is true.
+ Map usernameConfig4 = new HashMap<>();
+ usernameConfig4.put(USERNAME_RECOVERY_EMAIL_ENABLE, FALSE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties4 = getIdentityProviderProperties(
+ true, true, false);
+ Map expected4 = getExpectedPropertyValues(false, false, false);
+
+ // Only email is true. Preconditions: sms and username is true.
+ Map usernameConfig5 = new HashMap<>();
+ usernameConfig5.put(USERNAME_RECOVERY_EMAIL_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties5 = getIdentityProviderProperties(
+ true, false, true);
+ Map expected5 = getExpectedPropertyValues(true, true, true);
+
+ // Only sms is true. Preconditions: email and username is true.
+ Map usernameConfig6 = new HashMap<>();
+ usernameConfig6.put(USERNAME_RECOVERY_SMS_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties6 = getIdentityProviderProperties(
+ true, true, false);
+ Map expected6 = getExpectedPropertyValues(true, true, true);
+
+ // Sms config true and email config false. Preconditions: all the configs false.
+ Map usernameConfig7 = new HashMap<>();
+ usernameConfig7.put(USERNAME_RECOVERY_SMS_ENABLE, TRUE_STRING);
+ usernameConfig7.put(USERNAME_RECOVERY_EMAIL_ENABLE, FALSE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties7 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected7 = getExpectedPropertyValues(true, false, true);
+
+ // Email config true and sms config false. Preconditions: all the configs false.
+ Map usernameConfig8 = new HashMap<>();
+ usernameConfig8.put(USERNAME_RECOVERY_EMAIL_ENABLE, TRUE_STRING);
+ usernameConfig8.put(USERNAME_RECOVERY_SMS_ENABLE, FALSE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties8 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected8 = getExpectedPropertyValues(true, true, false);
+
+ // Sms config true and email config true. Preconditions: all the configs false.
+ Map usernameConfig9 = new HashMap<>();
+ usernameConfig9.put(USERNAME_RECOVERY_SMS_ENABLE, TRUE_STRING);
+ usernameConfig9.put(USERNAME_RECOVERY_EMAIL_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties9 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected9 = getExpectedPropertyValues(true, true, true);
+
+ // Only username config true. Preconditions: all the configs false.
+ Map usernameConfig10 = new HashMap<>();
+ usernameConfig10.put(USERNAME_RECOVERY_ENABLE, TRUE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties10 = getIdentityProviderProperties(
+ false, false, false);
+ Map expected10 = getExpectedPropertyValues(true, true, true);
+
+ // Only username config false. Preconditions: all the configs true.
+ Map usernameConfig11 = new HashMap<>();
+ usernameConfig11.put(USERNAME_RECOVERY_ENABLE, FALSE_STRING);
+
+ IdentityProviderProperty[] identityProviderProperties11 = getIdentityProviderProperties(
+ true, true, true);
+ Map expected11 = getExpectedPropertyValues(false, false, false);
+
+ return new Object[][]{
+ {usernameConfig1, identityProviderProperties1, expected1},
+ {usernameConfig2, identityProviderProperties2, expected2},
+ {usernameConfig3, identityProviderProperties3, expected3},
+ {usernameConfig4, identityProviderProperties4, expected4},
+ {usernameConfig5, identityProviderProperties5, expected5},
+ {usernameConfig6, identityProviderProperties6, expected6},
+ {usernameConfig7, identityProviderProperties7, expected7},
+ {usernameConfig8, identityProviderProperties8, expected8},
+ {usernameConfig9, identityProviderProperties9, expected9},
+ {usernameConfig10, identityProviderProperties10, expected10},
+ {usernameConfig11, identityProviderProperties11, expected11}
+ };
+
+ }
+
+ private IdentityProviderProperty[] getIdentityProviderProperties(boolean usernameEnable,
+ boolean usernameEmailEnable,
+ boolean usernameSmsEnable) {
+
+ IdentityProviderProperty identityProviderProperty1 = new IdentityProviderProperty();
+ identityProviderProperty1.setName(USERNAME_RECOVERY_ENABLE);
+ identityProviderProperty1.setValue(usernameEnable ? TRUE_STRING : FALSE_STRING);
+
+ IdentityProviderProperty identityProviderProperty2 = new IdentityProviderProperty();
+ identityProviderProperty2.setName(USERNAME_RECOVERY_SMS_ENABLE);
+ identityProviderProperty2.setValue(usernameSmsEnable ? TRUE_STRING : FALSE_STRING);
+
+ IdentityProviderProperty identityProviderProperty3 = new IdentityProviderProperty();
+ identityProviderProperty3.setName(USERNAME_RECOVERY_EMAIL_ENABLE);
+ identityProviderProperty3.setValue(usernameEmailEnable ? TRUE_STRING : FALSE_STRING);
+
+ return new IdentityProviderProperty[]{
+ identityProviderProperty1,
+ identityProviderProperty2,
+ identityProviderProperty3
+ };
+ }
+
+ private HashMap getExpectedPropertyValues(boolean usernameEnable,
+ boolean usernameEmailEnable,
+ boolean usernameSmsEnable) {
+
+ HashMap expected = new HashMap<>();
+ expected.put(USERNAME_RECOVERY_ENABLE, usernameEnable ? TRUE_STRING : FALSE_STRING);
+ expected.put(USERNAME_RECOVERY_EMAIL_ENABLE, usernameEmailEnable ? TRUE_STRING : FALSE_STRING);
+ expected.put(USERNAME_RECOVERY_SMS_ENABLE, usernameSmsEnable ? TRUE_STRING : FALSE_STRING);
+
+ return expected;
+ }
+}
diff --git a/components/org.wso2.carbon.identity.governance/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.governance/src/test/resources/testng.xml
index 6a4dc07448..0c10670da8 100644
--- a/components/org.wso2.carbon.identity.governance/src/test/resources/testng.xml
+++ b/components/org.wso2.carbon.identity.governance/src/test/resources/testng.xml
@@ -23,8 +23,9 @@
-
-
+
+
+
diff --git a/components/org.wso2.carbon.identity.idle.account.identification/pom.xml b/components/org.wso2.carbon.identity.idle.account.identification/pom.xml
index 4a01149ace..ab5297eaf0 100644
--- a/components/org.wso2.carbon.identity.idle.account.identification/pom.xml
+++ b/components/org.wso2.carbon.identity.idle.account.identification/pom.xml
@@ -18,7 +18,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.resolver.regex/pom.xml b/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.resolver.regex/pom.xml
index 88ec7493be..d27f62ddcc 100644
--- a/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.resolver.regex/pom.xml
+++ b/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.resolver.regex/pom.xml
@@ -21,7 +21,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.service/pom.xml b/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.service/pom.xml
index 675a538da9..654e112853 100644
--- a/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.service/pom.xml
+++ b/components/org.wso2.carbon.identity.multi.attribute.login/org.wso2.carbon.identity.multi.attribute.login.service/pom.xml
@@ -21,7 +21,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/components/org.wso2.carbon.identity.multi.attribute.login/pom.xml b/components/org.wso2.carbon.identity.multi.attribute.login/pom.xml
index 37d4dd80f7..cdeab9f10b 100644
--- a/components/org.wso2.carbon.identity.multi.attribute.login/pom.xml
+++ b/components/org.wso2.carbon.identity.multi.attribute.login/pom.xml
@@ -21,7 +21,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/components/org.wso2.carbon.identity.password.expiry/pom.xml b/components/org.wso2.carbon.identity.password.expiry/pom.xml
index 6cea48e83a..8a3dbf5ee3 100644
--- a/components/org.wso2.carbon.identity.password.expiry/pom.xml
+++ b/components/org.wso2.carbon.identity.password.expiry/pom.xml
@@ -20,7 +20,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.password.history/pom.xml b/components/org.wso2.carbon.identity.password.history/pom.xml
index 3b6661ee47..bd8ca5c75c 100644
--- a/components/org.wso2.carbon.identity.password.history/pom.xml
+++ b/components/org.wso2.carbon.identity.password.history/pom.xml
@@ -18,7 +18,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.password.policy/pom.xml b/components/org.wso2.carbon.identity.password.policy/pom.xml
index 61f8e30732..3a3c9547e6 100644
--- a/components/org.wso2.carbon.identity.password.policy/pom.xml
+++ b/components/org.wso2.carbon.identity.password.policy/pom.xml
@@ -18,7 +18,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.piicontroller/pom.xml b/components/org.wso2.carbon.identity.piicontroller/pom.xml
index b5f50b489e..ef90e193e0 100644
--- a/components/org.wso2.carbon.identity.piicontroller/pom.xml
+++ b/components/org.wso2.carbon.identity.piicontroller/pom.xml
@@ -19,7 +19,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.recovery.endpoint/pom.xml b/components/org.wso2.carbon.identity.recovery.endpoint/pom.xml
index 48ca6a5fd7..c8bb27d96a 100644
--- a/components/org.wso2.carbon.identity.recovery.endpoint/pom.xml
+++ b/components/org.wso2.carbon.identity.recovery.endpoint/pom.xml
@@ -5,7 +5,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.recovery/pom.xml b/components/org.wso2.carbon.identity.recovery/pom.xml
index 86fa4d959b..e09c41a8bb 100644
--- a/components/org.wso2.carbon.identity.recovery/pom.xml
+++ b/components/org.wso2.carbon.identity.recovery/pom.xml
@@ -18,7 +18,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
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 89a02917ee..53b3fa0edd 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
@@ -615,6 +615,8 @@ public static class ConnectorConfig {
public static final String FORCE_ADD_PW_RECOVERY_QUESTION = "Recovery.Question.Password.Forced.Enable";
public static final String FORCE_MIN_NO_QUESTION_ANSWERED = "Recovery.Question.MinQuestionsToAnswer";
public static final String USERNAME_RECOVERY_ENABLE = "Recovery.Notification.Username.Enable";
+ public static final String USERNAME_RECOVERY_EMAIL_ENABLE = "Recovery.Notification.Username.Email.Enable";
+ public static final String USERNAME_RECOVERY_SMS_ENABLE = "Recovery.Notification.Username.SMS.Enable";
public static final String USERNAME_RECOVERY_NON_UNIQUE_USERNAME = "Recovery.Notification.Username.NonUniqueUsername";
public static final String QUESTION_CHALLENGE_SEPARATOR = "Recovery.Question.Password.Separator";
public static final String QUESTION_MIN_NO_ANSWER = "Recovery.Question.Password.MinAnswers";
diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java
index 74ef59ffb6..218e43aa0f 100644
--- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java
+++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImpl.java
@@ -99,6 +99,10 @@ public Map getPropertyNameMapping() {
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.QUESTION_MIN_NO_ANSWER, "Number of questions " +
"required for password recovery");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE, "Username recovery");
+ nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ "Notification based username recovery via EMAIL");
+ nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ "Notification based username recovery via SMS");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
"Enable reCaptcha for username recovery");
nameMapping.put(IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME, "Recovery link expiry time in minutes");
@@ -193,6 +197,8 @@ public String[] getPropertyNames() {
properties.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_ENABLE);
properties.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_MAX_FAILED_ATTEMPTS);
properties.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE);
+ properties.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE);
+ properties.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE);
properties.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE);
properties.add(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE);
properties.add(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS);
@@ -227,6 +233,8 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
String enableRecoveryQuestionPasswordReCaptcha = "true";
String recoveryQuestionPasswordReCaptchaMaxFailedAttempts = "2";
String enableUsernameRecovery = "false";
+ String enableUsernameRecoveryEmail = "false";
+ String enableUsernameRecoverySMS = "false";
String enableNotificationInternallyManage = "true";
String expiryTime = "1440";
String expiryTimeSMSOTP = "1";
@@ -272,6 +280,10 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_MAX_FAILED_ATTEMPTS);
String usernameRecovery = IdentityUtil.getProperty(
IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE);
+ String usernameRecoveryEmail = IdentityUtil.getProperty(
+ IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE);
+ String usernameRecoverySMS = IdentityUtil.getProperty(
+ IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE);
String notificationInternallyManged = IdentityUtil.getProperty(
IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE);
String expiryTimeProperty = IdentityUtil.getProperty(IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME);
@@ -351,6 +363,14 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
}
if (StringUtils.isNotEmpty(usernameRecovery)) {
enableUsernameRecovery = usernameRecovery;
+ // Setting the username recovery value to keep backward compatibility.
+ enableUsernameRecoveryEmail = usernameRecovery;
+ }
+ if (StringUtils.isNotEmpty(usernameRecoveryEmail)){
+ enableUsernameRecoveryEmail = usernameRecoveryEmail;
+ }
+ if (StringUtils.isNotEmpty(usernameRecoverySMS)) {
+ enableUsernameRecoverySMS = usernameRecoverySMS;
}
if (StringUtils.isNotEmpty(expiryTimeProperty)) {
expiryTime = expiryTimeProperty;
@@ -433,6 +453,10 @@ public Properties getDefaultPropertyValues(String tenantDomain) throws IdentityG
recoveryQuestionPasswordReCaptchaMaxFailedAttempts);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE,
enableUsernameRecovery);
+ defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ enableUsernameRecoveryEmail);
+ defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ enableUsernameRecoverySMS);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
enableUsernameRecoveryReCaptcha);
defaultProperties.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE,
@@ -518,6 +542,12 @@ public Map getMetaData() {
meta.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE,
getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));
+ meta.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));
+
+ meta.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));
+
meta.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue()));
diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java
index 79f04f2748..e9c5e32d8f 100644
--- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java
+++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManager.java
@@ -23,6 +23,7 @@
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.common.model.User;
+import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
@@ -108,6 +109,107 @@ public static UserAccountRecoveryManager getInstance() {
return instance;
}
+ /**
+ * Initiate the username recovery flow for the user with matching claims when non-unique user config enabled.
+ *
+ * @param claims User claims
+ * @param tenantDomain Tenant domain
+ * @param properties Meta properties
+ * @return RecoveryChannelInfoDTO object.
+ */
+ public RecoveryChannelInfoDTO retrieveUsersRecoveryInformationForUsername(Map claims,
+ String tenantDomain,
+ Map properties)
+ throws IdentityRecoveryException {
+
+ RecoveryScenarios recoveryScenario = RecoveryScenarios.USERNAME_RECOVERY;
+ // Retrieve the user who matches the given set of claims.
+ ArrayList resultedUserList = getUserListByClaims(claims, tenantDomain);
+
+ if (!resultedUserList.isEmpty()) {
+ StringBuilder usernameCombined = new StringBuilder();
+ // Get the notification management mechanism.
+ List notificationChannels;
+ boolean isNotificationsInternallyManaged = Utils.isNotificationsInternallyManaged(tenantDomain, properties);
+ String recoveryFlowId = null;
+ String recoveryCode = null;
+ String notificationChannelList = null;
+ String username = null;
+ NotificationChannelDTO[] notificationChannelDTOS = null;
+
+ for (org.wso2.carbon.user.core.common.User resultedUser : resultedUserList) {
+ username = resultedUser.getUsername();
+ User user = Utils.buildUser(username, tenantDomain);
+
+ try {
+ // If the account is locked or disabled, do not let the user, recover the account.
+ checkAccountLockedStatus(user);
+
+ } catch (IdentityException e) {
+ if (log.isDebugEnabled()) {
+ log.debug(username + " is locked.");
+ }
+ continue;
+ }
+
+ /* If the notification is internally managed, then notification channels available for the user needs to
+ be retrieved. If external notifications are enabled, external channel list should be returned.*/
+ if (isNotificationsInternallyManaged) {
+ notificationChannels = getInternalNotificationChannelList(username, tenantDomain,
+ recoveryScenario);
+ } else {
+ notificationChannels = getExternalNotificationChannelList();
+ }
+
+ // Validate whether the user account is eligible for account recovery.
+ checkUserValidityForAccountRecovery(user, recoveryScenario, notificationChannels, properties);
+ // This flow will be initiated only if the user has any verified channels.
+ notificationChannelDTOS = getNotificationChannelsResponseDTOList(
+ tenantDomain, notificationChannels);
+ UserRecoveryDataStore userRecoveryDataStore = JDBCRecoveryDataStore.getInstance();
+ // Get the existing RESEND_CONFIRMATION_CODE details if there is any.
+ UserRecoveryData recoveryDataDO = userRecoveryDataStore.loadWithoutCodeExpiryValidation(
+ user, recoveryScenario, RecoverySteps.RESEND_CONFIRMATION_CODE);
+
+ notificationChannelList = getNotificationChannelListForRecovery(notificationChannels);
+ recoveryCode = UUID.randomUUID().toString();
+ recoveryFlowId = UUID.randomUUID().toString();
+
+ if (Utils.reIssueExistingConfirmationCode(recoveryDataDO,
+ NotificationChannels.EMAIL_CHANNEL.getChannelType())) {
+ /* Update the existing RESEND_CONFIRMATION_CODE details with new code details without changing the
+ time created of the RESEND_CONFIRMATION_CODE. */
+ userRecoveryDataStore.invalidateWithoutChangeTimeCreated(recoveryDataDO.getSecret(), recoveryCode,
+ RecoverySteps.SEND_RECOVERY_INFORMATION, notificationChannelList);
+ } else {
+ if (usernameCombined.length() > 0) {
+ usernameCombined.append(",");
+ }
+ usernameCombined.append(username);
+ }
+ }
+ if (StringUtils.isBlank(usernameCombined.toString())) {
+ if (log.isDebugEnabled()) {
+ log.debug("No valid user found for the given claims");
+ }
+ throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_USER_FOUND,
+ null);
+ }
+ addRecoveryDataObject(usernameCombined.toString(), tenantDomain, recoveryFlowId, recoveryCode,
+ recoveryScenario,
+ notificationChannelList);
+
+ return buildUserRecoveryInformationResponseDTO(username, recoveryFlowId, recoveryCode,
+ notificationChannelDTOS);
+
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("No valid user found for the given claims");
+ }
+ throw Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_USER_FOUND, null);
+ }
+ }
+
/**
* Initiate the recovery flow for the user with matching claims.
*
diff --git a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java
index f661daec65..742155fe90 100644
--- a/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java
+++ b/components/org.wso2.carbon.identity.recovery/src/main/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImpl.java
@@ -37,6 +37,8 @@
import org.wso2.carbon.identity.recovery.IdentityRecoveryServerException;
import org.wso2.carbon.identity.recovery.RecoveryScenarios;
import org.wso2.carbon.identity.recovery.RecoverySteps;
+import org.wso2.carbon.identity.recovery.dto.NotificationChannelDTO;
+import org.wso2.carbon.identity.recovery.dto.RecoveryChannelInfoDTO;
import org.wso2.carbon.identity.recovery.dto.RecoveryInformationDTO;
import org.wso2.carbon.identity.recovery.dto.UsernameRecoverDTO;
import org.wso2.carbon.identity.recovery.internal.IdentityRecoveryServiceDataHolder;
@@ -55,6 +57,7 @@
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkConstants.AUDIT_FAILED;
@@ -134,9 +137,35 @@ public RecoveryInformationDTO initiate(Map claims, String tenant
Map metaProperties = new HashMap<>();
metaProperties.put(IdentityRecoveryConstants.MANAGE_NOTIFICATIONS_INTERNALLY_PROPERTY_KEY,
Boolean.toString(manageNotificationsInternally));
- recoveryInformationDTO.setRecoveryChannelInfoDTO(userAccountRecoveryManager
- .retrieveUserRecoveryInformation(claims, tenantDomain, RecoveryScenarios.USERNAME_RECOVERY,
- metaProperties));
+
+ boolean nonUniqueUsernameEnabled = Boolean.parseBoolean(IdentityUtil.getProperty(
+ IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_NON_UNIQUE_USERNAME));
+
+ RecoveryChannelInfoDTO recoveryChannelInfoDTO;
+ if (nonUniqueUsernameEnabled) {
+ recoveryChannelInfoDTO = userAccountRecoveryManager
+ .retrieveUsersRecoveryInformationForUsername(claims, tenantDomain, metaProperties);
+ } else {
+ recoveryChannelInfoDTO = userAccountRecoveryManager
+ .retrieveUserRecoveryInformation(claims, tenantDomain, RecoveryScenarios.USERNAME_RECOVERY,
+ metaProperties);
+ }
+
+ // Filtering the notification channel list.
+ List enabledNotificationChannelDTOs = new ArrayList<>();
+ for (NotificationChannelDTO notificationChannelDTO : recoveryChannelInfoDTO.getNotificationChannelDTOs()) {
+ if (isRecoveryChannelEnabled(notificationChannelDTO.getType(), tenantDomain)) {
+ enabledNotificationChannelDTOs.add(notificationChannelDTO);
+ }
+ }
+ recoveryChannelInfoDTO.setNotificationChannelDTOs(
+ enabledNotificationChannelDTOs.toArray(new NotificationChannelDTO[0]));
+ String username = recoveryChannelInfoDTO.getUsername();
+ String recoveryFlowId = recoveryChannelInfoDTO.getRecoveryFlowId();
+ recoveryInformationDTO.setUsername(username);
+ recoveryInformationDTO.setRecoveryFlowId(recoveryFlowId);
+ // Do not add recovery channel information if Notification based recovery is not enabled.
+ recoveryInformationDTO.setRecoveryChannelInfoDTO(recoveryChannelInfoDTO);
return recoveryInformationDTO;
}
@@ -263,9 +292,15 @@ private UsernameRecoverDTO buildUserNameRecoveryResponseDTO(User user, String no
// If notifications are externally managed, username needs to be sent with the request.
// Build username for external notification.
- String username =
- String.format(qualifiedUsernameRegexPattern, user.getUserName(), user.getTenantDomain());
- usernameRecoverDTO.setUsername(username);
+ StringBuilder usernameCombined = new StringBuilder();
+ String[] usernames = user.getUserName().split(",");
+ for (String username : usernames) {
+ if(usernameCombined.length() > 0) {
+ usernameCombined.append(",");
+ }
+ usernameCombined.append(String.format(qualifiedUsernameRegexPattern, username, user.getTenantDomain()));
+ }
+ usernameRecoverDTO.setUsername(usernameCombined.toString());
} else {
usernameRecoverDTO.setCode(
IdentityRecoveryConstants.SuccessEvents.SUCCESS_STATUS_CODE_USERNAME_INTERNALLY_NOTIFIED.getCode());
@@ -313,27 +348,36 @@ private void triggerNotification(User user, String notificationChannel, String e
Map metaProperties)
throws IdentityRecoveryException {
- HashMap properties = new HashMap<>();
- properties.put(IdentityEventConstants.EventProperty.USER_NAME, user.getUserName());
- properties.put(IdentityEventConstants.EventProperty.TENANT_DOMAIN, user.getTenantDomain());
- properties.put(IdentityEventConstants.EventProperty.USER_STORE_DOMAIN, user.getUserStoreDomain());
- properties.put(IdentityEventConstants.EventProperty.NOTIFICATION_CHANNEL, notificationChannel);
- if (metaProperties != null) {
- for (String key : metaProperties.keySet()) {
- String value = metaProperties.get(key);
- if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
- properties.put(key, value);
+ if (!isRecoveryChannelEnabled(notificationChannel, user.getTenantDomain())) {
+ throw Utils.handleClientException(
+ IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CHANNEL_ID, null);
+ }
+
+ String combinedUsernames = user.getUserName();
+ String[] usernames = combinedUsernames.split(",");
+ for (String username : usernames) {
+ HashMap properties = new HashMap<>();
+ properties.put(IdentityEventConstants.EventProperty.USER_NAME, username);
+ properties.put(IdentityEventConstants.EventProperty.TENANT_DOMAIN, user.getTenantDomain());
+ properties.put(IdentityEventConstants.EventProperty.USER_STORE_DOMAIN, user.getUserStoreDomain());
+ properties.put(IdentityEventConstants.EventProperty.NOTIFICATION_CHANNEL, notificationChannel);
+ if (metaProperties != null) {
+ for (String key : metaProperties.keySet()) {
+ String value = metaProperties.get(key);
+ if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
+ properties.put(key, value);
+ }
}
}
- }
- properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE,
- IdentityRecoveryConstants.NOTIFICATION_ACCOUNT_ID_RECOVERY);
- Event identityMgtEvent = new Event(eventName, properties);
- try {
- IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService().handleEvent(identityMgtEvent);
- } catch (IdentityEventException e) {
- throw Utils.handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_TRIGGER_NOTIFICATION,
- user.getUserName(), e);
+ properties.put(IdentityRecoveryConstants.TEMPLATE_TYPE,
+ IdentityRecoveryConstants.NOTIFICATION_ACCOUNT_ID_RECOVERY);
+ Event identityMgtEvent = new Event(eventName, properties);
+ try {
+ IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService().handleEvent(identityMgtEvent);
+ } catch (IdentityEventException e) {
+ throw Utils.handleServerException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_TRIGGER_NOTIFICATION,
+ user.getUserName(), e);
+ }
}
}
@@ -467,4 +511,47 @@ private void auditUserNameRecovery(String action, Map claims, St
}
Utils.createAuditMessage(action, target, dataObject, result);
}
+
+ private boolean isRecoveryChannelEnabled(String notificationChannelType, String tenantDomain)
+ throws IdentityRecoveryServerException {
+
+ if (NotificationChannels.EMAIL_CHANNEL.getChannelType().equals(notificationChannelType)) {
+ return isEmailBasedRecoveryEnabled(tenantDomain);
+ } else if (NotificationChannels.SMS_CHANNEL.getChannelType().equals(notificationChannelType)) {
+ return isSMSBasedRecoveryEnabled(tenantDomain);
+ }
+ return false;
+ }
+
+ private boolean isEmailBasedRecoveryEnabled(String tenantDomain) throws IdentityRecoveryServerException {
+
+ try {
+ return Boolean.parseBoolean(
+ Utils.getRecoveryConfigs(
+ IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ tenantDomain));
+ } catch (IdentityRecoveryServerException e) {
+ // Prepend scenario to the thrown exception.
+ String errorCode = Utils
+ .prependOperationScenarioToErrorCode(IdentityRecoveryConstants.USER_NAME_RECOVERY,
+ e.getErrorCode());
+ throw Utils.handleServerException(errorCode, e.getMessage(), null);
+ }
+ }
+
+ private boolean isSMSBasedRecoveryEnabled(String tenantDomain) throws IdentityRecoveryServerException {
+
+ try {
+ return Boolean.parseBoolean(
+ Utils.getRecoveryConfigs(
+ IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ tenantDomain));
+ } catch (IdentityRecoveryServerException e) {
+ // Prepend scenario to the thrown exception.
+ String errorCode = Utils
+ .prependOperationScenarioToErrorCode(IdentityRecoveryConstants.USER_NAME_RECOVERY,
+ e.getErrorCode());
+ throw Utils.handleServerException(errorCode, e.getMessage(), null);
+ }
+ }
}
diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java
index beaf4d247a..8975a13b89 100644
--- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java
+++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/connector/RecoveryConfigImplTest.java
@@ -18,8 +18,13 @@
package org.wso2.carbon.identity.recovery.connector;
import org.apache.commons.lang.StringUtils;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import org.wso2.carbon.identity.application.common.model.Property;
+import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.governance.IdentityGovernanceException;
import org.wso2.carbon.identity.governance.IdentityMgtConstants;
import org.wso2.carbon.identity.recovery.IdentityRecoveryConstants;
@@ -32,6 +37,7 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
+import static org.wso2.carbon.identity.governance.IdentityGovernanceUtil.getPropertyObject;
/**
* This class does unit test coverage for RecoveryConfigImpl class.
@@ -105,6 +111,10 @@ public void testGetPropertyNameMapping() {
nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.QUESTION_MIN_NO_ANSWER, "Number of " +
"questions required for password recovery");
nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE, "Username recovery");
+ nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ "Notification based username recovery via EMAIL");
+ nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ "Notification based username recovery via SMS");
nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
"Enable reCaptcha for username recovery");
nameMappingExpected.put(IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME,
@@ -206,6 +216,8 @@ public void testGetPropertyNames() {
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_ENABLE);
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_MAX_FAILED_ATTEMPTS);
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE);
+ propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE);
+ propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE);
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE);
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE);
propertiesExpected.add(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS);
@@ -241,6 +253,8 @@ public void testGetDefaultPropertyValues() throws IdentityGovernanceException {
String testEnableRecoveryQuestionPasswordReCaptcha = "true";
String testRecoveryQuestionPasswordReCaptchaMaxFailedAttempts = "2";
String testEnableUsernameRecovery = "false";
+ String testEnableUsernameRecoveryEmail = "false";
+ String testEnableUsernameRecoverySMS = "false";
String testEnableNotificationInternallyManage = "true";
String testExpiryTime = "1440";
String testExpiryTimeSMSOTP = "1";
@@ -286,6 +300,10 @@ public void testGetDefaultPropertyValues() throws IdentityGovernanceException {
testRecoveryQuestionPasswordReCaptchaMaxFailedAttempts);
defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE,
testEnableUsernameRecovery);
+ defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ testEnableUsernameRecoveryEmail);
+ defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ testEnableUsernameRecoverySMS);
defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
enableUsernameRecoveryReCaptcha);
defaultPropertiesExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE,
@@ -327,6 +345,199 @@ public void testGetDefaultPropertyValues() throws IdentityGovernanceException {
assertEquals(defaultProperties, defaultPropertiesExpected, "Maps are not equal");
}
+ @Test(dataProvider = "defaultPropertyNames")
+ public void testGetDefaultPropertyValuesConditional(String property) throws IdentityGovernanceException{
+
+ String tenantDomain = "admin";
+ String testPropertyValue = "testValue";
+ try(MockedStatic identityUtilMockedStatic = Mockito.mockStatic(IdentityUtil.class)) {
+ identityUtilMockedStatic.when(() -> IdentityUtil.getProperty(property)).thenReturn(testPropertyValue);
+
+ Properties properties = recoveryConfigImpl.getDefaultPropertyValues(tenantDomain);
+ assertEquals(testPropertyValue, properties.getProperty(property));
+ }
+ }
+
+ @DataProvider(name="defaultPropertyNames")
+ public Object[][] buildPropertyNameList(){
+
+ return new Object[][] {
+ {IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_BASED_PW_RECOVERY},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SEND_OTP_IN_EMAIL},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_UPPERCASE_CHARACTERS_IN_OTP},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_LOWERCASE_CHARACTERS_IN_OTP},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_NUMBERS_IN_OTP},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_OTP_LENGTH},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_RECAPTCHA_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.QUESTION_BASED_PW_RECOVERY},
+ {IdentityRecoveryConstants.ConnectorConfig.QUESTION_MIN_NO_ANSWER},
+ {IdentityRecoveryConstants.ConnectorConfig.CHALLENGE_QUESTION_ANSWER_REGEX},
+ {IdentityRecoveryConstants.ConnectorConfig.ENFORCE_CHALLENGE_QUESTION_ANSWER_UNIQUENESS},
+ {IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_MAX_FAILED_ATTEMPTS},
+ {IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE},
+ {IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_EXPIRY_TIME},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_REGEX},
+ {IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS},
+ {IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_SECURITY_START},
+ {IdentityRecoveryConstants.ConnectorConfig.FORCE_ADD_PW_RECOVERY_QUESTION},
+ {IdentityRecoveryConstants.ConnectorConfig.FORCE_MIN_NO_QUESTION_ANSWERED},
+ {IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET},
+ {IdentityRecoveryConstants.ConnectorConfig.RECOVERY_NOTIFICATION_PASSWORD_MAX_FAILED_ATTEMPTS},
+ {IdentityRecoveryConstants.ConnectorConfig.RECOVERY_NOTIFICATION_PASSWORD_MAX_RESEND_ATTEMPTS},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_EMAIL_LINK_ENABLE},
+ {IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_ENABLE}
+ };
+ }
+
+ @Test
+ public void testGetMetaData() {
+
+ Map metaDataExpected = new HashMap<>();
+
+ Property testNotificationBasedPasswordRecovery =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoverySendOtpInEmail =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoveryUseUppercaseCharactersInOtp =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoveryUseLowercaseCharactersInOtp =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoveryUseNumbersInOtp =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoveryOtpLength =
+ getPropertyObject(IdentityMgtConstants.DataTypes.STRING.getValue());
+ Property testPasswordRecoveryRecaptchaEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testQuestionBasedPwRecovery =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testQuestionMinNoAnswer =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testEnforceChallengeQuestionAnswerUniqueness =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testRecoveryQuestionPasswordRecaptchaEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testRecoveryQuestionPasswordRecaptchaMaxFailedAttempts =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testUsernameRecoveryEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testUsernameRecoveryEmailEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testUsernameRecoverySmsEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testUsernameRecoveryRecaptchaEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testNotificationInternallyManage =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testExpiryTime =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testPasswordRecoverySmsOtpExpiryTime =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testNotificationSendRecoveryNotificationSuccess =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testNotificationSendRecoverySecurityStart =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testForceAddPwRecoveryQuestion =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testForceMinNoQuestionAnswered =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testEnableAutoLoginAfterPasswordReset =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testChallengeQuestionAnswerRegex =
+ getPropertyObject(IdentityMgtConstants.DataTypes.STRING.getValue());
+ Property testRecoveryCallbackRegex =
+ getPropertyObject(IdentityMgtConstants.DataTypes.STRING.getValue());
+ Property testRecoveryNotificationPasswordMaxFailedAttempts =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testRecoveryNotificationPasswordMaxResendAttempts =
+ getPropertyObject(IdentityMgtConstants.DataTypes.INTEGER.getValue());
+ Property testPasswordRecoveryEmailLinkEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoverySmsOtpEnable =
+ getPropertyObject(IdentityMgtConstants.DataTypes.BOOLEAN.getValue());
+ Property testPasswordRecoverySmsOtpRegex =
+ getPropertyObject(IdentityMgtConstants.DataTypes.STRING.getValue());
+
+ // Adding properties to the expected metadata map.
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_BASED_PW_RECOVERY,
+ testNotificationBasedPasswordRecovery);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SEND_OTP_IN_EMAIL,
+ testPasswordRecoverySendOtpInEmail);
+ metaDataExpected.put(
+ IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_UPPERCASE_CHARACTERS_IN_OTP,
+ testPasswordRecoveryUseUppercaseCharactersInOtp);
+ metaDataExpected.put(
+ IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_LOWERCASE_CHARACTERS_IN_OTP,
+ testPasswordRecoveryUseLowercaseCharactersInOtp);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_USE_NUMBERS_IN_OTP,
+ testPasswordRecoveryUseNumbersInOtp);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_OTP_LENGTH,
+ testPasswordRecoveryOtpLength);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_RECAPTCHA_ENABLE,
+ testPasswordRecoveryRecaptchaEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.QUESTION_BASED_PW_RECOVERY,
+ testQuestionBasedPwRecovery);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.QUESTION_MIN_NO_ANSWER, testQuestionMinNoAnswer);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.ENFORCE_CHALLENGE_QUESTION_ANSWER_UNIQUENESS,
+ testEnforceChallengeQuestionAnswerUniqueness);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_ENABLE,
+ testRecoveryQuestionPasswordRecaptchaEnable);
+ metaDataExpected.put(
+ IdentityRecoveryConstants.ConnectorConfig.RECOVERY_QUESTION_PASSWORD_RECAPTCHA_MAX_FAILED_ATTEMPTS,
+ testRecoveryQuestionPasswordRecaptchaMaxFailedAttempts);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_ENABLE,
+ testUsernameRecoveryEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_EMAIL_ENABLE,
+ testUsernameRecoveryEmailEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE,
+ testUsernameRecoverySmsEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_RECAPTCHA_ENABLE,
+ testUsernameRecoveryRecaptchaEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_INTERNALLY_MANAGE,
+ testNotificationInternallyManage);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.EXPIRY_TIME, testExpiryTime);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_EXPIRY_TIME,
+ testPasswordRecoverySmsOtpExpiryTime);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_NOTIFICATION_SUCCESS,
+ testNotificationSendRecoveryNotificationSuccess);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.NOTIFICATION_SEND_RECOVERY_SECURITY_START,
+ testNotificationSendRecoverySecurityStart);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.FORCE_ADD_PW_RECOVERY_QUESTION,
+ testForceAddPwRecoveryQuestion);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.FORCE_MIN_NO_QUESTION_ANSWERED,
+ testForceMinNoQuestionAnswered);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.ENABLE_AUTO_LGOIN_AFTER_PASSWORD_RESET,
+ testEnableAutoLoginAfterPasswordReset);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.CHALLENGE_QUESTION_ANSWER_REGEX,
+ testChallengeQuestionAnswerRegex);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.RECOVERY_CALLBACK_REGEX,
+ testRecoveryCallbackRegex);
+ metaDataExpected.put(
+ IdentityRecoveryConstants.ConnectorConfig.RECOVERY_NOTIFICATION_PASSWORD_MAX_FAILED_ATTEMPTS,
+ testRecoveryNotificationPasswordMaxFailedAttempts);
+ metaDataExpected.put(
+ IdentityRecoveryConstants.ConnectorConfig.RECOVERY_NOTIFICATION_PASSWORD_MAX_RESEND_ATTEMPTS,
+ testRecoveryNotificationPasswordMaxResendAttempts);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_EMAIL_LINK_ENABLE,
+ testPasswordRecoveryEmailLinkEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_ENABLE,
+ testPasswordRecoverySmsOtpEnable);
+ metaDataExpected.put(IdentityRecoveryConstants.ConnectorConfig.PASSWORD_RECOVERY_SMS_OTP_REGEX,
+ testPasswordRecoverySmsOtpRegex);
+
+ // Fetching actual metadata from the method.
+ Map metaData = recoveryConfigImpl.getMetaData();
+
+ // Asserting that the expected and actual maps are equal.
+ assertEquals(metaData, metaDataExpected);
+ }
+
+
@Test
public void testGetDefaultProperties() throws IdentityGovernanceException {
diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManagerTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManagerTest.java
index 46daf5e741..87e96a8801 100644
--- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManagerTest.java
+++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/UserAccountRecoveryManagerTest.java
@@ -19,6 +19,7 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
+import org.mockito.Spy;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
@@ -65,11 +66,14 @@
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
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;
@@ -88,9 +92,14 @@
*/
public class UserAccountRecoveryManagerTest {
+ private static final String TENANT_DOMAIN = "carbon.super";
+
@InjectMocks
private UserAccountRecoveryManager userAccountRecoveryManager;
+ @Spy
+ private UserAccountRecoveryManager userAccountRecoveryManagerSpy;
+
@Mock
private IdentityRecoveryServiceDataHolder identityRecoveryServiceDataHolder;
@@ -175,6 +184,80 @@ public void testRetrieveUserRecoveryInformation() throws Exception {
testGetUserWithNotificationsInternallyManaged();
}
+ /**
+ * Test retrieve user recovery information for username recovery.
+ *
+ * @throws Exception Error while getting user recovery information
+ */
+ @Test
+ public void testRetrieveUsersRecoveryInformationForUsername() throws Exception {
+
+ ArrayList userList = new ArrayList<>();
+ userList.add(new org.wso2.carbon.user.core.common.User(UUID.randomUUID().toString(), "user1", "user1"));
+ userList.add(new org.wso2.carbon.user.core.common.User(UUID.randomUUID().toString(), "user2", "user2"));
+
+ doReturn(userList).when(userAccountRecoveryManagerSpy).getUserListByClaims(any(), any());
+
+ mockBuildUser();
+ mockRecoveryConfigs(true);
+ mockUserstoreManager();
+ mockJDBCRecoveryDataStore();
+ mockClaimMetadataManagementService();
+
+ when(abstractUserStoreManager
+ .getUserClaimValues(anyString(), any(String[].class), eq(null))).thenReturn(userClaims);
+ when(IdentityRecoveryServiceDataHolder.getInstance().getIdentityEventService())
+ .thenReturn(identityEventService);
+ doNothing().when(identityEventService).handleEvent(any());
+
+ RecoveryChannelInfoDTO result = userAccountRecoveryManagerSpy.retrieveUsersRecoveryInformationForUsername(null,
+ TENANT_DOMAIN, null);
+
+ assertEquals(result.getNotificationChannelDTOs().length, 2);
+ assertEquals(result.getUsername(), "user2");
+ }
+
+ /**
+ * Test throw no user found error in retrieve user recovery information for username recovery.
+ *
+ * @throws IdentityRecoveryException Exception Error while getting user recovery information
+ */
+ @Test(expectedExceptions = IdentityRecoveryClientException.class)
+ public void testThrowNoUserFoundRetrieveUserRecoveryInformationForUsername() throws IdentityRecoveryException {
+
+ ArrayList resultedUserList = new ArrayList<>();
+ doReturn(resultedUserList).when(userAccountRecoveryManagerSpy).getUserListByClaims(any(), any());
+ when(Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_USER_FOUND, null)).
+ thenReturn(new IdentityRecoveryClientException(
+ null, null, null));
+ userAccountRecoveryManagerSpy.retrieveUsersRecoveryInformationForUsername(null, TENANT_DOMAIN, null);
+ }
+
+ /**
+ * Test throw no user found error when combined usernames become empty in retrieving user recovery information for
+ * username recovery.
+ *
+ * @throws IdentityRecoveryException Exception Error while getting user recovery information
+ */
+ @Test(expectedExceptions = IdentityRecoveryClientException.class)
+ public void testThrowNoUserFoundEmptyUsernamesRetrieveUserRecoveryInformationForUsername()
+ throws IdentityRecoveryException {
+
+ mockBuildUser();
+ ArrayList resultedUserList = new ArrayList<>();
+ resultedUserList.add(new org.wso2.carbon.user.core.common.User(UUID.randomUUID().toString(), "user1", "user1"));
+ doReturn(resultedUserList).when(userAccountRecoveryManagerSpy).getUserListByClaims(any(), any());
+
+ when(Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_USER_FOUND, null)).
+ thenReturn(new IdentityRecoveryClientException(
+ null, null, null));
+ when(Utils.handleClientException(nullable(String.class), anyString(), anyString())).
+ thenReturn(new IdentityRecoveryClientException(
+ null, null, null));
+ mockedUtils.when(() -> Utils.isAccountDisabled(any(User.class))).thenReturn(true);
+ userAccountRecoveryManagerSpy.retrieveUsersRecoveryInformationForUsername(null, TENANT_DOMAIN, null);
+ }
+
/**
* Tests that a NullPointerException is thrown during user recovery when a UserStoreException
* occurs.
diff --git a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImplTest.java b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImplTest.java
index d12714ab79..e96fb12254 100644
--- a/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImplTest.java
+++ b/components/org.wso2.carbon.identity.recovery/src/test/java/org/wso2/carbon/identity/recovery/internal/service/impl/username/UsernameRecoveryManagerImplTest.java
@@ -25,10 +25,14 @@
import org.wso2.carbon.identity.application.common.model.User;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.event.services.IdentityEventService;
+import org.wso2.carbon.identity.governance.service.notification.NotificationChannels;
import org.wso2.carbon.identity.recovery.IdentityRecoveryClientException;
import org.wso2.carbon.identity.recovery.IdentityRecoveryConstants;
import org.wso2.carbon.identity.recovery.IdentityRecoveryException;
+import org.wso2.carbon.identity.recovery.RecoveryScenarios;
import org.wso2.carbon.identity.recovery.RecoverySteps;
+import org.wso2.carbon.identity.recovery.dto.NotificationChannelDTO;
+import org.wso2.carbon.identity.recovery.dto.RecoveryChannelInfoDTO;
import org.wso2.carbon.identity.recovery.dto.RecoveryInformationDTO;
import org.wso2.carbon.identity.recovery.dto.UsernameRecoverDTO;
import org.wso2.carbon.identity.recovery.internal.IdentityRecoveryServiceDataHolder;
@@ -42,15 +46,19 @@
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
/**
* Test class for UsernameRecoveryManagerImpl.
@@ -152,9 +160,10 @@ public void testConfigValidation() throws IdentityRecoveryException {
*/
@DataProvider
public Object[][] channelIDProvider() {
- return new Object[][] {
- { null },
- { "0" }
+
+ return new Object[][]{
+ {null},
+ {"0"}
};
}
@@ -188,7 +197,8 @@ public void testInvalidateRecoveryCode() throws IdentityRecoveryException {
Map properties = new HashMap<>();
properties.put("useLegacyAPI", FALSE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
@@ -207,7 +217,8 @@ public void testInvalidateRecoveryCodeWithException() throws IdentityRecoveryExc
Map properties = new HashMap<>();
properties.put("useLegacyAPI", FALSE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRecoveryFlowId()).thenReturn("FlowID");
@@ -227,7 +238,8 @@ public void testExtractChannelDetails() throws IdentityRecoveryException {
Map properties = new HashMap<>();
properties.put("useLegacyAPI", FALSE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRemainingSetIds()).thenReturn("123");
@@ -249,19 +261,21 @@ public void testNotifyUser() throws IdentityRecoveryException {
Map properties = new HashMap<>();
properties.put("useLegacyAPI", FALSE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRemainingSetIds()).thenReturn("EXTERNAL,EXTERNAL");
when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
User mockUser = new User();
- mockUser.setUserName("KD123");
+ mockUser.setUserName("user1,user2");
mockUser.setTenantDomain(TENANT_DOMAIN);
when(mockUserRecoveryData.getUser()).thenReturn(mockUser);
when(Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_INVALID_CHANNEL_ID, null))
.thenReturn(new IdentityRecoveryClientException(null));
UsernameRecoverDTO code = usernameRecoveryManager.notify(recoveryCode, "2", TENANT_DOMAIN, properties);
assertEquals(code.getCode(), "UNR-02002");
+ assertEquals(code.getUsername(), String.format("user1@%s,user2@%s", TENANT_DOMAIN, TENANT_DOMAIN));
}
/**
@@ -277,7 +291,8 @@ public void testNotifyUserException() throws IdentityRecoveryException {
Map properties = new HashMap<>();
properties.put("useLegacyAPI", FALSE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRemainingSetIds()).thenReturn("SMS,SMS");
@@ -289,7 +304,8 @@ public void testNotifyUserException() throws IdentityRecoveryException {
when(mockUserRecoveryData.getUser()).thenReturn(mockUser);
UsernameRecoverDTO result = usernameRecoveryManager.notify(recoveryCode, "2", TENANT_DOMAIN, properties);
assertEquals(result.getCode(), "UNR-02001");
- assertEquals(result.getMessage(), "Username recovery information sent via user preferred notification channel.");
+ assertEquals(result.getMessage(),
+ "Username recovery information sent via user preferred notification channel.");
}
/**
@@ -307,7 +323,8 @@ public void testCallbackURLValidation() throws IdentityRecoveryException {
properties.put("useLegacyAPI", TRUE);
properties.put(IdentityRecoveryConstants.CALLBACK, callbackURL);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRemainingSetIds()).thenReturn("SMS,SMS");
@@ -338,13 +355,15 @@ public void testCallbackURLDecoding() throws IdentityRecoveryException {
properties.put("useLegacyAPI", TRUE);
properties.put(IdentityRecoveryConstants.CALLBACK, callbackURL);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserRecoveryData(recoveryCode, RecoverySteps.SEND_RECOVERY_INFORMATION))
.thenReturn(mockUserRecoveryData);
when(mockUserRecoveryData.getRemainingSetIds()).thenReturn("SMS,SMS");
when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
when(Utils.resolveEventName(anyString())).thenReturn("TRIGGER_SMS_NOTIFICATION_LOCAL");
- mockURLDecoder.when(() -> URLDecoder.decode(anyString(), anyString())).thenThrow(new UnsupportedEncodingException());
+ mockURLDecoder.when(() -> URLDecoder.decode(anyString(), anyString()))
+ .thenThrow(new UnsupportedEncodingException());
usernameRecoveryManager.notify(recoveryCode, "2", TENANT_DOMAIN, properties);
}
@@ -366,7 +385,8 @@ public void testInitiateRecoveryWithNullUsername() throws IdentityRecoveryExcept
properties.put("useLegacyAPI", TRUE);
when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserListByClaims(null, TENANT_DOMAIN)).thenReturn(userList);
when(IdentityUtil.getProperty(anyString())).thenReturn(TRUE);
when(Utils.handleClientException(IdentityRecoveryConstants.ErrorMessages.ERROR_CODE_NO_USER_FOUND, null))
@@ -391,7 +411,8 @@ public void testInitiateRecoveryValidUsername() throws IdentityRecoveryException
properties.put("useLegacyAPI", TRUE);
when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
mockedJDBCRecoveryDataStore.when(JDBCRecoveryDataStore::getInstance).thenReturn(mockUserRecoveryDataStore);
- mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance).thenReturn(mockUserAccountRecoveryManager);
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
when(mockUserAccountRecoveryManager.getUserListByClaims(null, TENANT_DOMAIN)).thenReturn(userList);
when(IdentityUtil.getProperty(anyString())).thenReturn(TRUE);
when(Utils.isNotificationsInternallyManaged(TENANT_DOMAIN, properties)).thenReturn(true);
@@ -401,6 +422,71 @@ public void testInitiateRecoveryValidUsername() throws IdentityRecoveryException
assertEquals(result.getUsername(), "testUser");
}
+ /**
+ * Test to initiate recovery with useLegacyAPI false.
+ *
+ * @throws IdentityRecoveryException if an error occurs during initiation.
+ */
+ @Test
+ public void testInitiateRecoveryWithUseLegacyAPIFalse() throws IdentityRecoveryException {
+
+ String TEST_USER = "testUser";
+ mockIdentityEventService();
+ RecoveryChannelInfoDTO recoveryChannelInfoDTO = new RecoveryChannelInfoDTO();
+ recoveryChannelInfoDTO.setUsername("testUser");
+ List notificationChannelDTOList = new ArrayList<>();
+
+ NotificationChannelDTO notificationChannelDTO1 = new NotificationChannelDTO();
+ notificationChannelDTO1.setId(1);
+ notificationChannelDTO1.setType(NotificationChannels.EMAIL_CHANNEL.getChannelType());
+
+ NotificationChannelDTO notificationChannelDTO2 = new NotificationChannelDTO();
+ notificationChannelDTO2.setId(2);
+ notificationChannelDTO2.setType(NotificationChannels.SMS_CHANNEL.getChannelType());
+
+ notificationChannelDTOList.add(notificationChannelDTO1);
+ notificationChannelDTOList.add(notificationChannelDTO2);
+ recoveryChannelInfoDTO.setNotificationChannelDTOs(
+ notificationChannelDTOList.toArray(new NotificationChannelDTO[0]));
+
+ Map properties = new HashMap<>();
+ when(Utils.getRecoveryConfigs(anyString(), anyString())).thenReturn(TRUE);
+
+ // Case 1: When Recovery.Notification.Username.NonUniqueUsername is enabled.
+ when(IdentityUtil.getProperty(
+ eq(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_NON_UNIQUE_USERNAME))).thenReturn(TRUE);
+
+ mockedRecoveryManagerStatic.when(UserAccountRecoveryManager::getInstance)
+ .thenReturn(mockUserAccountRecoveryManager);
+ when(mockUserAccountRecoveryManager.retrieveUsersRecoveryInformationForUsername(eq(null), eq(TENANT_DOMAIN),
+ any())).thenReturn(recoveryChannelInfoDTO);
+ when(Utils.isNotificationsInternallyManaged(TENANT_DOMAIN, properties)).thenReturn(true);
+ RecoveryInformationDTO result = usernameRecoveryManager.initiate(null, TENANT_DOMAIN, properties);
+ assertEquals(result.getRecoveryChannelInfoDTO().getUsername(), TEST_USER);
+ assertEquals(result.getRecoveryChannelInfoDTO().getNotificationChannelDTOs().length, 2);
+
+ // Case 2: When Recovery.Notification.Username.NonUniqueUsername is disabled.
+ when(IdentityUtil.getProperty(
+ eq(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_NON_UNIQUE_USERNAME))).thenReturn(FALSE);
+ when(mockUserAccountRecoveryManager.retrieveUserRecoveryInformation(eq(null), eq(TENANT_DOMAIN),
+ eq(RecoveryScenarios.USERNAME_RECOVERY),
+ any())).thenReturn(recoveryChannelInfoDTO);
+ result = usernameRecoveryManager.initiate(null, TENANT_DOMAIN, properties);
+ assertEquals(result.getRecoveryChannelInfoDTO().getUsername(), TEST_USER);
+ assertEquals(result.getRecoveryChannelInfoDTO().getNotificationChannelDTOs().length, 2);
+
+ // Case 3: Disable the sms channel.
+ when(Utils.getRecoveryConfigs(eq(IdentityRecoveryConstants.ConnectorConfig.USERNAME_RECOVERY_SMS_ENABLE),
+ eq(TENANT_DOMAIN)))
+ .thenReturn(FALSE);
+ result = usernameRecoveryManager.initiate(null, TENANT_DOMAIN, properties);
+ assertEquals(result.getRecoveryChannelInfoDTO().getUsername(), TEST_USER);
+ assertEquals(result.getRecoveryChannelInfoDTO().getNotificationChannelDTOs().length, 1);
+ assertEquals(result.getRecoveryChannelInfoDTO().getNotificationChannelDTOs()[0].getType(),
+ NotificationChannels.EMAIL_CHANNEL.getChannelType());
+
+ }
+
/**
* Mock the IdentityEventService.
*/
diff --git a/components/org.wso2.carbon.identity.tenant.resource.manager/pom.xml b/components/org.wso2.carbon.identity.tenant.resource.manager/pom.xml
index 248303df66..837321a5f5 100644
--- a/components/org.wso2.carbon.identity.tenant.resource.manager/pom.xml
+++ b/components/org.wso2.carbon.identity.tenant.resource.manager/pom.xml
@@ -22,7 +22,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.user.endpoint/pom.xml b/components/org.wso2.carbon.identity.user.endpoint/pom.xml
index b145ad5f2e..9e306cd47d 100644
--- a/components/org.wso2.carbon.identity.user.endpoint/pom.xml
+++ b/components/org.wso2.carbon.identity.user.endpoint/pom.xml
@@ -5,7 +5,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.user.export.core/pom.xml b/components/org.wso2.carbon.identity.user.export.core/pom.xml
index 0f5cf3720e..c1006ebc0d 100644
--- a/components/org.wso2.carbon.identity.user.export.core/pom.xml
+++ b/components/org.wso2.carbon.identity.user.export.core/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.user.onboard.core.service/pom.xml b/components/org.wso2.carbon.identity.user.onboard.core.service/pom.xml
index bc8083479c..55fca5ec39 100644
--- a/components/org.wso2.carbon.identity.user.onboard.core.service/pom.xml
+++ b/components/org.wso2.carbon.identity.user.onboard.core.service/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/components/org.wso2.carbon.identity.user.rename.core/pom.xml b/components/org.wso2.carbon.identity.user.rename.core/pom.xml
index 40dc8acb52..6eeccf4dbb 100644
--- a/components/org.wso2.carbon.identity.user.rename.core/pom.xml
+++ b/components/org.wso2.carbon.identity.user.rename.core/pom.xml
@@ -20,7 +20,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/features/org.wso2.carbon.identity.account.suspension.notification.task.server.feature/pom.xml b/features/org.wso2.carbon.identity.account.suspension.notification.task.server.feature/pom.xml
index dd52e6fda3..3617cd650e 100644
--- a/features/org.wso2.carbon.identity.account.suspension.notification.task.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.account.suspension.notification.task.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.auth.attribute.handler.server.feature/pom.xml b/features/org.wso2.carbon.identity.auth.attribute.handler.server.feature/pom.xml
index 648f8f6403..2e7de58bd1 100644
--- a/features/org.wso2.carbon.identity.auth.attribute.handler.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.auth.attribute.handler.server.feature/pom.xml
@@ -22,7 +22,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.captcha.server.feature/pom.xml b/features/org.wso2.carbon.identity.captcha.server.feature/pom.xml
index 5e48319248..0966762aab 100644
--- a/features/org.wso2.carbon.identity.captcha.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.captcha.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.governance.feature/pom.xml b/features/org.wso2.carbon.identity.governance.feature/pom.xml
index a00894cee4..07e9141350 100644
--- a/features/org.wso2.carbon.identity.governance.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.governance.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/features/org.wso2.carbon.identity.governance.server.feature/pom.xml b/features/org.wso2.carbon.identity.governance.server.feature/pom.xml
index 546ff249aa..53e9fad054 100644
--- a/features/org.wso2.carbon.identity.governance.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.governance.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.idle.account.identification.server.feature/pom.xml b/features/org.wso2.carbon.identity.idle.account.identification.server.feature/pom.xml
index b31545df3c..ff942cb533 100644
--- a/features/org.wso2.carbon.identity.idle.account.identification.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.idle.account.identification.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.multi.attribute.login.service.server.feature/pom.xml b/features/org.wso2.carbon.identity.multi.attribute.login.service.server.feature/pom.xml
index 5088d0b8cd..d09bcc4b6e 100644
--- a/features/org.wso2.carbon.identity.multi.attribute.login.service.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.multi.attribute.login.service.server.feature/pom.xml
@@ -3,7 +3,7 @@
identity-governance
org.wso2.carbon.identity.governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/features/org.wso2.carbon.identity.password.expiry.server.feature/pom.xml b/features/org.wso2.carbon.identity.password.expiry.server.feature/pom.xml
index 505d44cc5e..92166f19a3 100644
--- a/features/org.wso2.carbon.identity.password.expiry.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.password.expiry.server.feature/pom.xml
@@ -22,7 +22,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.password.history.server.feature/pom.xml b/features/org.wso2.carbon.identity.password.history.server.feature/pom.xml
index 9348f7df5d..e905f73a47 100644
--- a/features/org.wso2.carbon.identity.password.history.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.password.history.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.password.policy.server.feature/pom.xml b/features/org.wso2.carbon.identity.password.policy.server.feature/pom.xml
index 25765a7c6d..06acdef087 100644
--- a/features/org.wso2.carbon.identity.password.policy.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.password.policy.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.piicontroller.server.feature/pom.xml b/features/org.wso2.carbon.identity.piicontroller.server.feature/pom.xml
index 0147cce549..c12c9e2b19 100644
--- a/features/org.wso2.carbon.identity.piicontroller.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.piicontroller.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.recovery.server.feature/pom.xml b/features/org.wso2.carbon.identity.recovery.server.feature/pom.xml
index 05c088bdcf..abdbb6d4f8 100644
--- a/features/org.wso2.carbon.identity.recovery.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.recovery.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/features/org.wso2.carbon.identity.tenant.resource.manager.feature/pom.xml b/features/org.wso2.carbon.identity.tenant.resource.manager.feature/pom.xml
index 8e424251d1..3eee94c363 100644
--- a/features/org.wso2.carbon.identity.tenant.resource.manager.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.tenant.resource.manager.feature/pom.xml
@@ -20,7 +20,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/features/org.wso2.carbon.identity.user.onboard.core.service.feature/pom.xml b/features/org.wso2.carbon.identity.user.onboard.core.service.feature/pom.xml
index cb91d0a0a9..40d1352cfd 100644
--- a/features/org.wso2.carbon.identity.user.onboard.core.service.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.user.onboard.core.service.feature/pom.xml
@@ -20,7 +20,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
../../pom.xml
diff --git a/features/org.wso2.carbon.identity.user.server.feature/pom.xml b/features/org.wso2.carbon.identity.user.server.feature/pom.xml
index 38068077d6..60b4ecee09 100644
--- a/features/org.wso2.carbon.identity.user.server.feature/pom.xml
+++ b/features/org.wso2.carbon.identity.user.server.feature/pom.xml
@@ -21,7 +21,7 @@
org.wso2.carbon.identity.governance
identity-governance
../../pom.xml
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
diff --git a/pom.xml b/pom.xml
index af1bb0ad75..8cd85213df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
org.wso2.carbon.identity.governance
identity-governance
- 1.11.15-SNAPSHOT
+ 1.11.17-SNAPSHOT
4.0.0
pom
WSO2 Carbon - Governance Module