Impersonation in SharePoint In Details

Impersonation is the process of executing code in the context (or on behalf) of another user's credentials. You may want to use existing SPUser credentials or you may want to assume the Application Pool Credentials. Also, sometimes you might want to use impersonation to make use of existing Windows user accounts and their permissions.

Before you decide for a specific approach for impersonation, it's important that you understand the difference between the SharePoint Security Context and the Windows Security Context. In a SharePoint Visual webparts or application pages, apart from accessing the protected resources inside SharePoint (Lists, Libraries, files and so on), you may need to access the external resources (Shared Folder, SQL Server and so on) as well. Since the access to these external resources is not controlled by SharePoint, a correct impersonation approach should be adopted to make successful calls to external systems.

Also, You should keep in mind not to use Impersonation or Elevation of privilege to bypass the security, always use it to work with the security model your site needs.

SharePoint Sites are configured to run under the account of the requesting user. If you look at the web.config file for any SharePoint web application, you will find the following entry.

  1. <identity impersonate="true" />

With this setting, every request is instructed to run under the Windows security context of the current user. It means that when your custom webpart or page tries to access an external resource (such as a file system , database , or Web service) it runs under the Windows account of the requesting user.

[Note: In a plain ASP.NET website, by default, impersonation is disabled. So, all the code is executed using w3wp process account, which is the Application Pool Account configured in IIS.]

In case of Classic Mode Authentication (Windows), the user identity is the same in both the Windows and SharePoint security context. However,If you are using Claims Based Authentication, Windows security context may take on the identity of the IUSR_MACHINENAME account, or the account is specified in the IIS. This can deny access when accessing external resources because the IUSR_MACHINENAME account typically will not have rights to those resources. In such cases, you may need to use the Secure Store Service (SSS).

Now, Let's explore the ways to impersonate in SharePoint and see when to apply each technique effectively.

1. Using SPSecurity.RunWithElevatedPrivileges

In SharePoint, it's a very popular practice to run code with RunWithElevatedPrivileges. This is commonly used to do an action on behalf of a user with insufficient rights.

The following is an example.

  1. SPSecurity.RunWithElevatedPrivileges(delegate()
  2. {
  3. using (SPSite Site = new SPSite(SPContext.Current.Site))
  4. {
  5. using (SPWeb Webb = Site.OpenWeb(SPContext.Current.Web.Url))
  6. {
  7. // Perform administrative actions by using the elevated site and web objects.
  8. // Web.CurrentUser.LoginName gives SHAREPOINTsystem
  9. // WindowsIdentity.GetCurrent().Name gives Application pool Windows account(ContsoAdmin1)
  10. // Hence, Both SharePoint Security context and Windows Security context are changed.
  11. }
  12. }
  13. });

Considering the Windows Security Context, the code inside SPSecurity.RunWithElevatedPrivileges block runs under the Application Pool Account of your web application. This is the account under which the worker process (w3wp) runs. Besides running the web application, this Application Pool Account is used as the Windows account that connects to the SharePoint Content and Configuration databases. So the code is authenticated by Application pool acount when it attempts to access external resources, such as the local file system or a SQL Server database.

Considering the SharePoint Security Context, the code inside the SPSecurity.RunWithElevatedPrivileges block runs under the SHAREPOINTSYSTEM account . SHAREPOINTSYSTEM is an identity to which SharePoint maps internally. Any updates you do via SharePoint object model (SPListItem,SPFile) reflects the modified or created by SHAREPOINTSYSTEM.

SHAREPOINTSYSTEM is not recognized by the Windows security subsystem. As said earlier, this account is mapped to the Application Pool Account.

It is important to understand a few important aspects when using RunWithElevatedPrivileges:

  1. Elevation of privilege occurs only if new SPSite created inside the block

    You need to create new SPSite and SPWeb objects inside the SPSecurity.RunWithElevatedPrivileges block using either URL or GUID. If you don't do so or try to use SPSiteSPWeb from SPContext.Current, you will get an Access Denied error on using SPSecurity.RunWithElevatedPrivileges even. Also, never forget to dispose of your objects.

    For example, the following code does not elevate the privilege and is the wrong way to use SPSecurity.RunWithElevatedPrivileges:
    1. SPSecurity.RunWithElevatedPrivileges(delegate()
    2. {
    3. SPSite Site = SPContext.Current.Site;
    4. using (SPWeb Web = elevatedSite.OpenWeb())
    5. {
    6. // Performing administrative actions here will give Access Denied exception.
    7. }
    8. }
    9. });
  2. Improper usage may cause Security Issues

    SharePoint objects (SPList, SPFile and so on) that are created or accessed using the elevated SPSite (created inside RunWithElevatedPrivileges block) and SPWeb retain the permissions they were created with. Hence, you should not return these objects outside the RunWithElevatedPrivileges block otherwise it can lead to security issues.
    1. SPList taskList=null;
    2. SPSecurity.RunWithElevatedPrivileges(delegate()
    3. {
    4. SPSite Site = SPContext.Current.Site;
    5. using (SPWeb Web =
    6. Site.OpenWeb())
    7. {
    8. Splist Testlist = Web.Lists.TryGetList["Customer"]
    9. }
    10. }
    11. });
    12. //This code will succeed even outside the block as it is accessed via elevated SPWeb. Hence Security Risk.
    13. Testlist.Delete();
    Here is the explanation for this behavior:

    When you create an SPSite object, it is persisted by an underlying SPRequest object. The SPRequest remembers which user created the SPSite object. The SPRequest is shared by all objects that are accessed via that SPSite object.

    When an SPSite object is created in a RunWithElevatedPrivileges block, the SPRequest object records that the current user is the System Account. For example, If a SPList object is accessed via elevated SPSite object (like SPSite.RootWeb.Lists["Tasks"] ) , it shares the same SPRequest object and is actually an “elevated” object. If this SPList object is passed outside of the RunWithElevatedPrivileges block, it retains its underlying SPRequest object and continues to be elevated. So now if your code further uses this SPList object, you may have a security leak.
  3. RunWithElevatedPrivileges changes Windows Security Context as well

    For example, if you change an Application Pool Account of a web application from ContsoAdmin1 to ContsoAdmin2 , the code running using the SPSecurity.RunWithElevatedPrivileges block still acts and is audited as the SHAREPOINTSYSTEM account inside SharePoint (SharePoint Security Context).

    Reflects the changed windows user when a call is made outside SharePoint (Windows Security Context).

    So inside the RunWithElevatedPrivileges block, any call to external systems like DB or WebServices will be made by the Windows account of the application pool. It succeeds or does not depend on the permissions that the Windows account has on that external system.

    [Note: If the external system is on another server, calls will not succeed even if the Application Pool Account has permissions. This is due to a double-hop issue in NTLM authentication. You need to configure KERBEROS authentication for that or use Secure Store Service.]
  4. RunWithElevatedPrivileges does not work when HTTPContext is null

    RunWithElevatedPrivileges won't work when HTTPContext (SPContext to be more specific) is null. So, you will not have an elevation of privilege when using RunWithElevatedPrivilege in a Console Application, WorkFlow , Timer Job or Event handlers not initiated by a request in a browser.
  5. RunWithElevatedPrivileges does not work for a Sandbox Solution

    RunWithElevatedPrivileges works only if you deploy the component (webpartpage) as a Farm Solution. It does not work for Sandbox Solutions. (For more Sandbox Solution limits, please check here.)
  6. Some Write Operations may fail even

    If the method passed to RunWithElevatedPrivileges includes any write operations, the call to RunWithElevatedPrivileges should be preceded by a call to either SPUtility.ValidateFormDigest() or SPWeb.ValidateFormDigest(). Otherwise, the operation may not succeed.
  7. To use SPSecurity.RunWithElevatedPrivileges and still retain the CurrentUser's identit

    If you run code within a SPSecurity.RunWithElevatedPrivileges block and create new objects, such as list items within a list, the user is automatically assigned as the author or editor as SHAREPOINT\system. However, you may need the user to be the owner of an item with his or her current credentials.

    To do this, you must first retrieve the real credentials and then elevate the privileges.

    The following example shows how to deal with this issue of two identities.

    1. private void UpdateItem(SPList RestrictedList, bool IsAnonymous)
    2. {
    3. SPUser user =SPContext.CurrentWeb.CurrentUser;
    4. Guid siteID = RestrictedList.ParentWeb.Site.ID;
    5. Guid webID = RestrictedList.ParentWeb.ID;
    6. Guid listID = RestrictedList.ID;
    7. SPSecurity.RunWithElevatedPrivileges(() =>
    8. {
    9. using (SPSite site = new SPSite(siteID))
    10. {
    11. using (SPWeb web = site.OpenWeb(webID))
    12. {
    13. web.AllowUnsafeUpdates = true;
    14. SPList elevatedList = web.Lists[listID];
    15. SPListItem item = elevatedList.Items.Add();
    16. SPUser systemUser = web.AllUsers[@"SHAREPOINT\system"];
    17. SPFieldUserValue currentUser = new SPFieldUserValue( item.ParentList.ParentWeb, user.ID, user.Name);
    18. if (!IsAnonymous)
    19. {
    20. item["Author"] = currentUser;
    21. item["Editor"] = currentUser;
    22. } else {
    23. item["Author"] = systemUser;
    24. item["Editor"] = systemUser;
    25. }
    26. item.Update();
    27. );
    28. }

    The code can access the RestrictedList that the current user can't access normally. This prevents the user from accessing the list by entering the URL directly.

    Within the delegate, a new list item is created. The IsAnonymous parameter determines whether the Author and Editor list fields take the system account or the current user.

    Using the preceding way:

    • You can decide to set the user's data or leave the item in an anonymous state.
    • You can manipulate the Author and Editor field values as required.

2. Using SPUserToken(Preffered Way)

Another way to elevate the privilege or to impersonate is by using an SPUserToken object. The first step is to get the token for the user to be impersonated in SharePoint using SPUser.UserToken. Then use this token to the SPSite constructor to create a new impersonated security context.

This is the most recommend way and the best practice to perform impersonation in the context of SharePoint. However, when using SPUserToken, you need to ensure that the user exists of whom you are impersonating and that user has the proper permissions. In production scenarios, you may not know the user in advance and this technique may not work.

The following is the example to impersonate a SHAREPOINTSYSTEM account.

  1. SPWeb oWeb = SPContext.Current.Web;
  2. SPUserToken token = oWeb.AllUsers[@"SHAREPOINTSYSTEM"].UserToken;
  3. using (SPSite Site = new SPSite(oWeb.Site.ID, token))
  4. {
  5. using (SPWeb dweb = site.OpenWeb())
  6. {
  7. // Perform administrative actions by using the elevated site and web objects.
  8. // Web.CurrentUser.LoginName gives SHAREPOINTsystem
  9. // WindowsIdentity.GetCurrent().Name gives current logged-in username i.e. SPContext.Current.Web.CurrentUser.LoginName.
  10. // Hence,Only SharePoint Security context is changed while Windows Security context is not changed.
  11. }
  12. }

When a user request is authenticated, it runs under the context of a SPUser object and carries a security token, SPUserToken. When you create a SPSite, an instance of the SPUserToken and the SPUser are also created. When your code attempts to access resources inside SharePoint, this user's security token is checked against ACLs to determine whether it should grant or deny access.

It is important to understand a few important aspects when using the SPUserToken approach.

  1. Windows Security Context is not changed

    If you see the code above, WindowsIdentity.GetCurrent().Name is the same as the Name of the current user making the request which is SPContext.Current.Web.CurrentUser.

    So any call to external systems like DB or WebServices will be made by the Windows account of the current user. It succeeds or not depending on the permissions that the user has on that external system.

    [Note: If the external system is on another server, calls will not succeed even if the current user has permissions. This is due to a double-hop issue in NTLM authentication. You need to configure KERBEROS authentication for that or use Secure Store Service.
  2. Tokens have expiry time

    The tokens time out after 24 hours, so they can be used in the code that needs to impersonate users in the case of workflow actions or asynchronous event receivers occurring within 24 hours. fter the SPUserToken object is returned to the caller, it is the caller's responsibility to not use the token after it is expired.

    [Note: The token timeout value can be set using the Windows PowerShell console or stsadm as in the following:

    tsadm -o setproperty -propertyname token-timeout -propertyvalue 720

    ]
  3. Using Win32 API

    We see that none of the preceding ways, SPUserToken and RunWithelevatedPrivileges, work when we need to impersonate systems outside SharePoint like Console or Windows Applications.

    If you have Console or Windows Application and want to impersonate the call to a arePoint web service or Object Model, you must programmatically create a WindowsIdentity object for the caller. Create a WindowsIdentity object either by using a logon token returned from the Win32 LogonUser API or by using the WindowsIdentity(userPrincipalName) constructor that takes a single parameter of a user principal name (UPN).

    Pease note that you should avoid using this technique in SharePoint components like webpartsor application pages.
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using Microsoft.SharePoint;
    6. using System.Threading;
    7. using System.Web;
    8. using System.Security.Principal;
    9. using System.Runtime.InteropServices;
    10. namespace AmitKumawat.ConsoleApp
    11. {
    12. class Program
    13. {
    14. // Declare signatures for Win32 LogonUser and CloseHandle APIs
    15. [DllImport("advapi32.dll", SetLastError = true)]
    16. static extern bool LogonUser(
    17. string principal,
    18. string authority,
    19. string password,
    20. LogonSessionType logonType,
    21. LogonProvider logonProvider,
    22. out IntPtr token);
    23. [DllImport("kernel32.dll", SetLastError = true)]
    24. static extern bool CloseHandle(IntPtr handle);
    25. enum LogonSessionType : uint
    26. {
    27. Interactive = 2,
    28. Network,
    29. Batch,
    30. Service,
    31. NetworkCleartext = 8,
    32. NewCredentials
    33. }
    34. enum LogonProvider : uint
    35. {
    36. Default = 0, // default for platform (use this!)
    37. WinNT35, // sends smoke signals to authority
    38. WinNT40, // uses NTLM
    39. WinNT50 // negotiates Kerb or NTLM
    40. }
    41. static void Main(string[] args)
    42. {
    43. string username;
    44. string password;
    45. string domain;
    46. Console.Write("Enter username:");
    47. username = Console.ReadLine();
    48. Console.Write("Enter domain:");
    49. domain = Console.ReadLine();
    50. Console.Write("Enter password: ");
    51. password = Console.ReadLine();
    52. IntPtr token = IntPtr.Zero;
    53. WindowsImpersonationContext impersonatedUser = null;
    54. try
    55. {
    56. bool result = LogonUser(username, domain, password, LogonSessionType.Network, LogonProvider.Default, out token);
    57. if (result)
    58. {
    59. WindowsIdentity id = new WindowsIdentity(token);
    60. // Begin impersonation
    61. impersonatedUser = id.Impersonate();
    62. Console.WriteLine("Identity after impersonation : " + WindowsIdentity.GetCurrent().Name);
    63. // Call to Sharepoint Web services or object model
    64. }
    65. else
    66. {
    67. Console.WriteLine("LogonUser failed: " + Marshal.GetLastWin32Error().ToString());
    68. }
    69. }
    70. catch
    71. {
    72. }
    73. finally
    74. {
    75. // Stop impersonation and revert to the process identity
    76. if (impersonatedUser != null)
    77. impersonatedUser.Undo();
    78. // Free the token
    79. if (token != IntPtr.Zero)
    80. CloseHandle(token);
    81. }
    82. // Verify the old process identity
    83. Console.WriteLine(String.Format("Identity after Undo: " + WindowsIdentity.GetCurrent().Name));
    84. Console.Read();
    85. }

    86. }
    There are other ways of impersonation which is not so common but I see people use it sometimes (as used here).

These ways are not recommended for the components inside SharePoint like Web parts or Pages. The following is the reason why.

We now know that the SharePoint request must impersonate the calling user's identity (<identity impersonate=”true” /> in web.config). SharePoint web applications are configured to impersonate the calling user automatically. If you try to suspend this impersonation by using above ways, your code may fail or behave abnormally.

Summary

Let's summarize the important points from above discussion.