6
votes

Within the Plugin Context, there are two user ids,

  1. InitiatingUserId which returns the id of the user who actually fired the plugin.
  2. UserId which returns the user Id of the user the plugin is actually running under. (this is the user specified when registering the plugin, or the Calling User, if it's registered to run as the calling user)

But I'm interested in a third user id, the impersonated user id of the OrganizationServiceProxy that performed the call.

Lets say I have a ASP.Net website that is running with a CRM System Admin AD account, and any calls that are made to CRM, use impersonation, looking up the CRM userid of the person currently logged into the site.

This works great for selects and updates, the user is only able to see\update what they have rights to. But... if I create a plugin that is is triggered on the update of a particular entity, how do I lookup the user id of the impersonated user on the Asp website from within the plugin?

3
I read your question 4 times, if I understood you have this scenario: user DOMAIN\SMITH is logged inside a site running under DOMAIN\IAMSYSADMIN (application pool), and inside a triggered plugin running with calling user DOMAIN\PLUGINUSER, you want to know that was DOMAIN\SMITH and not DOMAIN\SPECTER the initial user, right?Guido Preite
This information should be available in InitiatingUserId. Do you get any other value? There is always WhoAmI() request that might help in your case.MarioZG
@MarioZG The InitiatingUserId in my example will be the id of the application pool user, not the impersonated user. The WhoAmI request will return the Id of the User running the plugin.Daryl
@GuidoPreite If by DOMAIN\SPECTOR you mean DOMAIN\IAMSYSADMIN, then yes. I believe you understand it correctly.Daryl
what I meant is that you want to know the user logged in the site (DOMAIN\SMITH or DOMAIN\SPECTER (another user of the website), because you already know that the application pool is DOMAIN\IAMSYSADMIN and is what you got from InitiatingUserId, you want to go deeper one level more.Guido Preite

3 Answers

3
votes

As far as my understanding of the situation goes, you are able to retrieve the User Id of the impersonated user, but not the authenticated user. Below are some scenarios mapped out that should hopefully clarify the behavior (tested in UR11 and UR12):

  1. Authenticated to CRM as User A, impersonating User B. Plugin is not registered to impersonate any specific user (i.e. ImpersonatingUserId of plugin step is set to null (from the plugin registration tool, "Run in User's Context" would be set to "Calling User)). Example would be with OrganizationServiceProxy authenticated as User A, where proxyService.CallerId is User B.
    • PluginContext.UserId = User B
    • PluginContext.InitiatingUserId = User B
    • createdonbehalfby (or modifiedonbehalfby) = User A
  2. Authenticated to CRM as User A, impersonating User B. Plugin is registered to impersonate User C. (i.e. ImpersonatingUserId of plugin step is set to User C (from the plugin registration tool, "Run in User's Context" would be set to User C)). Example would be with OrganizationServiceProxy authenticated as User A, where proxyService.CallerId is User B.
    • PluginContext.UserId = User C
    • PluginContext.InitiatingUserId = User B
    • createdonbehalfby (or modifiedonbehalfby) = User A

Scenario #1 is a bit confusing to me as I vaguely remember that working differently. This must mean that when the CallerId property is set, the impersonation happens in a different location than with Scenario #2. However, you can see that in Scenario #2, we seem to achieve your expected result.

The steps I used to produce this result were:

  1. Create Console Application and reference Microsoft.Xrm.Sdk and Microsoft.Crm.Sdk.Proxy. Instantiate a new OrganizationServiceProxy object where the authenticated user is User A. Set the CallerId to be the Guid representing User B.
  2. Create a super simple plugin that just throws an exception the first chance it gets like this:

    throw new Exception(string.Format("Initiating Id: {0}, User Id: {1}", 
        context.InitiatingUserId, 
        context.UserId));
    
0
votes

Just wanted to clarify that GotDibb's data is correct, even though my question contradicts it.

I created a UserIdTest Plugin as follows

public void Execute(IServiceProvider serviceProvider)
{
    var context = serviceProvider.GetContext();
    var service = serviceProvider.GetService(context);
    var iUser = service.Retrieve("systemuser", context.InitiatingUserId, new ColumnSet("fullname"));
    var uUser = service.Retrieve("systemuser", context.UserId, new ColumnSet("fullname"));
    throw new Exception(string.Format("Initiating: {0}, User: {1}", 
        iUser.GetAttributeValue<string>("fullname"),
        uUser.GetAttributeValue<string>("fullname")));
}

I Performed the same testing as GotDibbs, and got the same answers, which confused me because I wouldn't have asked the question if I wasn't somehow getting a different answer. But then I realized that the issue I was seeing was being caused by a recursive plugin.

The first call to the plugin worked as expected, but when the plugin triggered another call to the plugin, it used the plugin's Context user's credentials (which makes sense) and lost the impersonated user id's credentials. Here is a table to hopefully help clarify what happens

First for the initial plugin call:

+--------------------+------------ +----------------------+----------------------------------------+
|    Org Service     | Org Service |    Plugin Step Run   |               Results                  |
| Client Credentials |  CallerId   |   in User's Context  |                                        |
+--------------------+------------ +----------------------+----------------------------------------+
|                    |             |                      | InitiaitingUser : ServiceAccount       |
| ServiceAccount     | None        | PluginServiceAccount |                                        |
|                    |             |                      | UserId : PluginServiceAccount          |
+--------------------+------------ +----------------------+----------------------------------------+
|                    |             |                      | InitiaitingUser : UserBob              |
| ServiceAccount     | UserBob     | PluginServiceAccount |                                        |
|                    |             |                      | UserId : PluginServiceAccount          |
+--------------------+------------ +----------------------+----------------------------------------+

And second for the Plugin Depth > 1

+--------------------+------------ +----------------------+----------------------------------------+
|    Org Service     | Org Service |    Plugin Step Run   |               Results                  |
| Client Credentials |  CallerId   |   in User's Context  |                                        |
+--------------------+-------------+----------------------+----------------------------------------+
|                    |             |                      | InitiaitingUser : PluginServiceAccount |
| ServiceAccount     | None        | PluginServiceAccount |                                        |
|                    |             |                      | UserId : PluginServiceAccount          |
+--------------------+-------------+----------------------+----------------------------------------+
|                    |             |                      | InitiaitingUser : PluginServiceAccount |
| ServiceAccount     | UserBob     | PluginServiceAccount |                                        |
|                    |             |                      | UserId : PluginServiceAccount          |
+--------------------+-------------+----------------------+----------------------------------------+
-1
votes

This same issue is also relevant in CRM 2013 and probably has the same solution:

Taking the scenario where a Plugin is registered to execute under the context of the calling user, the plugin execution context's InitiatingUserId and UserId values would both be the same.

This is true even if the client established the connection by overriding the CallerId - in this case both values would represent the CallerId value specified instead of the identity of the user that was authenticated.

So how to get the actual caller's identity inside the plugin? Post phase is easy, just check createdonbehalfby or modifiedonbehalfby in a post execution image. Pre-Operation is not as obvious as these values don't exist anywhere but the trick is simple.

Inside the plugin, establish a new IOrganizationService where the UserId is NULL, not context.UserId or context.InitiatingUserId but instead just null. Then using that service, execute a WhoAmI request and the response will contain the GUID of the authenticated user that executed the service call instead of the identity of the CallerId that was specified.

It turns out that the IOrganizationServiceFactory actually has a private field that contains this value (AdministratorId) but since it is private it isn't available to read so instead this workaround will work at the cost of an extra overhead.