From 5f716bc124226f6ee64a8074a774bfcaa7db9ad9 Mon Sep 17 00:00:00 2001 From: Afra Hussaindeen Date: Fri, 8 Nov 2024 11:22:13 +0530 Subject: [PATCH] Add support for claim-wise uniqueness validation --- .../claim/metadata/mgt/dao/ClaimDAO.java | 69 ++++++++++++++ .../claim/metadata/mgt/dao/LocalClaimDAO.java | 73 ++++++++++++++- .../metadata/mgt/util/ClaimConstants.java | 12 +++ .../metadata/mgt/util/ClaimMetadataUtils.java | 18 ++++ .../claim/metadata/mgt/util/SQLConstants.java | 2 + ...UniqueClaimUserOperationEventListener.java | 89 +++++++++++++++---- .../resources/identity.xml | 3 + .../resources/identity.xml.j2 | 9 ++ 8 files changed, 257 insertions(+), 18 deletions(-) diff --git a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/ClaimDAO.java b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/ClaimDAO.java index 485202f6612a..226c93494ac5 100644 --- a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/ClaimDAO.java +++ b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/ClaimDAO.java @@ -31,6 +31,7 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import java.util.Set; import static org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants.ErrorMessage.ERROR_CODE_MAPPED_TO_INVALID_LOCAL_CLAIM_URI; @@ -232,4 +233,72 @@ protected void deleteClaimProperties(Connection connection, int claimId, int ten throw new ClaimMetadataException("Error while deleting claim properties", e); } } + + /** + * Deletes a set of claim properties. + * + * @param connection Database connection + * @param claimId ID of the claim + * @param claimPropertyNames Names of the claim properties to be deleted + * @param tenantId Tenant ID + * @throws ClaimMetadataException if deletion fails + */ + protected void deleteClaimProperties(Connection connection, int claimId, + Set claimPropertyNames, int tenantId) throws ClaimMetadataException { + + String query = SQLConstants.DELETE_CLAIM_PROPERTY_BY_NAME; + try (PreparedStatement prepStmt = connection.prepareStatement(query)) { + for (String propertyName : claimPropertyNames) { + prepStmt.setInt(1, claimId); + prepStmt.setString(2, propertyName); + prepStmt.setInt(3, tenantId); + prepStmt.addBatch(); + } + prepStmt.executeBatch(); + } catch (SQLException e) { + throw new ClaimMetadataException("Error while deleting claim properties: " + claimPropertyNames, e); + } + } + + /** + * Updates claim properties atomically by performing deletions and additions within a single transaction. + * This method ensures that all operations either complete successfully or are rolled back entirely. + * + * @param connection Database connection to be used + * @param claimId ID of the claim whose properties are being updated + * @param newClaimProperties Map of new claim properties to be added (property name to value mapping) + * @param claimPropertiesToDelete Set of claim property names to be deleted + * @param tenantId ID of the tenant + * @throws ClaimMetadataException If an error occurs during the transaction or while managing claim properties + */ + protected void updateClaimPropertiesAtomically(Connection connection, int claimId, + Map newClaimProperties, + Set claimPropertiesToDelete, + int tenantId) throws ClaimMetadataException { + + boolean autoCommit = false; + try { + autoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + + if (claimPropertiesToDelete != null && !claimPropertiesToDelete.isEmpty()) { + deleteClaimProperties(connection, claimId, claimPropertiesToDelete, tenantId); + } + + if (newClaimProperties != null && !newClaimProperties.isEmpty()) { + addClaimProperties(connection, claimId, newClaimProperties, tenantId); + } + + IdentityDatabaseUtil.commitTransaction(connection); + } catch (SQLException e) { + IdentityDatabaseUtil.rollbackTransaction(connection); + throw new ClaimMetadataException("Error while updating claim properties atomically", e); + } finally { + try { + connection.setAutoCommit(autoCommit); + } catch (SQLException e) { + throw new ClaimMetadataException("Error while resetting auto-commit state", e); + } + } + } } diff --git a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/LocalClaimDAO.java b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/LocalClaimDAO.java index 0f3debc73001..047d1f36c92f 100644 --- a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/LocalClaimDAO.java +++ b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/dao/LocalClaimDAO.java @@ -34,8 +34,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; + +import static org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimMetadataUtils.getServerLevelClaimUniquenessScope; /** * Data access object for org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim. @@ -65,8 +69,13 @@ public List getLocalClaims(int tenantId) throws ClaimMetadataExcepti List attributeMappingsOfClaim = claimAttributeMappingsOfDialect.get(claimId); Map propertiesOfClaim = claimPropertiesOfDialect.get(claimId); - - localClaims.add(new LocalClaim(claim.getClaimURI(), attributeMappingsOfClaim, propertiesOfClaim)); + LocalClaim localClaim = new LocalClaim(claim.getClaimURI(), attributeMappingsOfClaim, propertiesOfClaim); + if (shouldAddUniquenessScopeInClaimProperties(propertiesOfClaim)) { + ClaimConstants.ClaimUniquenessScope uniquenessScope = getServerLevelClaimUniquenessScope(); + storeUniquenessScopeInClaimProperties(connection, claimId, uniquenessScope, tenantId); + addUniquenessScopeToClaimProperties(localClaim,uniquenessScope); + } + localClaims.add(localClaim); } } finally { IdentityDatabaseUtil.closeConnection(connection); @@ -179,6 +188,10 @@ public void addLocalClaim(LocalClaim localClaim, int tenantId) throws ClaimMetad } addClaimAttributeMappings(connection, localClaimId, localClaim.getMappedAttributes(), tenantId); + if (shouldAddUniquenessScopeInClaimProperties(localClaim.getClaimProperties())) { + ClaimConstants.ClaimUniquenessScope uniquenessScope = getServerLevelClaimUniquenessScope(); + addUniquenessScopeToClaimProperties(localClaim,uniquenessScope); + } addClaimProperties(connection, localClaimId, localClaim.getClaimProperties(), tenantId); // End transaction @@ -209,6 +222,10 @@ public void updateLocalClaim(LocalClaim localClaim, int tenantId) throws ClaimMe addClaimAttributeMappings(connection, localClaimId, localClaim.getMappedAttributes(), tenantId); deleteClaimProperties(connection, localClaimId, tenantId); + if (shouldAddUniquenessScopeInClaimProperties(localClaim.getClaimProperties())) { + ClaimConstants.ClaimUniquenessScope uniquenessScope = getServerLevelClaimUniquenessScope(); + addUniquenessScopeToClaimProperties(localClaim,uniquenessScope); + } addClaimProperties(connection, localClaimId, localClaim.getClaimProperties(), tenantId); // End transaction @@ -398,4 +415,56 @@ public List fetchMappedExternalClaims(String localClaimURI, int tenantId) throw new ClaimMetadataException("Error while obtaining mapped external claims for local claim.", e); } } + + /** + * Checks if the uniqueness scope should be included in the given claim properties. + * + * @param claimProperties Map of claim properties to check. + * @return true if uniqueness scope should be included, false otherwise. + */ + private boolean shouldAddUniquenessScopeInClaimProperties(Map claimProperties) { + + return claimProperties != null && + Boolean.parseBoolean(claimProperties.get(ClaimConstants.IS_UNIQUE_CLAIM_PROPERTY)) && + !claimProperties.containsKey(ClaimConstants.CLAIM_UNIQUENESS_SCOPE_PROPERTY); + } + + /** + * Adds the specified uniqueness scope to the claim properties of a given LocalClaim. + * This method removes the legacy isUnique flag and replaces it with the new UniquenessScope property. + * + * @param localClaim LocalClaim instance to which the uniqueness scope property will be added. + * @param uniquenessScope Enum value representing the uniqueness scope to be added. + */ + private void addUniquenessScopeToClaimProperties(LocalClaim localClaim, + ClaimConstants.ClaimUniquenessScope uniquenessScope) { + + Map claimProperties = localClaim.getClaimProperties(); + claimProperties.remove(ClaimConstants.IS_UNIQUE_CLAIM_PROPERTY); + claimProperties.put(ClaimConstants.CLAIM_UNIQUENESS_SCOPE_PROPERTY, uniquenessScope.toString()); + } + + /** + * Stores the uniqueness scope in the claim properties for a given claim in the database. + * This method removes the legacy IS_UNIQUE flag and replaces it with the new uniqueness scope property + * in an atomic transaction. + * + * @param connection Database connection. + * @param claimId ID of the claim for which the uniqueness scope will be stored. + * @param uniquenessScope Enum value representing the uniqueness scope to store. + * @param tenantId ID of the tenant to which the claim belongs. + * @throws ClaimMetadataException if an error occurs while storing the claim properties. + */ + private void storeUniquenessScopeInClaimProperties(Connection connection, int claimId, + ClaimConstants.ClaimUniquenessScope uniquenessScope, + int tenantId) throws ClaimMetadataException { + + Map newClaimProperties = new HashMap<>(); + newClaimProperties.put(ClaimConstants.CLAIM_UNIQUENESS_SCOPE_PROPERTY, uniquenessScope.toString()); + + Set claimPropertiesToDelete = new HashSet<>(); + claimPropertiesToDelete.add(ClaimConstants.IS_UNIQUE_CLAIM_PROPERTY); + + updateClaimPropertiesAtomically(connection, claimId, newClaimProperties, claimPropertiesToDelete, tenantId); + } } diff --git a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimConstants.java b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimConstants.java index 86e54c269800..a9155d0cf05c 100644 --- a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimConstants.java +++ b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimConstants.java @@ -36,6 +36,9 @@ public class ClaimConstants { public static final String READ_ONLY_PROPERTY = "ReadOnly"; public static final String CLAIM_URI_PROPERTY = "ClaimURI"; public static final String MASKING_REGULAR_EXPRESSION_PROPERTY = "MaskingRegEx"; + public static final String CLAIM_UNIQUENESS_SCOPE_PROPERTY = "UniquenessScope"; + public static final String IS_UNIQUE_CLAIM_PROPERTY = "isUnique"; + public static final String UNIQUENESS_VALIDATION_SCOPE = "UserClaimUpdate.UniquenessValidation.ScopeWithinUserstore"; public static final String DEFAULT_ATTRIBUTE = "DefaultAttribute"; public static final String MAPPED_LOCAL_CLAIM_PROPERTY = "MappedLocalClaim"; @@ -112,4 +115,13 @@ public String getMessage() { return message; } } + + /** + * Enum for claim uniqueness validation scopes. + */ + public enum ClaimUniquenessScope { + NONE, + WITHIN_USERSTORE, + ACROSS_USERSTORES + } } diff --git a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimMetadataUtils.java b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimMetadataUtils.java index b18e46519721..a7e2a88da24f 100644 --- a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimMetadataUtils.java +++ b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/ClaimMetadataUtils.java @@ -26,6 +26,7 @@ import org.wso2.carbon.identity.claim.metadata.mgt.model.ClaimDialect; import org.wso2.carbon.identity.claim.metadata.mgt.model.ExternalClaim; import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserCoreConstants; @@ -38,6 +39,8 @@ import java.util.List; import java.util.Map; +import static org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants.UNIQUENESS_VALIDATION_SCOPE; + /** * Utility class containing various claim metadata implementation related functionality. */ @@ -318,4 +321,19 @@ public static ClaimMapping convertExternalClaimToClaimMapping(ExternalClaim exte claimMapping.getClaim().setClaimUri(externalClaim.getClaimURI()); return claimMapping; } + + /** + * Retrieves the server-level uniqueness validation scope for claims based on configuration. + * + * @return Enum value of ClaimConstants.ClaimUniquenessScope indicating the server-level uniqueness scope. + * Returns WITHIN_USERSTORE if the configuration is set to restrict uniqueness within the user store; + * otherwise, returns ACROSS_USERSTORES. + */ + public static ClaimConstants.ClaimUniquenessScope getServerLevelClaimUniquenessScope() { + + boolean isScopeWithinUserstore = Boolean.parseBoolean(IdentityUtil.getProperty(UNIQUENESS_VALIDATION_SCOPE)); + + return isScopeWithinUserstore ? ClaimConstants.ClaimUniquenessScope.WITHIN_USERSTORE : + ClaimConstants.ClaimUniquenessScope.ACROSS_USERSTORES; + } } diff --git a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/SQLConstants.java b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/SQLConstants.java index cc157236520f..d80cd3832884 100644 --- a/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/SQLConstants.java +++ b/components/claim-mgt/org.wso2.carbon.identity.claim.metadata.mgt/src/main/java/org/wso2/carbon/identity/claim/metadata/mgt/util/SQLConstants.java @@ -90,6 +90,8 @@ private SQLConstants() { "PROPERTY_VALUE, TENANT_ID) VALUES (?, ?, ?, ?)"; public static final String DELETE_CLAIM_PROPERTY = "DELETE FROM IDN_CLAIM_PROPERTY WHERE LOCAL_CLAIM_ID=? AND " + "TENANT_ID=?"; + public static final String DELETE_CLAIM_PROPERTY_BY_NAME = "DELETE FROM IDN_CLAIM_PROPERTY WHERE " + + "LOCAL_CLAIM_ID=? AND PROPERTY_NAME=? AND TENANT_ID=?"; public static final String GET_CLAIMS = "SELECT CLAIMS.ID, PROPERTY.PROPERTY_NAME, PROPERTY.PROPERTY_VALUE, " + "CLAIMS.CLAIM_URI, RESOLVED.CLAIM_URI MAPPED_URI FROM IDN_CLAIM CLAIMS " diff --git a/components/multi-attribute-login/org.wso2.carbon.identity.unique.claim.mgt/src/main/java/org/wso2/carbon/identity/unique/claim/mgt/listener/UniqueClaimUserOperationEventListener.java b/components/multi-attribute-login/org.wso2.carbon.identity.unique.claim.mgt/src/main/java/org/wso2/carbon/identity/unique/claim/mgt/listener/UniqueClaimUserOperationEventListener.java index 8a86481bf032..562e508d89e5 100644 --- a/components/multi-attribute-login/org.wso2.carbon.identity.unique.claim.mgt/src/main/java/org/wso2/carbon/identity/unique/claim/mgt/listener/UniqueClaimUserOperationEventListener.java +++ b/components/multi-attribute-login/org.wso2.carbon.identity.unique.claim.mgt/src/main/java/org/wso2/carbon/identity/unique/claim/mgt/listener/UniqueClaimUserOperationEventListener.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.claim.metadata.mgt.exception.ClaimMetadataException; import org.wso2.carbon.identity.claim.metadata.mgt.model.LocalClaim; +import org.wso2.carbon.identity.claim.metadata.mgt.util.ClaimConstants; import org.wso2.carbon.identity.core.AbstractIdentityUserOperationEventListener; import org.wso2.carbon.identity.core.model.IdentityEventListenerConfig; import org.wso2.carbon.identity.core.util.IdentityCoreConstants; @@ -102,8 +103,9 @@ public boolean doPreSetUserClaimValue(String userName, String claimURI, String c } try { String tenantDomain = getTenantDomain(userStoreManager); - if (isUniqueClaim(claimURI, tenantDomain)) { - return !isClaimDuplicated(userName, claimURI, claimValue, profile, userStoreManager); + ClaimConstants.ClaimUniquenessScope uniquenessScope = getClaimUniquenessScope(claimURI, tenantDomain); + if (shouldValidateUniqueness(uniquenessScope)) { + return !isClaimDuplicated(userName, claimURI, claimValue, profile, userStoreManager, uniquenessScope); } } catch (org.wso2.carbon.user.api.UserStoreException | ClaimMetadataException e) { log.error("Error while retrieving details. " + e.getMessage(), e); @@ -132,7 +134,9 @@ private void checkClaimUniqueness(String username, Map claims, S Claim claimObject = null; for (Map.Entry claim : claims.entrySet()) { try { - if (StringUtils.isNotEmpty(claim.getValue()) && isUniqueClaim(claim.getKey(), tenantDomain)) { + ClaimConstants.ClaimUniquenessScope uniquenessScope = + getClaimUniquenessScope(claim.getKey(), tenantDomain); + if (StringUtils.isNotEmpty(claim.getValue()) && shouldValidateUniqueness(uniquenessScope)) { try { claimObject = userStoreManager.getClaimManager().getClaim(claim.getKey()); } catch (org.wso2.carbon.user.api.UserStoreException e) { @@ -147,7 +151,8 @@ private void checkClaimUniqueness(String username, Map claims, S claimObject.getDisplayTag() + "!"; throw new UserStoreException(errorMessage, new PolicyViolationException(errorMessage)); } - if (isClaimDuplicated(username, claim.getKey(), claim.getValue(), profile, userStoreManager)) { + if (isClaimDuplicated(username, claim.getKey(), claim.getValue(), profile, userStoreManager, + uniquenessScope)) { String displayTag = claimObject.getDisplayTag(); if (StringUtils.isBlank(displayTag)) { displayTag = claim.getKey(); @@ -175,14 +180,15 @@ private void checkClaimUniqueness(String username, Map claims, S } private boolean isClaimDuplicated(String username, String claimUri, String claimValue, String profile, - UserStoreManager userStoreManager) throws UserStoreException { + UserStoreManager userStoreManager, + ClaimConstants.ClaimUniquenessScope uniquenessScope) throws UserStoreException { String domainName = userStoreManager.getRealmConfiguration().getUserStoreProperty( UserCoreConstants.RealmConfig.PROPERTY_DOMAIN_NAME); String[] userList; // Get UserStoreManager from realm since the received one might be for a secondary user store UserStoreManager userStoreMgrFromRealm = getUserstoreManager(userStoreManager.getTenantId()); - if (isScopeWithinUserstore()) { + if (ClaimConstants.ClaimUniquenessScope.WITHIN_USERSTORE.equals(uniquenessScope)) { String claimValueWithDomain = domainName + UserCoreConstants.DOMAIN_SEPARATOR + claimValue; userList = userStoreMgrFromRealm.getUserList(claimUri, claimValueWithDomain, profile); } else { @@ -200,17 +206,67 @@ private boolean isClaimDuplicated(String username, String claimUri, String claim return true; } - public boolean isUniqueClaim(String claimUrI, String tenantDomain) throws ClaimMetadataException { + /** + * Determines the uniqueness validation scope for a given claim URI. + * This method checks the claim properties to determine how uniqueness should be enforced: + * 1. First checks for explicit uniquenessScope property + * 2. If not found, checks for legacy isUnique property + * 3. If claim is unique, scope is determined by isScopeWithinUserstore server-level configuration + * 4. Defaults to NONE if no uniqueness requirements are found + * + * @param claimUri The URI of the claim to check + * @param tenantDomain The tenant domain where the claim exists + * @return The ClaimUniquenessScope (NONE, WITHIN_USERSTORE, or ACROSS_USERSTORES) + * @throws ClaimMetadataException If there is an error accessing claim metadata + */ + private ClaimConstants.ClaimUniquenessScope getClaimUniquenessScope(String claimUri, String tenantDomain) + throws ClaimMetadataException { + + List localClaims = UniqueClaimUserOperationDataHolder.getInstance() + .getClaimMetadataManagementService().getLocalClaims(tenantDomain); + + LocalClaim targetLocalClaim = localClaims.stream() + .filter(claim -> claim.getClaimURI().equals(claimUri)) + .findFirst() + .orElse(null); + + if (targetLocalClaim != null) { + String uniquenessScope = targetLocalClaim.getClaimProperty(ClaimConstants.CLAIM_UNIQUENESS_SCOPE_PROPERTY); + if (StringUtils.isNotBlank(uniquenessScope)) { + try { + return ClaimConstants.ClaimUniquenessScope.valueOf(uniquenessScope); + } catch (IllegalArgumentException e) { + if (log.isWarnEnabled()) { + log.warn("Invalid uniqueness validation scope '" + uniquenessScope + "' provided for " + + "claim URI: " + claimUri + ". Defaulting to NONE, where no uniqueness validation " + + "will be performed."); + } + return ClaimConstants.ClaimUniquenessScope.NONE; + } + } - List localClaims = UniqueClaimUserOperationDataHolder.getInstance(). - getClaimMetadataManagementService().getLocalClaims(tenantDomain); - for (LocalClaim localClaim : localClaims) { - if (localClaim.getClaimURI().equals(claimUrI) && - Boolean.parseBoolean(localClaim.getClaimProperty(IS_UNIQUE_CLAIM))) { - return true; + boolean isUniqueClaim = Boolean.parseBoolean(targetLocalClaim.getClaimProperty(IS_UNIQUE_CLAIM)); + if (isUniqueClaim) { + return isScopeWithinUserstore() + ? ClaimConstants.ClaimUniquenessScope.WITHIN_USERSTORE + : ClaimConstants.ClaimUniquenessScope.ACROSS_USERSTORES; } } - return false; + + return ClaimConstants.ClaimUniquenessScope.NONE; + } + + /** + * Determines whether uniqueness validation should be performed for a given uniqueness scope. + * Returns true for any scope other than NONE. + * + * @param uniquenessScope The ClaimUniquenessScope to check + * @return true if uniqueness validation should be performed, false otherwise + * @throws ClaimMetadataException If there is an error processing the metadata + */ + private boolean shouldValidateUniqueness(ClaimConstants.ClaimUniquenessScope uniquenessScope) { + + return !ClaimConstants.ClaimUniquenessScope.NONE.equals(uniquenessScope); } private void checkUsernameUniqueness(String username, UserStoreManager userStoreManager) throws UserStoreException { @@ -219,8 +275,9 @@ private void checkUsernameUniqueness(String username, UserStoreManager userStore String tenantDomain = getTenantDomain(userStoreManager); try { - if (isUniqueClaim(USERNAME_CLAIM, tenantDomain) && - isClaimDuplicated(username, USERNAME_CLAIM, username, null, userStoreManager)) { + ClaimConstants.ClaimUniquenessScope uniquenessScope = getClaimUniquenessScope(USERNAME_CLAIM, tenantDomain); + if (shouldValidateUniqueness(uniquenessScope) && + isClaimDuplicated(username, USERNAME_CLAIM, username, null, userStoreManager, uniquenessScope)) { errorMessage = "Username " + username + " is already in use by a different user!"; throw new UserStoreException(errorMessage, new PolicyViolationException(errorMessage)); diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml index 257bd346b95f..7bd156584e87 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml @@ -1160,6 +1160,9 @@ + + false + diff --git a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 index 0d90b8ec0420..365dc42ca4e5 100644 --- a/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 +++ b/features/identity-core/org.wso2.carbon.identity.core.server.feature/resources/identity.xml.j2 @@ -1880,6 +1880,15 @@ {{identity_mgt.user_claim_update.enable_multiple_emails_and_mobile_numbers}} + + + + {{identity_mgt.user_claim_update.uniqueness.scope_within_userstore}} +