User Impersonation with Silverlight

Sometimes it’s useful for Dynamics CRM to think you’re someone else. An administrator may wish to retrieve or alter data as if they were another user, or execute certain special commands on someone else’s behalf. Developers using the Microsoft.Xrm.Sdk will find that implementing this functionality is quite straight forward, as it’s built into the SDK assemblies. However, when the SDK assemblies are unavailable (for example, in a Silverlight application), implementing this impersonation is more difficult.

Using the SDK

Developers using the Microsoft.Xrm.Sdk assembly have access to the OrganizationServiceProxy and CallerImpersonationScope classes. The former is a proxy class that acts as a local reference to the CRM Organization web service, and provides more functionality than directly referencing the service. One such feature is the ability to specify a CallerId when performing web service requests. When a value is given, CRM reacts as if the specified user were calling them, rather than the authenticated user. CallerImpersonationScope works in a similar way, although you pass an existing service reference into it rather than creating a new connection, which is more useful in plug-ins, for example.

Code Snippet

  1. public void CreateAccount(OrganizationServiceProxy service)
  2. {      
  3.     var account = new Entity("account");
  4.     account["name"] = "SDK Impersonated Account";
  5.             
  6.     var testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
  7.     service.CallerId = testUser1;
  8.             
  9.     var response = service.Create(account);
  10. }

Silverlight applications

Silverlight applications cannot reference the SDK assemblies. However, we can still achieve the same functionality using the organization service WCF endpoint. We can actually inject the CallerId parameter as a SOAP header before making a web service call, and CRM has some great built in functionality to accommodate this approach.

Another approach is to authenticate against the web service with another users credentials. There are a number of security risks associated with this approach, and it is generally a bad idea.

The following code demonstrates an example of this CallerId injection. Please note this example depends on classes introduced in this walkthrough.

Code Snippet

  1. Guid testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
  2.  
  3. var account1 = GetNewAccount("account1");
  4. var account2 = GetNewAccount("account2");
  5. var account3 = GetNewAccount("account3");
  6. var account4 = GetNewAccount("account4");
  7.  
  8. var service = (OrganizationServiceClient)SilverlightUtility.GetSoapService();
  9.           
  10. //Create first account
  11. service.CreateAsync(account1);
  12.  
  13. using (var scope = new OperationContextScope(service.InnerChannel))
  14. {              
  15.     //Inject caller Id to SOAP header
  16.     var ns = "https://schemas.microsoft.com/xrm/2011/Contracts";
  17.     var userHeader = MessageHeader.CreateHeader("CallerId", ns, testUser1);
  18.     OperationContext.Current.OutgoingMessageHeaders.Add(userHeader);
  19.  
  20.     //Create second account
  21.     service.CreateAsync(account2);
  22.               
  23.     //Create third account within the same OperationContextScope to indicate
  24.     //all operations within this using block are performed with impersonation
  25.     service.CreateAsync(account3);
  26. }
  27.  
  28. //Create fourth account
  29. service.CreateAsync(account4);

 

The use of OperationContextScope allows us to modify the header of each request sent to the CRM web service within the scope of the using block. To impersonate, we need to include the CallerId parameter. CRM interprets this parameter and executes messages on the specified user’s behalf.

The result

So what does the impersonation look like in CRM? In the example, I’m creating four entities. I’ve omitted some of the initialization logic to keep the sample brief, but assume the entities are all identical Entity objects representing accounts, named account1, account2, account3, and account4.  Accounts 2 and 3 are created within the scope of the OperationContextScope, and 1 and 4 are not. Also note I injected the systemuserid of Test User 1 as a SOAP header within the using block, and I’ve not had to specify any additional usernames or passwords anywhere.

When I run the code, I authenticate against the service using my own account (Dave Burman in CRM). The four accounts are created as pictured below.

clip_image002

Notice that the owner and created by fields are different for accounts 2 and 3. CRM has created these records almost as if I were authenticated as Test User 1. Almost, but not quite.  Notice the Created By (Delegate) field is populated for those two records. CRM is aware I’m impersonating a user, and notes down my user name in this field.

Security considerations

So, if we can impersonate other users, what’s to stop a standard user impersonating a system administrator and retrieving data they wouldn’t normally be able to see? Fortunately we have a way of preventing this, via the security role functionality built into CRM. The “Act on Behalf of Another User” privilege allows administrators to control who can impersonate other users. This is the only privilege set in the built in “Delegate” role, so assigning this role to a user or team will allow them to impersonate. The web service will return an error back to an authenticated client attempting impersonation without this privilege.

image

However, the “Act on Behalf of Another User” privilege should be used with caution.  Care must be taken to ensure impersonation does not inadvertantly give users the ability to access or modify data that they would not normally be able to.  Whilst CRM does not allow users to gain new privileges via impersonation (e.g. a user who does not have Account read access will not be able to gain it by impersonating a user that does), privilege scope can be elevated using this method.  For example, a user with user scope read access to Accounts could gain organization wide access by impersonating a user with this privilege (e.g. the system administrator).

Tidying it all up

Finally, the Silverlight example above works well, but the code becomes cumbersome where there’s lots of impersonation happening.  The CRM Guys have developed a class of extension methods to provide each of the web service proxy methods with an extra CallerId parameter, which can be downloaded here. To use the class, simply drop the file into your Silverlight application, and alter it’s namespace to match the namespace of your web service reference.

As an example, the Silverlight example above can then be rewritten as follows.

Code Snippet

  1. Guid testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
  2.             
  3. var account1 = GetNewAccount("account1");
  4. var account2 = GetNewAccount("account2");
  5. var account3 = GetNewAccount("account3");
  6. var account4 = GetNewAccount("account4");
  7.  
  8. var service = (OrganizationServiceClient)SilverlightUtility.GetSoapService();
  9.             
  10. //Create first account
  11. service.CreateAsync(account1);
  12.  
  13. //Create second account
  14. service.CreateAsync(account2, testUser1);
  15.                 
  16. //Create third account
  17. service.CreateAsync(account3, testUser1);
  18.  
  19. //Create fourth account
  20. service.CreateAsync(account4);

 

Conclusion

This article has provided an overview of the impersonation functionality available to CRM developers interacting with the application’s web services. We’ve looked at a number of different implementation options, including scenarios where the CRM SDK assemblies are unavailable. We’ve also looked at some of the built in features that CRM has to accommodate this functionality. Hopefully you’ve found it useful.

      

Dave

Dave Burman Consultant Microsoft Consulting Services UK

View my bio