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 support for bulkID in SCIM2 #393

Merged
merged 6 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.wso2.charon3.core.encoder;

import org.apache.commons.lang.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -53,8 +54,10 @@
import java.io.IOException;
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.charon3.core.schema.SCIMDefinitions.DataType.BINARY;
import static org.wso2.charon3.core.schema.SCIMDefinitions.DataType.BOOLEAN;
Expand Down Expand Up @@ -881,6 +884,7 @@ public BulkRequestData decodeBulkData(String bulkResourceString) throws BadReque
List<BulkRequestContent> rolesEndpointOperationList = new ArrayList<>();
int failOnErrorsAttribute;
List<String> schemas = new ArrayList<>();
Set<String> encounteredBulkIds = new HashSet<>();

JSONObject decodedObject;
try {
Expand Down Expand Up @@ -917,9 +921,15 @@ public BulkRequestData decodeBulkData(String bulkResourceString) throws BadReque

if (requestMethod.equals(SCIMConstants.OperationalConstants.POST)) {

if (!member.optString(SCIMConstants.OperationalConstants.BULK_ID).equals("") &&
member.optString(SCIMConstants.OperationalConstants.BULK_ID) != null) {
String bulkId = member.optString(SCIMConstants.OperationalConstants.BULK_ID);

if (StringUtils.isNotEmpty(bulkId)) {
if (encounteredBulkIds.contains(bulkId)) {
String error = "Duplicate bulkId found: " + bulkId;
logger.error(error);
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
throw new BadRequestException(error, ResponseCodeConstants.INVALID_VALUE);
}
encounteredBulkIds.add(bulkId);
setRequestData(requestType, requestMethod, requestVersion, member, usersEndpointOperationList,
groupsEndpointOperationList, rolesEndpointOperationList);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
*/
package org.wso2.charon3.core.protocol;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.wso2.charon3.core.exceptions.BadRequestException;
import org.wso2.charon3.core.extensions.RoleManager;
import org.wso2.charon3.core.extensions.UserManager;
Expand All @@ -30,6 +34,10 @@
import org.wso2.charon3.core.protocol.endpoints.UserResourceManager;
import org.wso2.charon3.core.schema.SCIMConstants;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
*
*/
Expand Down Expand Up @@ -130,91 +138,114 @@ public BulkResponseData processBulkRequests(BulkRequestData bulkRequestData) thr
}

}
Map<String, String> userIdMappings = getUserIdBulkIdMapping(bulkResponseData.getUserOperationResponse());

for (BulkRequestContent bulkRequestContent : bulkRequestData.getGroupOperationRequests()) {
if (failOnError == 0) {
bulkResponseData.addGroupOperation
(getBulkResponseContent(bulkRequestContent, groupResourceManager));
(getBulkResponseContent(bulkRequestContent,
userIdMappings, groupResourceManager));
} else {
if (errors < failOnError) {
bulkResponseData.addGroupOperation
(getBulkResponseContent(bulkRequestContent, groupResourceManager));
(getBulkResponseContent(bulkRequestContent,
userIdMappings, groupResourceManager));
}
}

}
for (BulkRequestContent bulkRequestContent : bulkRequestData.getRoleOperationRequests()) {
if (failOnError == 0) {
bulkResponseData.addRoleOperation(getBulkResponseContent(bulkRequestContent, roleResourceManager));
bulkResponseData.addRoleOperation(getBulkResponseContent(bulkRequestContent, userIdMappings,
roleResourceManager));
} else {
if (errors < failOnError) {
bulkResponseData.addRoleOperation(getBulkResponseContent(bulkRequestContent, roleResourceManager));
bulkResponseData.addRoleOperation(getBulkResponseContent(bulkRequestContent,
userIdMappings, roleResourceManager));
}
}
}
bulkResponseData.setSchema(SCIMConstants.BULK_RESPONSE_URI);
return bulkResponseData;
}

private BulkResponseContent getBulkResponseContent
(BulkRequestContent bulkRequestContent, ResourceManager resourceManager)
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
throws BadRequestException {
return getBulkResponseContent(bulkRequestContent, null, resourceManager);
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
}

private BulkResponseContent getBulkResponseContent
(BulkRequestContent bulkRequestContent, ResourceManager resourceManager)
(BulkRequestContent bulkRequestContent, Map<String, String> userIdMappings,
ResourceManager resourceManager)
throws BadRequestException {

BulkResponseContent bulkResponseContent = null;
SCIMResponse response;

if (bulkRequestContent.getMethod().equals(SCIMConstants.OperationalConstants.POST)) {

if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.createRole(bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager.create(bulkRequestContent.getData(), userManager, null, null);
processBulkRequestContent(bulkRequestContent, userIdMappings, bulkRequestContent.getMethod());

switch (bulkRequestContent.getMethod()) {
case SCIMConstants.OperationalConstants.POST:
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.createRole(bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager.create(bulkRequestContent.getData(), userManager,
null, null);
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.POST, bulkRequestContent);
errorsCheck(response);
break;

case SCIMConstants.OperationalConstants.PUT: {
String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.updateWithPUTRole(resourceId,
bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager
.updateWithPUT(resourceId, bulkRequestContent.getData(), userManager,
null, null);
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.PUT, bulkRequestContent);
errorsCheck(response);
break;
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.POST, bulkRequestContent);
errorsCheck(response);

} else if (bulkRequestContent.getMethod().equals(SCIMConstants.OperationalConstants.PUT)) {

String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.updateWithPUTRole(resourceId, bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager
.updateWithPUT(resourceId, bulkRequestContent.getData(), userManager, null, null);
case SCIMConstants.OperationalConstants.PATCH: {
String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.updateWithPATCHRole(resourceId,
bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager
.updateWithPATCH(resourceId, bulkRequestContent.getData(), userManager,
null, null);
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.PATCH, bulkRequestContent);
errorsCheck(response);
break;
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.PUT, bulkRequestContent);
errorsCheck(response);

} else if (bulkRequestContent.getMethod().equals(SCIMConstants.OperationalConstants.PATCH)) {

String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.updateWithPATCHRole(resourceId, bulkRequestContent.getData(), roleManager);
} else {
response = resourceManager
.updateWithPATCH(resourceId, bulkRequestContent.getData(), userManager, null, null);
case SCIMConstants.OperationalConstants.DELETE: {
String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.deleteRole(resourceId, roleManager);
} else {
response = resourceManager.delete(resourceId, userManager);
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.DELETE, bulkRequestContent);
errorsCheck(response);
break;
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.PATCH, bulkRequestContent);
errorsCheck(response);

} else if (bulkRequestContent.getMethod().equals(SCIMConstants.OperationalConstants.DELETE)) {

String resourceId = extractIDFromPath(bulkRequestContent.getPath());
if (bulkRequestContent.getPath().contains(SCIMConstants.ROLE_ENDPOINT)) {
response = resourceManager.deleteRole(resourceId, roleManager);
} else {
response = resourceManager.delete(resourceId, userManager);
}

bulkResponseContent = createBulkResponseContent
(response, SCIMConstants.OperationalConstants.DELETE, bulkRequestContent);
errorsCheck(response);
}
return bulkResponseContent;
}
Expand Down Expand Up @@ -252,4 +283,131 @@ private void errorsCheck(SCIMResponse response) {
}
}

/**
* This method is used to process the bulk request content.
* This method will replace the bulk id with the created user id.
*
* @param bulkRequestContent Bulk request content
* @param userIDMappings User id bulk id mapping
* @param method HTTP method
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @throws BadRequestException Bad request exception
*/
private void processBulkRequestContent(BulkRequestContent bulkRequestContent,
Map<String, String> userIDMappings, String method)
throws BadRequestException {
try {
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
if (userIDMappings == null
|| userIDMappings.isEmpty()
|| method.equals(SCIMConstants.OperationalConstants.DELETE)) {
return;
}
// Parse the data field to a JSON object.
JSONObject dataJson = new JSONObject(bulkRequestContent.getData());
String usersOrMembersKey = getUsersOrMembersKey(bulkRequestContent.getPath());
JSONArray usersArray = getUserArray(dataJson, method, usersOrMembersKey);

if (usersArray != null) {
String bulkIdPrefix = SCIMConstants.OperationalConstants.BULK_ID + ":";

for (int i = 0; i < usersArray.length(); i++) {
JSONObject user = usersArray.getJSONObject(i);
String userValue = user.getString(SCIMConstants.OperationalConstants.VALUE);
if (userValue.startsWith(bulkIdPrefix)) {
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
String userBulkId = userValue.substring(bulkIdPrefix.length());
String userId = userIDMappings.get(userBulkId);
if (userId != null) {
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
user.put(SCIMConstants.OperationalConstants.VALUE, userId);
}
}
}
}
bulkRequestContent.setData(dataJson.toString());

PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
} catch (JSONException e) {
throw new BadRequestException("Error while parsing the data field of the bulk request content",
ResponseCodeConstants.INVALID_SYNTAX);
}
}

private String getUsersOrMembersKey(String path) {
return path.contains(SCIMConstants.ROLE_ENDPOINT) ? SCIMConstants.RoleSchemaConstants.USERS :
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
SCIMConstants.GroupSchemaConstants.MEMBERS;
}

/**
* This method is used to get the user array from the data JSON object.
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @param dataJson SCIM data JSON object
* @param method HTTP method
* @param usersOrMembersKey Users or members key
*
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @return User array
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @throws JSONException JSON exception
*/
private JSONArray getUserArray(JSONObject dataJson, String method, String usersOrMembersKey)
throws JSONException {
switch (method) {
case SCIMConstants.OperationalConstants.POST:
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
case SCIMConstants.OperationalConstants.PUT:
return dataJson.optJSONArray(usersOrMembersKey);
case SCIMConstants.OperationalConstants.PATCH:
return getUserArrayForPatch(dataJson, usersOrMembersKey);
default:
return null;
}
}

private JSONArray getUserArrayForPatch(JSONObject dataJson, String usersOrMembersKey) throws JSONException {
JSONArray operations = dataJson.optJSONArray(SCIMConstants.OperationalConstants.OPERATIONS);

if (operations != null) {
for (int i = 0; i < operations.length(); i++) {
JSONObject operation = operations.getJSONObject(i);
if (isAddOperation(operation, usersOrMembersKey)) {
if (operation.has(SCIMConstants.OperationalConstants.PATH)) {
return operation.optJSONArray(SCIMConstants.OperationalConstants.VALUE);
} else {
JSONObject valueObject = operation.optJSONObject(SCIMConstants.OperationalConstants.VALUE);
if (valueObject != null) {
return valueObject.optJSONArray(usersOrMembersKey);
}
}
}
}
}
return null;
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
}

private boolean isAddOperation(JSONObject operation, String path) throws JSONException {
String operationType = operation.optString(SCIMConstants.OperationalConstants.OP);
String operationPath = operation.optString(SCIMConstants.OperationalConstants.PATH, "");

return SCIMConstants.OperationalConstants.ADD.equalsIgnoreCase(operationType) &&
(operationPath.equals(path) || operationPath.isEmpty());
}

/**
* This method is used to get user id bulk id mapping from the bulk user operation response.
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @param bulkUserOperationResponse Bulk user operation response
*
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @return Bulk id user id mapping
*/
private static Map<String, String> getUserIdBulkIdMapping(List<BulkResponseContent> bulkUserOperationResponse) {
Map<String, String> userIdMappings = new HashMap<>();

PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
for (BulkResponseContent bulkResponse : bulkUserOperationResponse) {
String bulkId = bulkResponse.getBulkID();

SCIMResponse response = bulkResponse.getScimResponse();
if (response.getResponseStatus() == ResponseCodeConstants.CODE_CREATED) {
String locationHeader = response.getHeaderParamMap().get(SCIMConstants.LOCATION_HEADER);

if (locationHeader != null) {
String[] locationHeaderParts = locationHeader.split("/");
String userId = locationHeaderParts[locationHeaderParts.length - 1];
userIdMappings.put(bulkId, userId);
}
}
}
return userIdMappings;
}
}