how to impersonate a user via odata
Asked Answered
S

2

19

We have been succesful in using the odata v8.1 endpoint in 2016 to impersonate a user.

Please note that the intended request flow is: Postman-->LocalHost Microservice-->CRM

Example of a working request from Postman-->CRM (directly, without going through the microservice)

Accept:application/json
Content-Type:application/json; charset=utf-8
OData-MaxVersion:4.0
OData-Version:4.0
MSCRMCallerID:d994d6ff-5531-e711-9422-00155dc0d345
Cache-Control:no-cache

Against the odata endpoint: ..../api/data/v8.1/leads

Note that this has been successful only when issued directly against the odata v8.1 endpoint via postman.

When attempting to do the same, having a service running locally (Postman-->LocalHost Service-->CRM), this fails, and simply ignores??? the MSCRMCallerID header.

Upon examining headers that were passed to the LocalHost Microservice from Postman, the request, as examined by the debugger in VS 2017:

{Method: POST, RequestUri: 'https://.../api/data/v8.1/leads', Version: 1.1, Content: System.Net.Http.StringContent, Headers:
{
  OData-Version: 4.0
  OData-MaxVersion: 4.0
  MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
  Cache-Control: no-cache
  Accept: application/json
  Content-Type: application/json; charset=utf-8
}}

The record is created succesfully, however on the CreatedBy field is the service username NOT the MSCRMCallerID username (d994d6ff-5531-e711-9422-00155dc0d345), and the CreatedOnBehalf field is empty.

What are we doing wrong?

How do we get this impersonation working from our service?

EDIT + More Info

Please note that I do believe that I've included all the relevant info, but if I have not, please let me know what other input I should provide on this issue.

What have I tried?

  1. changed the order of headers
  2. played with the case of the headers
  3. ensured that the guid is correct of the user for impersonation
  4. ensured that the user has both delegate and sys admin role (although this is irrelevant because this works when executing requesting directly against crm odata endpoint, rather than the endpoint that the our service exposes
  5. have tried to execute the request against both https AND http
  6. fiddler trace as shown below

Please note that this fiddler trace is a trace showing Postman --> Microservice request. It does not show the communication from the localhost microservice to CRM. (I'm not sure why, perhaps because it is encrypted)

POST https://localhost:19081/.....Leads/API/leads HTTP/1.1
Host: localhost:19081
Connection: keep-alive
Content-Length: 84
Cache-Control: no-cache
Origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
X-Postman-Interceptor-Id: d79b1d2e-2155-f2ec-4ad7-e9b63e7fb90d
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: ai_user=Ka2Xn|2017-05-25T17:30:57.941Z

{
    "subject": "created by mscrmcaller user2: d994d6ff-5531-e711-9422-00155dc0d345"
}

@Ram has suggested that we use the organization service to authenticate, is this an option, considering we are executing against Web API? Will the requested token still be valid. (Please note that this may be a silly question, and the reason is because I am not understanding how authentication works).

The following is a code snippet from how we are authenticating currently on every call:

//check headers to see if we got a redirect to the new location
            var shouldAuthenticate = redirectUri.AbsoluteUri.Contains("adfs/ls");

            if (!shouldAuthenticate)
            {
                return;
            }

            var adfsServerName = redirectUri.Authority;
            var queryParams = HttpUtility.ParseQueryString(redirectUri.Query);

            ServicePointManager.ServerCertificateValidationCallback +=
                (sender, cert, chain, sslPolicyErrors) => true;

            WSTrustChannelFactory factory = null;
            try
            {
                // use a UserName Trust Binding for username authentication
                factory = new WSTrustChannelFactory(
                    new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
                    $"https://{adfsServerName}/adfs/services/trust/13/usernamemixed")
                {
                    Credentials =
                    {
                        UserName =
                        {
                            UserName = $"{credential.Domain}\\{credential.UserName}",
                            Password = credential.Password
                        }
                    },
                    TrustVersion = TrustVersion.WSTrust13
                };

                var rst = new RequestSecurityToken
                {
                    RequestType = RequestTypes.Issue,
                    AppliesTo = new EndpointReference(_client.BaseAddress.AbsoluteUri),
                    TokenType = "urn:oasis:names:tc:SAML:1.0:assertion",
                    KeyType = KeyTypes.Bearer
                };

                var channel = factory.CreateChannel();
                channel.Issue(rst, out RequestSecurityTokenResponse rstr);

                var fedSerializer = new WSFederationSerializer();
                var rstrContent = fedSerializer.GetResponseAsString(rstr, new WSTrustSerializationContext());

                // construct a authentication form
                var crmauthenticaionPostDictionary = new Dictionary<string, string>
                {
                    {"wa", queryParams["wa"]},
                    {"wresult", rstrContent},
                    {"wctx", queryParams["wctx"]}
                };

                // post the authentication form to the website. 
                var crmAuthorizationPostResponse = _client.PostAsync(_client.BaseAddress.AbsoluteUri, new FormUrlEncodedContent(crmauthenticaionPostDictionary)).Result;

                var crmAuthorizationPostResponseString = crmAuthorizationPostResponse.Content.ReadAsStringAsync().Result;
                //we  should be authenticated here
                if (
                    !(
                        // we are correctly authorized if we got redirected to the correct address that we
                        // were trying to reach in the first place.
                        crmAuthorizationPostResponse.StatusCode == HttpStatusCode.Redirect
                        && crmAuthorizationPostResponse.Headers.Location == authenticationTestUri
                    )
                )
                {
                    throw new Exception("ADFS Authentication to CRM failed.");
                }
Smarmy answered 14/9, 2017 at 22:30 Comment(10)
Were you able to verify the header in fiddler or browser network tab?Devil
@ArunVinoth thank you for your question, i've updated the questionSmarmy
were you able to solve this?Devil
@ArunVinoth nope. would love to do a screen share session when you are ready. i've got an open ticket with microsoft, they've spent approximately 10 hours of their lowest level tech support, copying and pasting random code snippets they've googgledSmarmy
Can you show complete headers and cookies that go when you do Postman-->CRM? How do you authenticate to CRM in Postman and how do you do it in your service?Kagoshima
@l--''''''---------'''''''''''', Any update on this?Kagoshima
The caller ID should be Set in OrganizationServiceProxy . Otherwise it will not set the ID in CreatedBy FieldEisenhart
@l--''''''---------'''''''''''' see if my updated answer helps you. Since you decided to use CRM web api, Orgservice is out of focus.Devil
@l--''''''---------'''''''''''' so finally you ended up using org service impersonation?Devil
@ArunVinoth nope still attempting to figure this out, probably a bug with crm. next in line is to use web api authenticationSmarmy
D
3

When you are doing Postman to CRM request, its direct call & CRM handles it in expected way.

But in Postman -> Microservice -> CRM, the header get lost between Microservice to CRM.

In your Microservice, you have to handle the Header forward manually to CRM SDK call.

HttpWebRequest myHttpWebRequest1= (HttpWebRequest)WebRequest.Create(uri);
myHttpWebRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");

Or HTTP Header Forwarding (Sorry I could not find one for Azure / C#)

enter image description here

Update:

Am assuming you are following this MSDN samples to do your CRM web api call in c# microservice. I have included our header in need - MSCRMCallerID. See if it helps you.

public async Task BasicCreateAndUpdatesAsync()
{
   Console.WriteLine("--Section 1 started--");
   string queryOptions;  //select, expand and filter clauses
                         //First create a new contact instance,  then add additional property values and update 
                         // several properties.
                         //Local representation of CRM Contact instance
   contact1.Add("firstname", "Peter");
   contact1.Add("lastname", "Cambel");

   HttpRequestMessage createRequest1 =
       new HttpRequestMessage(HttpMethod.Post, getVersionedWebAPIPath() + "contacts");
   createRequest1.Content = new StringContent(contact1.ToString(),
       Encoding.UTF8, "application/json");

createRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");

   HttpResponseMessage createResponse1 =
       await httpClient.SendAsync(createRequest1);

   if (createResponse1.StatusCode == HttpStatusCode.NoContent)  //204
   {
    Console.WriteLine("Contact '{0} {1}' created.",
        contact1.GetValue("firstname"), contact1.GetValue("lastname"));
    contact1Uri = createResponse1.Headers.
        GetValues("OData-EntityId").FirstOrDefault();
    entityUris.Add(contact1Uri);
    Console.WriteLine("Contact URI: {0}", contact1Uri);
   }
   else
   {
    Console.WriteLine("Failed to create contact for reason: {0}",
        createResponse1.ReasonPhrase);
    throw new CrmHttpResponseException(createResponse1.Content);
   }
}
Devil answered 15/9, 2017 at 16:57 Comment(5)
This is a very good hypothesis but I inspected the headers that are being sent from the service to CRM. I put a breakpoint right before the Microsoft issues our request to CRM and I looked into the request and it shows that the desired Hatter is indeed inside of the request. I cannot be 100% sure that the header is not lost because I have not been able to do the trace in Fiddler because Fiddler is blocking localhost.Smarmy
I meant Microservice not Microsoft in the first commentSmarmy
I would advise you to send wrong user Guid & test, if not fails report it to MSDevil
it did not fail! looks like it's using what ever user is authenticated in the browser and if a user is not authenticated then it will give 401Smarmy
that means either MSCRMCallerID is not at all passing to API call or API is skipping it. See whats the error coming in response when invalid GUID is added in Postman to CRM direct call? You can report it in MS board or report the bug, if you are sure about Microservice transferring the HEADER.Devil
E
2

There are fews things that you have to take care while impersonating

1. To impersonate a user, set the CallerId property on an instance of OrganizationServiceProxy before calling the service’s Web methods.

2. The user (impersonator) must have the ActOnBehalfOf privilege or be a member of the PrivUserGroup group in Active Directory

Code Example

 SystemUser user = null;
 user = new SystemUser(systemUser);
 OrganizationServiceProxy service = CrmService.Proxy;
 service.CallerID = user.Id;

Since your code is not available please ensure all the above fields are set properly

For detailed understanding use the link https://crmbusiness.wordpress.com/2015/07/21/crm-2015-understanding-impersonation-in-plugins-and-knowing-when-to-use-it/

Eisenhart answered 4/10, 2017 at 9:31 Comment(1)
we don't want to do this through the org service if we don't have to, i've included a code snippet of how we are authenticating. please see the updated questionSmarmy

© 2022 - 2024 — McMap. All rights reserved.