Monday, February 18, 2019

Enabling SCIM using Microsoft.SystemForCrossDomainIdentityManagement - user PATCH request fails

Introduction

SCIM is a protocol to basically sync users and groups (and other resources) meaning that you can have your users on Azure AD, Onelogin, Octa, etc. and keep provisioning the new ones, updating the current ones and deprovision the ones that have left your company to the third party applications.

A simple documentation of SCIM can be found here:
http://www.simplecloud.info/

For .net developers, Microsoft has implemented a nuget package that contains an implementation of the protocol:
https://www.nuget.org/packages/Microsoft.SystemForCrossDomainIdentityManagement/
Which is also mentioned in their updated documentation:
https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups

The Problems
Since Microsoft has the most prominent Identity provider (Active Directory) they have implemented the protocol slightly different than what it was.
1 issue was that they were sending a list of values on a PATCH request for everything including the scaler values. For example lets say someone's displayName has changed. The nuget was designed to receive
{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"] ,
"Operations":[ {"op":"Replace","path":"displayName","value":
[ {"$ref":null,"value":"User x"}]}]
}}
Instead of
{ "schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ {"op":"Replace","path":"displayName","value":" User x"} ]}
Meaning that no one else other than Azure AD could have called your application with PATCH if they have done it right!

When Azure AD calls our endpoint, it breaks without even reaching the part that is in our hands. The error is pretty simple:

Failed to update User 'xxx@yyy.zzz' in customappsso; Error: StatusCode: BadRequest Message: Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details. Web Response: {"Message":"The request is invalid."}. We will retry this operation on the next synchronization attempt.
It got wrose!

In December 2018 the problem gor worse, because they have actually corrected their Azure AD's behaviour Advertised here but then didn't fix the nuget package. So your code would just break without you knowing what is wrong and why.
I have created a support case for Microsoft and basically wrote everywhere possible, but after a month still there is no update on the package so I've decided to write a small fix for it.



The Solution
The code for the package is not open source and basically there is no right way to do it other than Microsoft change their code and support the correct behaviour, but a simple way to correct their behaviour is to intercept every request using a middleware and change it to the expected format(if it is not already).


public class PatchRequestUpdaterMiddleware : OwinMiddleware

{

     private const string OperationValueFinderRegex = "({[\\s\\w\":,.\\[\\]\\\\]*op[\\s\\w\":,.\\[\\]\\\\]*\"value\"\\s*:\\s*)(\"[\\w\\s\\-,.@?!*;\'\\(\\)]+\")"; //{"op":"x","value":"Andrew1"}

public override async Task Invoke(IOwinContext context)

    {

        if (context.Request.Method.ToLower() != "patch")

        {

            await Next.Invoke(context);

            return;

        }

        var streamReader = new StreamReader(context.Request.Body);

        string body = streamReader.ReadToEnd();

        body = Regex.Replace(body, OperationValueFinderRegex, m => $"{m.Groups[1].Value}[{{\"value\":{m.Groups[2].Value}}}]"); //{"op":"x","value":"Ashkan"} ==>> {"op":"x","value":[{"value":"Ashkan"}]}

        context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));

        await Next.Invoke(context);

    }

 }

And just add this to the provider that you have created:

class myProvider:ProviderBase

{

....

   private void OnServiceStartup(IAppBuilder appBuilder, HttpConfiguration configuration)

        {

...

  appBuilder.Use<PatchRequestUpdaterMiddleware>();

...

}

2 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete