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

Azure patches multi-value attributes not like standard #516

Closed
palmique opened this issue Aug 25, 2023 · 8 comments
Closed

Azure patches multi-value attributes not like standard #516

palmique opened this issue Aug 25, 2023 · 8 comments

Comments

@palmique
Copy link

If setting up a user mapping via AppRoleAssignmentsComplex expression (as described via https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/customize-application-attributes), and defining target attribute "roles" as multi-value, Azure applies patch as following:

    {
        "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\"}"
            }
        ]
    }

According standard, patch should look like following (with complex object serialized as string):

    {
        "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\"}"
        ]
    }

Does "Captain-P-Goldfish" already support Azure fix for this issue (as for other Azure issues)? I don't see other option for provisioning of more than 1 role per user.

@Captain-P-Goldfish
Copy link
Owner

I am not sure if I can follow your question. Both representations should produce the same result:

if the following test is executed with the two PatchOperations:

  public static Stream<Arguments> testStrings()
  {
    // @formatter:off
    return Stream.of(Arguments.arguments("""
                    {
                      "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
                      "Operations" : [ {
                        "path" : "roles",
                        "op" : "add",
                        "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\\"}" ]
                      } ]
                    }
                    """),
                     Arguments.arguments("""
                       {
                         "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
                         "Operations" : [ {
                           "path" : "roles",
                           "op" : "add",
                           "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"
                                       }
                                     ]
                         } ]
                       }
                       """));
    // @formatter:on
  }

  /**
   * // TODO
   */
  @ParameterizedTest
  @MethodSource("testStrings()")
  public void test(String json)
  {
    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();
    PatchOpRequest patchOpRequest = JsonHelper.readJsonDocument(json, 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());
  }

I get the following result in both cases:

{
  "schemas" : [ "urn:ietf:params:scim:schemas:core:2.0:User" ],
  "id" : "542de6cf-8a9d-4088-97d1-7d217fa80fb7",
  "userName" : "goldfish",
  "roles" : [ {
    "id" : "827f0d2e-be15-4d8f-a8e3-f7697239c112",
    "value" : "DocumentMgmt-BuyerAdmin",
    "displayName" : "DocumentMgmt BuyerAdmin"
  }, {
    "id" : "8ae06bd4-35bb-4fcd-977e-12e074ad1192",
    "value" : "Buyer-Admin",
    "displayName" : "Buyer Admin"
  } ],
  "meta" : {
    "lastModified" : "2023-08-25T12:00:30.1889959+02:00"
  }
}

@palmique
Copy link
Author

palmique commented Aug 25, 2023

Issue is NOT, that Azure serializes the complex objects as string - that works.
Issue is, that Azure sends an array of objects (with "value" sub attribute that takes the serialized complex object). It's a strange way of sending an array of complex objects.

 {
        "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\"}"
            }
        ]
    }

Following patch operation is according to schema:

{
        "op": "Add",
        "path": "roles",
        "value": [
            "{\"id\":\"827f0d2e-be15-4d8f-a8e3-f8697239c112\",\"value\":\"DocumentMgmt-Admin\",\"displayName\":\"DocumentMgmt Admin\"}",
            "{\"id\":\"8ae06bd4-35bb-4fcd-977e-14e074ad1192\",\"value\":\"Admin\",\"displayName\":\"Admin\"}"
        ]
    }

My idea is to provide another Azure workaround, which converts 1st (invalid) patch variant into 2nd (valid) one.

@Captain-P-Goldfish
Copy link
Owner

I do not see that a workaround is necessary. The test above shows that the json is correctly parsed if the value -attributes values are json-objects.

I just tried this:

PATCH http://localhost:8080/scim/v2/Users/9c3c9a8d-6b69-4b18-8a83-b172f7005f16
Content-Type: application/scim+json

{
   "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
   "Operations" : [ {
   "path" : "roles",
   "op" : "add",
   "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"
         }]
   }]
}

and no errors. Everything works well.

@palmique
Copy link
Author

palmique commented Aug 28, 2023

Seems, that there is a misunderstanding? Issue is, that roleObjects are additionally wrapped by another object with single "value" attribute containing serialized roleObject.

Issue is also described here: https://github.com/MicrosoftDocs/azure-docs/issues/50772
Unfortunately, MS seems not to prioritize the fixing of SCIM compliance issues...

@Captain-P-Goldfish
Copy link
Owner

ah sorry. I misread the json examples. Since microsoft does not seem to fix this problem I think we could add an additional workaround. But I won't have capacities left to do it before next week.

@palmique
Copy link
Author

Hi Pascal,

my current workaround rewrites request body for PATCH operation. Feel free to reuse:

if (RequestMethod.PATCH.name().equals(request.getMethod())) {
   JsonNode node = JsonHelper.readJsonDocument(requestBody);
   JsonNode node0 = node.get("Operations");
   if (node0.isArray()) {
   	node0.forEach(node1 -> {
   		if (node1.has("value")) {
   			JsonNode node2 = node1.get("value");
   			if (node2.isArray()) {
   				for (int i = 0; i < node2.size(); i++) {
   					JsonNode node3 = node2.get(i);
   					if (node3.has("value")) {
   						JsonNode node4 = node3.get("value");
   						if (JsonHelper.isValidJson(node4.textValue())) {
   							((ArrayNode) node2).set(i, node4);
   						}
   					}
   				}
   			}
   		}
   	});
   }
   requestBody = JsonHelper.toPrettyJsonString(node);
}

BTW: MS provides an (incomplete) overview regarding SCIM 2.0 compliance: https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/application-provisioning-config-problem-scim-compatibility#flags-to-alter-the-scim-behavior

Regards,
Franz

@Captain-P-Goldfish
Copy link
Owner

just a thought, but there seems to be some kind of feature-flag that changes the behaviour of the SCIM-client. May it be possible that this will already fix the problem?

@Captain-P-Goldfish
Copy link
Owner

sorry for the long delay. But the pile of work is huge. This feature is now next on my list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants