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 4 commits
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,14 @@ 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;
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,11 @@
*/
package org.wso2.charon3.core.protocol;

import org.apache.commons.lang.StringUtils;
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 +35,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,96 +139,117 @@ 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) throws BadRequestException {

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

private BulkResponseContent getBulkResponseContent(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);
}

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;
}

private String extractIDFromPath(String path) throws BadRequestException {

String [] parts = path.split("[/]");
if (parts[2] != null) {
return parts[2];
Expand All @@ -231,8 +261,8 @@ private String extractIDFromPath(String path) throws BadRequestException {

private BulkResponseContent createBulkResponseContent(SCIMResponse response, String method,
BulkRequestContent requestContent) {
BulkResponseContent bulkResponseContent = new BulkResponseContent();

BulkResponseContent bulkResponseContent = new BulkResponseContent();
bulkResponseContent.setScimResponse(response);
bulkResponseContent.setMethod(method);
if (response.getHeaderParamMap() != null) {
Expand All @@ -242,7 +272,6 @@ private BulkResponseContent createBulkResponseContent(SCIMResponse response, Str
bulkResponseContent.setVersion(requestContent.getVersion());

return bulkResponseContent;

}

private void errorsCheck(SCIMResponse response) {
Expand All @@ -252,4 +281,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.
* @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 (StringUtils.isNotBlank(userId)) {
user.put(SCIMConstants.OperationalConstants.VALUE, userId);
}
}
}
}
bulkRequestContent.setData(dataJson.toString());
} 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
*
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.
* @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
*
PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
* @param bulkUserOperationResponse Bulk user operation response.
* @return Bulk id user id mapping.
*/
private static Map<String, String> getUserIdBulkIdMapping(List<BulkResponseContent> bulkUserOperationResponse) {

PasinduYeshan marked this conversation as resolved.
Show resolved Hide resolved
Map<String, String> userIdMappings = new HashMap<>();
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;
}
}