Skip to content

Commit

Permalink
Add support for claim-wise uniqueness validation
Browse files Browse the repository at this point in the history
  • Loading branch information
AfraHussaindeen committed Nov 17, 2024
1 parent c0ba5cc commit 8116bea
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -232,4 +233,79 @@ 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<String> 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<String, String> newClaimProperties,
Set<String> claimPropertiesToDelete,
int tenantId) throws ClaimMetadataException {

boolean autoCommit = false;
SQLException resetAutoCommitException = null;

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) {
resetAutoCommitException = e;
}
}

if (resetAutoCommitException != null) {
throw new ClaimMetadataException("Error occurred while resetting auto-commit state",
resetAutoCommitException);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -65,8 +69,13 @@ public List<LocalClaim> getLocalClaims(int tenantId) throws ClaimMetadataExcepti

List<AttributeMapping> attributeMappingsOfClaim = claimAttributeMappingsOfDialect.get(claimId);
Map<String, String> 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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -398,4 +415,56 @@ public List<Claim> 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<String, String> 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<String, String> 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<String, String> newClaimProperties = new HashMap<>();
newClaimProperties.put(ClaimConstants.CLAIM_UNIQUENESS_SCOPE_PROPERTY, uniquenessScope.toString());

Set<String> claimPropertiesToDelete = new HashSet<>();
claimPropertiesToDelete.add(ClaimConstants.IS_UNIQUE_CLAIM_PROPERTY);

updateClaimPropertiesAtomically(connection, claimId, newClaimProperties, claimPropertiesToDelete, tenantId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -112,4 +115,13 @@ public String getMessage() {
return message;
}
}

/**
* Enum for claim uniqueness validation scopes.
*/
public enum ClaimUniquenessScope {
NONE,
WITHIN_USERSTORE,
ACROSS_USERSTORES
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
Loading

0 comments on commit 8116bea

Please sign in to comment.