diff --git a/scim-sdk-common/pom.xml b/scim-sdk-common/pom.xml index b0e92a44..e4ddcb3a 100644 --- a/scim-sdk-common/pom.xml +++ b/scim-sdk-common/pom.xml @@ -55,7 +55,7 @@ ${project.basedir}/src/main/resources/de/captaingoldfish/scim/sdk/common/meta/service-provider.schema.json - 26f37caa5c5d9bd817211da114636b40 + fae587470a4e58531b319f7c2edcbddb md5 diff --git a/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/constants/AttributeNames.java b/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/constants/AttributeNames.java index d1a0292c..35fffe9c 100644 --- a/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/constants/AttributeNames.java +++ b/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/constants/AttributeNames.java @@ -276,6 +276,14 @@ public static class Custom * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/416 */ public static final String ACTIVATE_MS_AZURE_FILTER_WORKAROUND = "activateMsAzureFilterWorkaround"; + + /** + * A workaround indicator to handle MsAzures illegal value-subattribute notation. There might be cases in + * which this switch will cause with normal execution. That is why this must be activated explicitly. + * + * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516 + */ + public static final String ACTIVATE_MS_AZURE_VALUE_SUB_ATTRIBUTE_WORKAROUND = "activateMsAzureValueSubAttributeWorkaround"; } /** diff --git a/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/resources/complex/PatchConfig.java b/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/resources/complex/PatchConfig.java index a20b4a29..8bfe1999 100644 --- a/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/resources/complex/PatchConfig.java +++ b/scim-sdk-common/src/main/java/de/captaingoldfish/scim/sdk/common/resources/complex/PatchConfig.java @@ -19,7 +19,10 @@ public class PatchConfig extends ScimObjectNode { @Builder - public PatchConfig(Boolean supported, Boolean activateSailsPointWorkaround, Boolean activateMsAzureWorkaround) + public PatchConfig(Boolean supported, + Boolean activateSailsPointWorkaround, + Boolean activateMsAzureWorkaround, + Boolean activateMsAzureValueSubAttributeWorkaround) { super(null); setSupported(supported); @@ -67,6 +70,7 @@ public void setActivateSailsPointWorkaround(Boolean activateSailsPointWorkaround * A workaround to handle filter-expressions in patch-paths as attributes that will be added to the resource * * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/416 + * @see de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchFilterWorkaround */ public boolean isMsAzureFilterWorkaroundActive() { @@ -77,12 +81,36 @@ public boolean isMsAzureFilterWorkaroundActive() * A workaround to handle filter-expressions in patch-paths as attributes that will be added to the resource * * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/416 + * @see de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchFilterWorkaround */ public void setMsAzureFilterWorkaroundActive(Boolean msAzureWorkaroundActive) { setAttribute(AttributeNames.Custom.ACTIVATE_MS_AZURE_FILTER_WORKAROUND, msAzureWorkaroundActive); } + /** + * A workaround to handle MsAzures illegal value-subattribute notation + * + * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516 + * @see de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchValueSubAttributeRebuilder + */ + public boolean isMsAzureValueSubAttributeWorkaroundActive() + { + return getBooleanAttribute(AttributeNames.Custom.ACTIVATE_MS_AZURE_VALUE_SUB_ATTRIBUTE_WORKAROUND).orElse(false); + } + + /** + * A workaround to handle MsAzures illegal value-subattribute notation + * + * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516 + * @see de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchValueSubAttributeRebuilder + */ + public void setMsAzureValueSubAttributeWorkaroundActive(Boolean msAzureValueSubAttributeWorkaroundActive) + { + setAttribute(AttributeNames.Custom.ACTIVATE_MS_AZURE_VALUE_SUB_ATTRIBUTE_WORKAROUND, + msAzureValueSubAttributeWorkaroundActive); + } + /** * override lombok builder with public constructor */ diff --git a/scim-sdk-common/src/main/resources/de/captaingoldfish/scim/sdk/common/meta/service-provider.schema.json b/scim-sdk-common/src/main/resources/de/captaingoldfish/scim/sdk/common/meta/service-provider.schema.json index 179131f8..59e805a3 100644 --- a/scim-sdk-common/src/main/resources/de/captaingoldfish/scim/sdk/common/meta/service-provider.schema.json +++ b/scim-sdk-common/src/main/resources/de/captaingoldfish/scim/sdk/common/meta/service-provider.schema.json @@ -56,7 +56,18 @@ { "name": "activateMsAzureFilterWorkaround", "type": "boolean", - "description": "A workaround to handle filter-expressions in patch-paths as attributes that will be added to the resource.", + "description": "A workaround to handle filter-expressions in patch-paths as attributes that will be added to the resource. @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/416", + "mutability": "readOnly", + "returned": "request", + "uniqueness": "none", + "multiValued": false, + "required": false, + "caseExact": false + }, + { + "name": "activateMsAzureValueSubAttributeWorkaround", + "type": "boolean", + "description": "A workaround to handle MsAzures illegal value-subattribute notation. @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516", "mutability": "readOnly", "returned": "request", "uniqueness": "none", diff --git a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/PatchHandler.java b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/PatchHandler.java index 4c9d5856..f24421a1 100644 --- a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/PatchHandler.java +++ b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/PatchHandler.java @@ -23,6 +23,7 @@ import de.captaingoldfish.scim.sdk.server.filter.AttributePathRoot; import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchAttributeRebuilder; import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchRemoveRebuilder; +import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchValueSubAttributeRebuilder; import de.captaingoldfish.scim.sdk.server.schemas.ResourceType; import de.captaingoldfish.scim.sdk.server.utils.RequestUtils; import lombok.Getter; @@ -166,6 +167,14 @@ private boolean handlePatchOp(ResourceNode resource, PatchRequestOperation opera values); path = msAzureWorkaround.fixPath(); } + + if (patchConfig.isMsAzureValueSubAttributeWorkaroundActive()) + { + MsAzurePatchValueSubAttributeRebuilder msAzureWorkaround; + msAzureWorkaround = new MsAzurePatchValueSubAttributeRebuilder(operation.getOp(), values); + values = msAzureWorkaround.fixValues(); + } + PatchTargetHandler patchTargetHandler = new PatchTargetHandler(patchConfig, resourceType, operation.getOp(), path); boolean changeWasMade = patchTargetHandler.handleOperationValues(resource, values); diff --git a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchAttributeRebuilder.java b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchAttributeRebuilder.java index 43ad55df..41879cbc 100644 --- a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchAttributeRebuilder.java +++ b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchAttributeRebuilder.java @@ -41,7 +41,7 @@ * } * * - * the value in the request must not be present. Instead the request should look like this: + * the value in the request must not be present. Instead, the request should look like this: * *
  * PATCH /scim/Users/2752513
diff --git a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchFilterWorkaround.java b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchFilterWorkaround.java
index cd689b7d..a95d8673 100644
--- a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchFilterWorkaround.java
+++ b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchFilterWorkaround.java
@@ -15,6 +15,61 @@
 public class MsAzurePatchFilterWorkaround
 {
 
+  /**
+   * this method is a workaround for MsAzures expected behaviour that a value is added to a multivalued complex
+   * node if it is reference in a patch-filter but not added within the attribute itself
+   *
+   * 
+   * {
+   *   "schemas": [
+   *     "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+   *   ],
+   *   "Operations": [
+   *     {
+   *       "op": "add",
+   *       "path": "emails[type eq \"work\"].value",
+   *       "value": "max@mustermann.de"
+   *     }
+   *   ]
+   * }
+   * 
+ * + * must be rebuilt to: + * + *
+   * {
+   *   "schemas": [
+   *     "urn:ietf:params:scim:api:messages:2.0:PatchOp"
+   *   ],
+   *   "Operations": [
+   *     {
+   *       "op": "add",
+   *       "path": "emails[type eq \"work\"].value",
+   *       "value": [
+   *          {
+   *             "value": "max@mustermann.de"
+   *          },
+   *          {
+   *             "type": "work"
+   *          }
+   *       ]
+   *     }
+   *   ]
+   * }
+   * 
+ * + * The complete rebuild is not done within this method. This method will simply create the last objectNode + * from the filter-expression: + * + *
+   *  {
+   *    "type": "work"
+   *  }
+   * 
+ * + * @param path the patch filter expression node. + * @return the + */ public ScimObjectNode createAttributeFromPatchFilter(AttributePathRoot path) { SchemaAttribute schemaAttribute = path.getSchemaAttribute(); diff --git a/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilder.java b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilder.java new file mode 100644 index 00000000..ddbe3bdd --- /dev/null +++ b/scim-sdk-server/src/main/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilder.java @@ -0,0 +1,165 @@ +package de.captaingoldfish.scim.sdk.server.patch.msazure; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import de.captaingoldfish.scim.sdk.common.constants.AttributeNames; +import de.captaingoldfish.scim.sdk.common.constants.enums.PatchOp; +import de.captaingoldfish.scim.sdk.common.utils.JsonHelper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + + +/** + * This class is a workaround handler in order to handle the broken patch requests of Microsoft Azure. Azure + * sends illegal patch requests that look like this: + * + *
+ * {
+ *         "op": "Add",
+ *         "path": "roles",
+ *         "value": [
+ *             {
+ *                 "value": "{\"id\":\"827f0d2e-be15-4d8f-a8e3-f8697239c112\",
+ *                            \"value\":\"DocumentMgmt-Admin\",
+ *                            \"displayName\":\"DocumentMgmt Admin\"}"
+ *             },
+ *             {
+ *                 "value": "{\"id\":\"8ae06bd4-35bb-4fcd-977e-14e074ad1192\",
+ *                            \"value\":\"Admin\",
+ *                            \"displayName\":\"Admin\"}"
+ *             }
+ *         ]
+ *     }
+ * 
+ * + * The problem in this request is the nested value-attribute that should not be there. Instead, the request + * should look like this: + * + *
+ *    {
+ *         "op": "Add",
+ *         "path": "roles",
+ *         "value": [
+ *
+ *             "{\"id\":\"827f0d2e-be15-4d8f-a8e3-f7697239c112\",
+ *               \"value\":\"DocumentMgmt-BuyerAdmin\",
+ *               \"displayName\":\"DocumentMgmt BuyerAdmin\"}",
+ *
+ *             "{\"id\":\"8ae06bd4-35bb-4fcd-977e-12e074ad1192\",
+ *               \"value\":\"Buyer-Admin\",
+ *               \"displayName\":\"Buyer Admin\"}"
+ *         ]
+ *     }
+ * 
+ * + * @author Pascal Knueppel + * @since 07.10.2023 + */ +@Slf4j +@RequiredArgsConstructor +public class MsAzurePatchValueSubAttributeRebuilder +{ + + /** + * the patch operation that is currently executed + */ + private final PatchOp patchOp; + + /** + * the values of the patch operation. This attribute should actually be empty + */ + private final List patchValues; + + /** + * this method will try to resolve a PatchRequest as described in the class-documentation to its correct state + * + * @param patchValues the values sent in the PatchRequest + * @return the fixed values from the PatchRequest + */ + public List fixValues() + { + // simply a check to return early from this method if a remove operation is used + if (PatchOp.REMOVE.equals(patchOp)) + { + log.trace("[MS Azure value-subAttribute workaround] only handling 'REPLACE' and 'ADD' requests"); + return patchValues; + } + + if (patchValues == null || patchValues.isEmpty()) + { + log.trace("[MS Azure value-subAttribute workaround] not executed for values-list is empty"); + return patchValues; + } + + List fixedValues = new ArrayList<>(); + for ( String patchValue : patchValues ) + { + final JsonNode jsonNode; + try + { + jsonNode = JsonHelper.readJsonDocument(patchValue); + } + catch (Exception ex) + { + log.trace("[MS Azure value-subAttribute workaround] ignored value-node because it is no valid JSON " + + "object-node"); + fixedValues.add(patchValue); + continue; + } + + boolean isObjectNode = jsonNode instanceof ObjectNode; + if (!isObjectNode) + { + log.trace("[MS Azure value-subAttribute workaround] ignored value because it is no JSON-ObjectNode"); + fixedValues.add(patchValue); + continue; + } + + ObjectNode objectNode = (ObjectNode)jsonNode; + if (objectNode.size() != 1) + { + log.trace("[MS Azure value-subAttribute workaround] ignored JSON-ObjectNode because it has less or more " + + "than 1 sub-nodes"); + fixedValues.add(patchValue); + continue; + } + + JsonNode innerValueNode = objectNode.get(AttributeNames.RFC7643.VALUE); + if (innerValueNode == null) + { + log.trace("[MS Azure value-subAttribute workaround] ignored JSON-ObjectNode because it has no value-node"); + fixedValues.add(patchValue); + continue; + } + + final JsonNode innerObjectNode; + try + { + innerObjectNode = JsonHelper.readJsonDocument(innerValueNode.textValue()); + } + catch (Exception ex) + { + log.trace("[MS Azure value-subAttribute workaround] ignored inner value-node because it is no valid JSON-node"); + fixedValues.add(patchValue); + continue; + } + + isObjectNode = innerObjectNode instanceof ObjectNode; + if (!isObjectNode) + { + log.trace("[MS Azure value-subAttribute workaround] ignored inner value-node because it is no not a JSON " + + "object-node"); + fixedValues.add(patchValue); + continue; + } + + fixedValues.add(innerObjectNode.toString()); + } + + return fixedValues; + } +} diff --git a/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/PatchAddResourceHandlerTest.java b/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/PatchAddResourceHandlerTest.java index 8b1544e1..6e986a55 100644 --- a/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/PatchAddResourceHandlerTest.java +++ b/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/PatchAddResourceHandlerTest.java @@ -5,12 +5,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; @@ -24,6 +26,7 @@ import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import de.captaingoldfish.scim.sdk.common.constants.AttributeNames; @@ -45,6 +48,7 @@ import de.captaingoldfish.scim.sdk.common.resources.complex.Name; import de.captaingoldfish.scim.sdk.common.resources.complex.PatchConfig; import de.captaingoldfish.scim.sdk.common.resources.multicomplex.Email; +import de.captaingoldfish.scim.sdk.common.resources.multicomplex.PersonRole; import de.captaingoldfish.scim.sdk.common.utils.JsonHelper; import de.captaingoldfish.scim.sdk.server.resources.AllTypes; import de.captaingoldfish.scim.sdk.server.schemas.ResourceType; @@ -1576,6 +1580,140 @@ public void testHandleReplaceOnComplexTypesAsAdd() Matchers.containsInAnyOrder("test1", "test2")); } + /** + * this patch request is based on the github-issue: https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516. + * MsAzure is building illegal patch-requests that prevents a correct resolving of the patch request. + */ + private String getMsAzureSubValueAttributeTestString() + { + return " { \"schemas\" : [ \"urn:ietf:params:scim:api:messages:2.0:PatchOp\" ], "// + + "\"Operations\" : [ "// + + " { \"path\" : \"roles\", "// + + " \"op\" : \"add\", "// + + " \"value\" : [ " // + + " {" // + + " \"value\": \"{\\\"$ref\\\":\\\"827f0d2e-be15-4d8f-a8e3-f7697239c112\\\"," + + " \\\"value\\\":\\\"DocumentMgmt-BuyerAdmin\\\"," + + " \\\"display\\\":\\\"DocumentMgmt BuyerAdmin\\\"" + + " }\"" // + + " },"// + + " {" // + + " \"value\": \"{\\\"$ref\\\":\\\"8ae06bd4-35bb-4fcd-977e-12e074ad1192\\\"," + + " \\\"value\\\":\\\"Buyer-Admin\\\"," + + " \\\"display\\\":\\\"Buyer Admin\\\"" + + " }\"" // + + " }"// + + " ]"// + + " } "// + + "]}"; + } + + /** + * This test makes sure that the illegal MsAzure Patch-Requests with the value sub-attribute object structure + * is resolved correctly if the feature is activated + * + * @see #getMsAzureSubValueAttributeTestString() + * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516 + */ + @DisplayName("MsAzure value-subAttribute workaround is active and resolves correctly") + @Test + public void testMsAzureSubValueAttributeResolvingWithWorkaroundActive() + { + serviceProvider.getPatchConfig().setMsAzureValueSubAttributeWorkaroundActive(true); + + JsonNode userSchema = JsonHelper.loadJsonDocument(ClassPathReferences.USER_SCHEMA_JSON); + JsonNode userResourceTypeNode = JsonHelper.loadJsonDocument(ClassPathReferences.USER_RESOURCE_TYPE_JSON); + JsonNode enterpriseUserSchema = JsonHelper.loadJsonDocument(ClassPathReferences.ENTERPRISE_USER_SCHEMA_JSON); + ResourceType userResourceType = resourceTypeFactory.registerResourceType(null, + userResourceTypeNode, + userSchema, + enterpriseUserSchema); + + final String resourceId = UUID.randomUUID().toString(); + final String patchRequestString = getMsAzureSubValueAttributeTestString(); + PatchOpRequest patchOpRequest = JsonHelper.readJsonDocument(patchRequestString, PatchOpRequest.class); + User user = User.builder().id(resourceId).userName("goldfish").build(); + + PatchHandler patchHandler = new PatchHandler(serviceProvider.getPatchConfig(), userResourceType); + User patchedUser = patchHandler.patchResource(user, patchOpRequest); + + log.info(patchedUser.toPrettyString()); + + List personRoles = patchedUser.getRoles(); + Assertions.assertEquals(2, personRoles.size()); + + { + PersonRole role1 = personRoles.get(0); + Assertions.assertEquals("827f0d2e-be15-4d8f-a8e3-f7697239c112", role1.getRef().get()); + Assertions.assertEquals("DocumentMgmt-BuyerAdmin", role1.getValue().get()); + Assertions.assertEquals("DocumentMgmt BuyerAdmin", role1.getDisplay().get()); + } + + { + PersonRole role2 = personRoles.get(1); + Assertions.assertEquals("8ae06bd4-35bb-4fcd-977e-12e074ad1192", role2.getRef().get()); + Assertions.assertEquals("Buyer-Admin", role2.getValue().get()); + Assertions.assertEquals("Buyer Admin", role2.getDisplay().get()); + } + } + + /** + * This test makes sure that the illegal MsAzure Patch-Requests with the value sub-attribute object structure + * is not resolved anymore if the feature is deactivated + * + * @see #getMsAzureSubValueAttributeTestString() + * @see https://github.com/Captain-P-Goldfish/SCIM-SDK/issues/516 + */ + @DisplayName("MsAzure value-subAttribute workaround is inactive and resolves correctly") + @Test + public void testMsAzureSubValueAttributeResolvingWithWorkaroundInActive() + { + serviceProvider.getPatchConfig().setMsAzureValueSubAttributeWorkaroundActive(false); + + JsonNode userSchema = JsonHelper.loadJsonDocument(ClassPathReferences.USER_SCHEMA_JSON); + JsonNode userResourceTypeNode = JsonHelper.loadJsonDocument(ClassPathReferences.USER_RESOURCE_TYPE_JSON); + JsonNode enterpriseUserSchema = JsonHelper.loadJsonDocument(ClassPathReferences.ENTERPRISE_USER_SCHEMA_JSON); + ResourceType userResourceType = resourceTypeFactory.registerResourceType(null, + userResourceTypeNode, + userSchema, + enterpriseUserSchema); + + final String resourceId = UUID.randomUUID().toString(); + final String patchRequestString = getMsAzureSubValueAttributeTestString(); + PatchOpRequest patchOpRequest = JsonHelper.readJsonDocument(patchRequestString, PatchOpRequest.class); + User user = User.builder().id(resourceId).userName("goldfish").build(); + + PatchHandler patchHandler = new PatchHandler(serviceProvider.getPatchConfig(), userResourceType); + User patchedUser = patchHandler.patchResource(user, patchOpRequest); + + log.info(patchedUser.toPrettyString()); + + List personRoles = patchedUser.getRoles(); + Assertions.assertEquals(2, personRoles.size()); + + List values = patchOpRequest.getOperations().get(0).getValues(); + + { + PersonRole role1 = personRoles.get(0); + String expectedContent = JsonHelper.readJsonDocument(values.get(0), ObjectNode.class) + .get(AttributeNames.RFC7643.VALUE) + .textValue(); + Assertions.assertEquals(expectedContent, role1.getValue().get()); + Assertions.assertFalse(role1.getRef().isPresent()); + Assertions.assertFalse(role1.getDisplay().isPresent()); + } + + { + PersonRole role2 = personRoles.get(1); + String expectedContent = JsonHelper.readJsonDocument(values.get(1), ObjectNode.class) + .get(AttributeNames.RFC7643.VALUE) + .textValue(); + Assertions.assertEquals(expectedContent, role2.getValue().get()); + Assertions.assertFalse(role2.getRef().isPresent()); + Assertions.assertFalse(role2.getDisplay().isPresent()); + } + } + /** * this method returns a specific attribute definition that will be added to the enterprise user that is used * as extension for the alltypes schema. this shall provoke a naming conflict with a complex type in the diff --git a/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilderTest.java b/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilderTest.java new file mode 100644 index 00000000..1b7a64a2 --- /dev/null +++ b/scim-sdk-server/src/test/java/de/captaingoldfish/scim/sdk/server/patch/msazure/MsAzurePatchValueSubAttributeRebuilderTest.java @@ -0,0 +1,211 @@ +package de.captaingoldfish.scim.sdk.server.patch.msazure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import de.captaingoldfish.scim.sdk.common.constants.enums.PatchOp; +import de.captaingoldfish.scim.sdk.common.resources.multicomplex.PersonRole; +import de.captaingoldfish.scim.sdk.common.utils.JsonHelper; + + +/** + * @author Pascal Knueppel + * @since 07.10.2023 + */ +public class MsAzurePatchValueSubAttributeRebuilderTest +{ + + /** + * makes sure that the workaround-handler returns the original list in case of a remove-operation + */ + @DisplayName("Remove operations are not handled") + @Test + public void testRemoveOperationsAreNotHandles() + { + final PatchOp patchOp = PatchOp.REMOVE; + final List values = new ArrayList<>(Arrays.asList("{\"value\": \"{\\\"display\\\":\\\"DocumentMgmt-BuyerAdmin\\\"}\"}", + "{\"value\": \"{\\\"display\\\":\\\"Buyer-Admin\\\"}\"}")); + + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that an empty or a null list of values will not cause any errors + */ + @DisplayName("Empty or null lists do not cause errors") + @Test + public void testEmptyListsDoNotCauseErrors() + { + final PatchOp patchOp = PatchOp.ADD; + + { + MsAzurePatchValueSubAttributeRebuilder handler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + new ArrayList<>()); + List fixedValues = handler.fixValues(); + Assertions.assertTrue(fixedValues.isEmpty()); + } + { + MsAzurePatchValueSubAttributeRebuilder handler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, null); + List fixedValues = handler.fixValues(); + Assertions.assertNull(fixedValues); + } + } + + /** + * makes sure that no error occurs if the sub-value attribute contains an invalid json structure + */ + @DisplayName("Invalid-Json-Object in value-subAttribute does not cause errors") + @Test + public void testInvalidJsonObjectInSubValueCausesNoError() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = new ArrayList<>(Arrays.asList("{\"display\" \"Admin\"", "{\"display\" \"User\"")); + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that no error occurs if the sub-value attribute is a normal patch-value request without + * underlying JSON-objects + */ + @DisplayName("None-Json-Object in value-subAttribute does not cause errors") + @Test + public void testNonJsonObjectInSubValueCausesNoError() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = new ArrayList<>(Arrays.asList("{\"display\": \"Admin\"}", "{\"display\": \"User\"}")); + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that no error occurs if the value-attribute is a normal patch-value request without underlying + * JSON-objects + */ + @DisplayName("Array-Json-Object in value-attribute does not cause errors") + @Test + public void testArrayJsonObjectInValueCausesNoError() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = new ArrayList<>(Arrays.asList("[{\"display\": \"Admin\"}]", + "[{\"display\": \"User\"}]")); + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that no error occurs if the sub-value-attribute is a normal patch-value request without + * underlying JSON-objects + */ + @DisplayName("Array-Json-Object in value-subAttribute does not cause errors") + @Test + public void testArrayJsonObjectInSubValueCausesNoError() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = new ArrayList<>(Arrays.asList("{\"display\": [\"Admin\"]}", + "{\"display\": [\"User\"]}")); + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * Makes sure that the handler is ignored if the value-sub-attribute has siblings. + */ + @DisplayName("Handler does not execute if sub-value-attribute has siblings") + @Test + public void testIgnoreInnerSubValueIfMoreThanOneAttribute() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = Collections.singletonList("{\"value\": \"{\\\"display\\\":\\\"DocumentMgmt-BuyerAdmin\\\"}\"," + + "\"$ref\": \"123456789\"}"); + + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that the node is not parsed if the value-sub-attribute contains illegal non parseable json + */ + @DisplayName("Handler is ignored if the inner value-sub-attribute contains illegal json") + @Test + public void testInnerValueSubAttributeIsIgnoredWhenIllegalJson() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = Collections.singletonList("{\"value\": \"{\\\"display\\\"\\\"DocumentMgmt-BuyerAdmin\\\"\"}"); + + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that the node is not parsed if the value-sub-attribute is an array instead of an object + */ + @DisplayName("Handler is ignored if the inner value-sub-attribute is an array") + @Test + public void testInnerValueSubAttributeIsIgnoredWhenArray() + { + final PatchOp patchOp = PatchOp.ADD; + final List values = Collections.singletonList("{\"value\": \"[{\\\"display\\\":\\\"DocumentMgmt-BuyerAdmin\\\"}]\"}"); + + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertEquals(fixedValues, values); + } + + /** + * makes sure that the node is not parsed if the value-sub-attribute is an array instead of an object + */ + @DisplayName("Handler works wor ADD and REPLACE operations") + @ParameterizedTest + @ValueSource(strings = {"ADD", "REPLACE"}) + public void testHandlerWorksForAddAndReplaceOps(PatchOp patchOp) + { + final List values = new ArrayList<>(Arrays.asList("{\"value\": \"{\\\"display\\\":\\\"DocumentMgmt-BuyerAdmin\\\"}\"}", + "{\"value\": \"{\\\"display\\\":\\\"Buyer-Admin\\\"}\"}")); + + MsAzurePatchValueSubAttributeRebuilder workaroundHandler = new MsAzurePatchValueSubAttributeRebuilder(patchOp, + values); + List fixedValues = workaroundHandler.fixValues(); + Assertions.assertNotEquals(fixedValues, values); + { + PersonRole personRole1 = JsonHelper.readJsonDocument(fixedValues.get(0), PersonRole.class); + Assertions.assertEquals("DocumentMgmt-BuyerAdmin", personRole1.getDisplay().get()); + } + { + PersonRole personRole2 = JsonHelper.readJsonDocument(fixedValues.get(1), PersonRole.class); + Assertions.assertEquals("Buyer-Admin", personRole2.getDisplay().get()); + } + } +}