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
- public void CreateAccount(OrganizationServiceProxy service)
- {
- var account = new Entity("account");
- account["name"] = "SDK Impersonated Account";
- var testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
- service.CallerId = testUser1;
- var response = service.Create(account);
- }
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
- Guid testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
- var account1 = GetNewAccount("account1");
- var account2 = GetNewAccount("account2");
- var account3 = GetNewAccount("account3");
- var account4 = GetNewAccount("account4");
- var service = (OrganizationServiceClient)SilverlightUtility.GetSoapService();
- //Create first account
- service.CreateAsync(account1);
- using (var scope = new OperationContextScope(service.InnerChannel))
- {
- //Inject caller Id to SOAP header
- var ns = "https://schemas.microsoft.com/xrm/2011/Contracts";
- var userHeader = MessageHeader.CreateHeader("CallerId", ns, testUser1);
- OperationContext.Current.OutgoingMessageHeaders.Add(userHeader);
- //Create second account
- service.CreateAsync(account2);
- //Create third account within the same OperationContextScope to indicate
- //all operations within this using block are performed with impersonation
- service.CreateAsync(account3);
- }
- //Create fourth account
- 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.
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.
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
- Guid testUser1 = new Guid("C29551F3-123D-E111-8463-00155D001003");
- var account1 = GetNewAccount("account1");
- var account2 = GetNewAccount("account2");
- var account3 = GetNewAccount("account3");
- var account4 = GetNewAccount("account4");
- var service = (OrganizationServiceClient)SilverlightUtility.GetSoapService();
- //Create first account
- service.CreateAsync(account1);
- //Create second account
- service.CreateAsync(account2, testUser1);
- //Create third account
- service.CreateAsync(account3, testUser1);
- //Create fourth account
- 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 Burman Consultant Microsoft Consulting Services UK |
Comments
Anonymous
April 04, 2012
Awesome!!! I was never able to figure this out so had to take a different approach to my Silverlight App. This definitely opens up more possibilities in the future. Thanks again for posting this.Anonymous
June 20, 2012
This post is very helpful! The approach is exactly what I'm looking for and the extension class from the team is godsend. Thank you very much, I really appreciate your work.Anonymous
June 21, 2012
Hi Dave, Great Article! In my silverlight app, I am trying to change the Business unit of the current user by impersonating him as a user with system administrator role....But I am not able to do so...Can you please tell me how can I achieve it?Anonymous
June 25, 2012
Hi Swaroop That's an interesting requirement. Unfortunately I think you may be out of luck; although the impersonation technique outlined above allows users to act on behalf of other users, CRM remains aware of the current user and is able enforce any behaviour as necessary. CRM does not allow any user to change their own business unit (even admins), and I would expect this functional restriction to be enforced even under impersonation. If you do find a way of achieving this I'd be really interested to hear about it.Anonymous
December 13, 2012
We used this approach in one of our app and checked it on an OnPremise system. But this code does not seem to work in CRM Online. Are there any changes required to the code?Anonymous
December 13, 2012
Hi Sam You're correct, currently there is an issue with this in CRM Online. We raised a support case for this; my understanding is that a fix is in development and will be released in the near future.