Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getSyncedUserAttributes method #600

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
31cdde7
Update the group last modified date on group update
ThaminduDilshan Jan 3, 2025
bd0a2c0
Add unit tests
ThaminduDilshan Jan 3, 2025
2265fb7
Address review comments
ThaminduDilshan Jan 21, 2025
17af955
Add attribute profiles properties to schema response.
PasinduYeshan Jan 19, 2025
46c49f3
Refactor code
PasinduYeshan Jan 19, 2025
3533fb3
Update framework version
PasinduYeshan Jan 20, 2025
20ab33b
Add unit tests
PasinduYeshan Jan 20, 2025
07f8dd3
Add supportedByDefault globally
PasinduYeshan Jan 20, 2025
7abd5fc
[WSO2 Release] [Jenkins #1136] [Release 3.4.111] prepare release v3.4…
wso2-jenkins-bot Jan 21, 2025
c49549d
[WSO2 Release] [Jenkins #1136] [Release 3.4.111] prepare for next dev…
wso2-jenkins-bot Jan 21, 2025
ee28acf
[WSO2 Release] [Jenkins #1138] [Release 3.4.112] prepare release v3.4…
wso2-jenkins-bot Jan 21, 2025
f163428
[WSO2 Release] [Jenkins #1138] [Release 3.4.112] prepare for next dev…
wso2-jenkins-bot Jan 21, 2025
1582378
bump charon version
amanda-ariyaratne Jan 23, 2025
05fa170
[WSO2 Release] [Jenkins #1140] [Release 3.4.113] prepare release v3.4…
wso2-jenkins-bot Jan 23, 2025
34d28fb
[WSO2 Release] [Jenkins #1140] [Release 3.4.113] prepare for next dev…
wso2-jenkins-bot Jan 23, 2025
232aead
Populate Identity Context in password update flow (#597)
ashanthamara Jan 26, 2025
620f245
[WSO2 Release] [Jenkins #1142] [Release 3.4.114] prepare release v3.4…
wso2-jenkins-bot Jan 26, 2025
e081fe8
[WSO2 Release] [Jenkins #1142] [Release 3.4.114] prepare for next dev…
wso2-jenkins-bot Jan 26, 2025
9de0b36
update claim change addition when old claims don't have value and upd…
AnuradhaSK Jan 26, 2025
9e7dbc6
[WSO2 Release] [Jenkins #1144] [Release 3.4.115] prepare release v3.4…
wso2-jenkins-bot Jan 26, 2025
50e1e62
[WSO2 Release] [Jenkins #1144] [Release 3.4.115] prepare for next dev…
wso2-jenkins-bot Jan 26, 2025
150cee9
Refactor SCIM2 web.xml
lashinijay Jan 29, 2025
e5b835e
[WSO2 Release] [Jenkins #1146] [Release 3.4.116] prepare release v3.4…
wso2-jenkins-bot Jan 30, 2025
edab6c4
[WSO2 Release] [Jenkins #1146] [Release 3.4.116] prepare for next dev…
wso2-jenkins-bot Jan 30, 2025
e98b045
add getSyncedUserAttributes method
amanda-ariyaratne Jan 31, 2025
cb72101
bump charon version
amanda-ariyaratne Jan 31, 2025
b46a4a0
add unit test for getSyncedUserAttributes method
amanda-ariyaratne Jan 31, 2025
377c874
bump charon version
amanda-ariyaratne Feb 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/org.wso2.carbon.identity.scim2.common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<groupId>org.wso2.carbon.identity.inbound.provisioning.scim2</groupId>
<artifactId>identity-inbound-provisioning-scim2</artifactId>
<relativePath>../../pom.xml</relativePath>
<version>3.4.111-SNAPSHOT</version>
<version>3.4.117-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,38 @@ public void addSCIMGroupAttributesToSCIMDisabledHybridRoles(int tenantId,
}
}

/**
* Update SCIM group attributes.
*
* @param tenantId Tenant id.
* @param roleName Group name.
* @param attributes Attributes to be updated.
* @throws IdentitySCIMException If an error occurred while updating the attributes.
*/
public void updateSCIMGroupAttributes(int tenantId, String roleName,
Map<String, String> attributes) throws IdentitySCIMException {

Connection connection = IdentityDatabaseUtil.getDBConnection();
doUpdateSCIMGroupAttributes(tenantId, roleName, attributes, SQLQueries.UPDATE_ATTRIBUTES_SQL);
}

/**
* Do update SCIM group attributes.
*
* @param tenantId Tenant id.
* @param roleName Group name.
* @param attributes Attributes to be updated.
* @param sqlQuery SQL query to update the attributes.
* @throws IdentitySCIMException If an error occurred while updating the attributes.
*/
private void doUpdateSCIMGroupAttributes(int tenantId, String roleName, Map<String, String> attributes,
String sqlQuery) throws IdentitySCIMException {

Connection connection = IdentityDatabaseUtil.getDBConnection(true);
PreparedStatement prepStmt = null;

if (isExistingGroup(SCIMCommonUtils.getGroupNameWithDomain(roleName), tenantId)) {
try {
prepStmt = connection.prepareStatement(SQLQueries.UPDATE_ATTRIBUTES_SQL);

prepStmt = connection.prepareStatement(sqlQuery);
prepStmt.setInt(2, tenantId);
prepStmt.setString(3, roleName);

Expand All @@ -308,19 +330,16 @@ public void updateSCIMGroupAttributes(int tenantId, String roleName,
prepStmt.setString(4, entry.getKey());
prepStmt.setString(1, entry.getValue());
prepStmt.addBatch();

} else {
throw new IdentitySCIMException("Error when adding SCIM Attribute: "
+ entry.getKey()
+ " An attribute with the same name doesn't exists.");
+ entry.getKey() + " An attribute with the same name doesn't exists.");
}
}
int[] return_count = prepStmt.executeBatch();
int[] returnCount = prepStmt.executeBatch();
if (log.isDebugEnabled()) {
log.debug("No. of records updated for updating SCIM Group : " + return_count.length);
log.debug("No. of records updated for updating SCIM Group : " + returnCount.length);
}
connection.commit();

} catch (SQLException e) {
throw new IdentitySCIMException("Error updating the SCIM Group Attributes.", e);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ public class SQLQueries {
public static final String CHECK_EXISTING_ATTRIBUTE_WITH_AUDIENCE_SQL =
"SELECT TENANT_ID, ROLE_NAME, ATTR_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " +
"IDN_SCIM_GROUP.ROLE_NAME=? AND IDN_SCIM_GROUP.ATTR_NAME=? AND IDN_SCIM_GROUP.AUDIENCE_REF_ID=?";

public static final String UPDATE_ATTRIBUTES_SQL =
"UPDATE IDN_SCIM_GROUP SET UM_ATTR_VALUE=? WHERE TENANT_ID=? AND ROLE_NAME=? AND ATTR_NAME=?";
"UPDATE IDN_SCIM_GROUP SET ATTR_VALUE=? WHERE TENANT_ID=? AND ROLE_NAME=? AND ATTR_NAME=?";
public static final String UPDATE_GROUP_NAME_SQL =
"UPDATE IDN_SCIM_GROUP SET ROLE_NAME=? WHERE TENANT_ID=? AND ROLE_NAME=?";

public static final String DELETE_GROUP_SQL =
"DELETE FROM IDN_SCIM_GROUP WHERE TENANT_ID=? AND ROLE_NAME=?";
public static final String CHECK_EXISTING_ATTRIBUTE_SQL =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,4 +335,17 @@ public String[] getGroupListFromAttributeName(String attributeName, String searc
GroupDAO groupDAO = new GroupDAO();
return groupDAO.getGroupNameList(attributeName, searchAttribute, this.tenantId, domainName);
}

/**
* Update SCIM attributes of the group.
*
* @param groupName The display name of the group.
* @param attributes The attributes to be updated.
* @throws IdentitySCIMException IdentitySCIMException when updating the SCIM Group information.
*/
public void updateSCIMAttributes(String groupName, Map<String, String> attributes) throws IdentitySCIMException {

GroupDAO groupDAO = new GroupDAO();
groupDAO.updateSCIMGroupAttributes(tenantId, groupName, attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.json.JSONObject;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
Expand Down Expand Up @@ -180,6 +181,8 @@ public class SCIMUserManager implements UserManager {
private static final String DISPLAY_ORDER_PROPERTY = "displayOrder";
private static final String REGULAR_EXPRESSION_PROPERTY = "regEx";
private static final String EXCLUDED_USER_STORES_PROPERTY = "excludedUserStores";
private static final String SUPPORTED_BY_DEFAULT_PROPERTY = "supportedByDefault";
private static final String ATTRIBUTE_PROFILES_PROPERTY = "profiles";
private static final String SHARED_PROFILE_VALUE_RESOLVING_METHOD_PROPERTY = "sharedProfileValueResolvingMethod";
private static final String LOCATION_CLAIM = "http://wso2.org/claims/location";
private static final String LAST_MODIFIED_CLAIM = "http://wso2.org/claims/modified";
Expand Down Expand Up @@ -3524,6 +3527,9 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L
List<PatchOperation> displayNameOperations = new ArrayList<>();
List<PatchOperation> memberOperations = new ArrayList<>();
String newGroupName = currentGroupName;
Date groupLastUpdatedTime = null;
String groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(currentGroupName);

for (List<PatchOperation> patchOperationList : patchOperations.values()) {
for (PatchOperation patchOperation : patchOperationList) {
if (StringUtils.equals(SCIMConstants.GroupSchemaConstants.DISPLAY_NAME,
Expand All @@ -3540,7 +3546,14 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L

if (CollectionUtils.isNotEmpty(displayNameOperations)) {
newGroupName = (String) displayNameOperations.get(0).getValues();
setGroupDisplayName(groupId, currentGroupName, newGroupName);
String[] resolvedGroupNames = resolveGroupDomains(currentGroupName, newGroupName);
boolean groupUpdated = setGroupDisplayName(groupId, resolvedGroupNames[0], resolvedGroupNames[1]);
if (groupUpdated) {
groupLastUpdatedTime = new Date();
groupNameForIDNScim = resolvedGroupNames[1];
} else {
groupNameForIDNScim = resolvedGroupNames[0];
}
}

Collections.sort(memberOperations);
Expand Down Expand Up @@ -3626,10 +3639,20 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L
carbonUM.updateUserListOfRoleWithID(newGroupName,
deletedMemberIdsFromUserstore.toArray(new String[0]),
addedMemberIdsFromUserstore.toArray(new String[0]));
groupLastUpdatedTime = new Date();
}

// Update the group name in UM_HYBRID_GROUP_ROLE table.
carbonUM.updateGroupName(currentGroupName, newGroupName);

// Update the last modified time of the group.
if (!carbonUM.isUniqueGroupIdEnabled() && groupLastUpdatedTime != null) {
Map<String, String> attributes = new HashMap<>();
attributes.put(SCIMConstants.CommonSchemaConstants.LAST_MODIFIED_URI,
AttributeUtil.formatDateTime(groupLastUpdatedTime.toInstant()));
SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
groupHandler.updateSCIMAttributes(groupNameForIDNScim, attributes);
}
} catch (UserStoreException e) {
if (e instanceof org.wso2.carbon.user.core.UserStoreException && (StringUtils
.equals(UserCoreErrorConstants.ErrorMessages.ERROR_CODE_DUPLICATE_WHILE_WRITING_TO_DATABASE
Expand Down Expand Up @@ -3697,12 +3720,10 @@ private void prepareAddedRemovedMemberLists(Set<String> addedMembers, Set<String
}
}

private void setGroupDisplayName(String groupId, String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, CharonException, BadRequestException, IdentitySCIMException,
UserStoreException {
private String[] resolveGroupDomains(String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, CharonException, IdentitySCIMException {

String userStoreDomainFromSP = getUserStoreDomainFromSP();

String oldGroupDomain = IdentityUtil.extractDomainFromName(oldGroupName);
if (userStoreDomainFromSP != null && !userStoreDomainFromSP.equalsIgnoreCase(oldGroupDomain)) {
throw new CharonException("Group :" + oldGroupName + "is not belong to user store " +
Expand All @@ -3728,10 +3749,19 @@ private void setGroupDisplayName(String groupId, String oldGroupName, String new
oldGroupName = SCIMCommonUtils.getGroupNameWithDomain(oldGroupName);
newGroupName = SCIMCommonUtils.getGroupNameWithDomain(newGroupName);

return new String[] { oldGroupName, newGroupName };
}

private boolean setGroupDisplayName(String groupId, String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, IdentitySCIMException, UserStoreException {

if (!StringUtils.equals(oldGroupName, newGroupName)) {
// Update group name in carbon UM.
carbonUM.renameGroup(groupId, newGroupName);
return true;
}

return false;
}

@Override
Expand Down Expand Up @@ -3779,6 +3809,9 @@ public Group updateGroup(Group oldGroup, Group newGroup, Map<String, Boolean> re
public boolean doUpdateGroup(Group oldGroup, Group newGroup) throws CharonException, IdentitySCIMException,
BadRequestException, IdentityApplicationManagementException, org.wso2.carbon.user.core.UserStoreException {

Date groupLastUpdatedTime = null;
String groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(oldGroup.getDisplayName());

setGroupDisplayName(oldGroup, newGroup);
if (log.isDebugEnabled()) {
log.debug("Updating group: " + oldGroup.getDisplayName());
Expand Down Expand Up @@ -3827,14 +3860,27 @@ public boolean doUpdateGroup(Group oldGroup, Group newGroup) throws CharonExcept
// Update group name in carbon UM
carbonUM.renameGroup(oldGroup.getId(), newGroupDisplayName);
updated = true;
groupLastUpdatedTime = new Date();
groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(newGroupDisplayName);
}
// Update the group with added members and deleted members.
if (isNotEmpty(addedMembers) || isNotEmpty(deletedMembers)) {
carbonUM.updateUserListOfRoleWithID(newGroupDisplayName,
deletedMemberIdsFromUserstore.toArray(new String[0]),
addedMemberIdsFromUserstore.toArray(new String[0]));
updated = true;
groupLastUpdatedTime = new Date();
}

// Update the last modified time of the group.
if (!carbonUM.isUniqueGroupIdEnabled() && groupLastUpdatedTime != null) {
Map<String, String> attributes = new HashMap<>();
attributes.put(SCIMConstants.CommonSchemaConstants.LAST_MODIFIED_URI,
AttributeUtil.formatDateTime(groupLastUpdatedTime.toInstant()));
SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
groupHandler.updateSCIMAttributes(groupNameForIDNScim, attributes);
}

return updated;
}

Expand Down Expand Up @@ -5462,6 +5508,13 @@ private void updateUserClaims(User user, Map<String, String> oldClaimList,
simpleMultiValuedClaimsToBeAdded.put(key, Arrays.asList(newClaimList.get(key).split(separator)));
}
}
/*
If the newClaimList has a claim with empty value and the claim is not exist in the oldClaimList, remove that
claim from newClaimList.
*/
newClaimList.entrySet()
.removeIf(entry -> entry.getValue().isEmpty() && !oldClaimList.containsKey(entry.getKey()));

/*
Prepare user claims expect multi-valued claims to be added, deleted and modified.
Remove simple multi-valued claims URIS from existing claims and updated user's claims.
Expand Down Expand Up @@ -5881,8 +5934,19 @@ private Map<String, Attribute> getFilteredSchemaAttributes(Map<ExternalClaim, Lo

private boolean isSupportedByDefault(LocalClaim mappedLocalClaim) {

String supportedByDefault = mappedLocalClaim.getClaimProperty(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY);
return Boolean.parseBoolean(supportedByDefault);
String globalSupportedByDefault =
mappedLocalClaim.getClaimProperty(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY);

boolean profileSpecificSupportedByDefault = mappedLocalClaim.getClaimProperties().entrySet().stream()
.filter(entry -> StringUtils.startsWith(entry.getKey(), ClaimConstants.PROFILES_CLAIM_PROPERTY_PREFIX))
.anyMatch(entry -> {
String[] profilePropertyKeyArray = entry.getKey().split("\\.");
return profilePropertyKeyArray.length == 3 &&
StringUtils.endsWith(entry.getKey(), ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY) &&
Boolean.parseBoolean(entry.getValue());
});

return Boolean.parseBoolean(globalSupportedByDefault) || profileSpecificSupportedByDefault;
}

private boolean isUsernameClaim(ExternalClaim scimClaim) {
Expand Down Expand Up @@ -6101,9 +6165,65 @@ private void populateBasicAttributes(LocalClaim mappedLocalClaim, AbstractAttrib
attribute.addAttributeProperty(SHARED_PROFILE_VALUE_RESOLVING_METHOD_PROPERTY,
mappedLocalClaim.getClaimProperty(ClaimConstants.SHARED_PROFILE_VALUE_RESOLVING_METHOD));
}
if (mappedLocalClaim.getClaimProperty(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY) != null) {
attribute.addAttributeProperty(SUPPORTED_BY_DEFAULT_PROPERTY,
mappedLocalClaim.getClaimProperty(ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY));
}

// Add attribute profile properties.
for (Map.Entry<String, String> entry: mappedLocalClaim.getClaimProperties().entrySet()) {
if (StringUtils.startsWith(entry.getKey(), ClaimConstants.PROFILES_CLAIM_PROPERTY_PREFIX)) {
addAttributeProfilesProperty(attribute, entry.getKey(), entry.getValue());
}
}
}
}

/**
* Helper method to insert attribute profile-related properties as a JSON object to attribute.
*
* @param attribute Attribute object.
* @param propertyKey Key of the property.
* @param propertyValue Value of the property.
*/
private void addAttributeProfilesProperty(AbstractAttribute attribute, String propertyKey, String propertyValue) {

// Example key format: "Profiles.{profileName}.{propertyName}" - Profiles.console.SupportedByDefault.
String[] propertyKeyParts = propertyKey.split("\\.");
if (propertyKeyParts.length != 3) {
return;
}
String profileName = propertyKeyParts[1];
String profileProperty = propertyKeyParts[2];

JSONObject profilesObject = attribute.getAttributeJSONProperty(ATTRIBUTE_PROFILES_PROPERTY) != null
? attribute.getAttributeJSONProperty(ATTRIBUTE_PROFILES_PROPERTY)
: new JSONObject();

if (!profilesObject.has(profileName)) {
profilesObject.put(profileName, new JSONObject());
}

JSONObject profileObject = profilesObject.getJSONObject(profileName);
switch(profileProperty) {
case ClaimConstants.SUPPORTED_BY_DEFAULT_PROPERTY:
profileObject.put(SUPPORTED_BY_DEFAULT_PROPERTY, Boolean.parseBoolean(propertyValue));
break;
case ClaimConstants.REQUIRED_PROPERTY:
profileObject.put(SCIMConfigConstants.REQUIRED, Boolean.parseBoolean(propertyValue));
break;
case ClaimConstants.READ_ONLY_PROPERTY:
profileObject.put(SCIMConfigConstants.MUTABILITY, Boolean.parseBoolean(propertyValue)
? SCIMDefinitions.Mutability.READ_ONLY
: SCIMDefinitions.Mutability.READ_WRITE);
break;
default:
break;
}

attribute.addAttributeJSONProperty(ATTRIBUTE_PROFILES_PROPERTY, profilesObject);
}

/**
* Builds complex attribute schema for default user schema with correct sub attributes using the flat attribute map.
*
Expand Down Expand Up @@ -6817,4 +6937,31 @@ private boolean checkIterateOverDomainRequired(int limit, PaginatedUserResponse
// When required users are retrieved and limit is set to zero, skip iterating the user stores.
return limit > 0;
}

public Map<String, String> getSyncedUserAttributes() throws CharonException {

try {
Map<String, String> scimToLocalMappings = SCIMCommonUtils.getSCIMtoLocalMappings();
Map<String, List<String>> localToScimMappings = new HashMap<>();
Map<String, String> syncedAttributes = new HashMap<>();

scimToLocalMappings.forEach((scimAttribute, localAttribute) ->
localToScimMappings.computeIfAbsent(localAttribute, k -> new ArrayList<>()).add(scimAttribute)
);
localToScimMappings.values().stream()
.filter(scimAttributes -> scimAttributes.size() > 1)
.forEach(scimAttributes -> {
for (int i = 0; i < scimAttributes.size(); i++) {
for (int j = i + 1; j < scimAttributes.size(); j++) {
syncedAttributes.put(scimAttributes.get(i), scimAttributes.get(j));
syncedAttributes.put(scimAttributes.get(j), scimAttributes.get(i));
}
}
});

return syncedAttributes;
} catch (org.wso2.carbon.user.core.UserStoreException e) {
throw new CharonException("Error while retrieving scim to local mappings.", e);
}
}
}
Loading