Thursday, October 9, 2008

Impersonation in C#

Impersonation (in programming context) refers to a technique that executes code under another user context than the user who originally started an application. In this case, the user context is temporarily changed once or multiple times during the lifetime of an application.

The reason for doing this is to perform tasks that the current user context of an application is not allowed to do. For example, if the application is running in a restricted  account and if it needs administrative privileges to perform certain actions, impersonations is the right technique.  Of course it is possible to grant the user more privileges, but usually this is a bad idea (due to security constraints) or impossible (e.g. no administrative access to a machine to set privileges for the user).

The System.Security.Principal namespace has classes that can be used for impersonation. The Impersonate() method of WindowsIdentity class returns a WindowsImpersonationContext and this can be used for executing code as a different user. The LogonUser() API function is used for initializing the access token and this token is used for the creation of WindowsIdentity object. After executing code with different privileges, we can return to the current login using Undo() method of the WindowsImpersonationContext instance.

Code snippet for impersonation is given below. First, add the following references to the class:

using System.Security.Principal;
using System.ComponentModel;

Next, declare the Win32 functions and constants (in the same class or preferably, in a separate class - say Win32API)

using System.Runtime.InteropServices;

public class Win32API
{
    // Constant declarations
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;

    // The LogonUser() API
    [DllImport("advapi32.dll", SetLastError=true)]
    public static extern bool LogonUser(string lpszUsername,
                                         string lpszDomain,
                                         string lpszPassword,
                                         int dwLogonType,
                                         int dwLogonProvider,
                                         out IntPtr phToken);
}

Now the function for impersonation:

private static void RunAsDifferentUser(string userName, string password, string domain)
{
    IntPtr accessToken = IntPtr.Zero;

    try
    {
        if (LogonUser(userName,
                        domain,
                        password,
                        Win32API.LOGON32_LOGON_INTERACTIVE,
                        Win32API.LOGON32_PROVIDER_DEFAULT,
                        out accessToken))
        {
            // Use the token to create an instance of WindwosIdentity class
            using (WindowsIdentity identity = new WindowsIdentity(accessToken))
            {
                // Impersonate now
                using (WindowsImpersonationContext context = identity.Impersonate())
                {
                    //
                    // Add code - Perform the actual operation here
                    //

                    // Return to the original Windows login
                    context.Undo();
                }
            }
        }
        else
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }
    catch
    {
        throw;
    }
    finally
    {
        if (accessToken != IntPtr.Zero)
        {
            CloseHandle(accessToken);
        }
    }
}

If the computer is in a workgroup (instead of domain), pass a "." to the domain parameter of the above function.

No comments: