11

I am using the code to impersonate a user account to get access to a file share.

public class Impersonator :
    IDisposable
{
    #region Public methods.
    // ------------------------------------------------------------------

    /// <summary>
    /// Constructor. Starts the impersonation with the given credentials.
    /// Please note that the account that instantiates the Impersonator class
    /// needs to have the 'Act as part of operating system' privilege set.
    /// </summary>
    /// <param name="userName">The name of the user to act as.</param>
    /// <param name="domainName">The domain name of the user to act as.</param>
    /// <param name="password">The password of the user to act as.</param>
    public Impersonator(
        string userName,
        string domainName,
        string password )
    {
        ImpersonateValidUser( userName, domainName, password );
    }

    // ------------------------------------------------------------------
    #endregion

    #region IDisposable member.
    // ------------------------------------------------------------------

    public void Dispose()
    {
        UndoImpersonation();
    }

    // ------------------------------------------------------------------
    #endregion

    #region P/Invoke.
    // ------------------------------------------------------------------

    [DllImport("advapi32.dll", SetLastError=true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    private static extern  bool CloseHandle(
        IntPtr handle);

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    // ------------------------------------------------------------------
    #endregion

    #region Private member.
    // ------------------------------------------------------------------

    /// <summary>
    /// Does the actual impersonation.
    /// </summary>
    /// <param name="userName">The name of the user to act as.</param>
    /// <param name="domainName">The domain name of the user to act as.</param>
    /// <param name="password">The password of the user to act as.</param>
    private void ImpersonateValidUser(
        string userName, 
        string domain, 
        string password )
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if ( RevertToSelf() )
            {
                if ( LogonUser(
                    userName, 
                    domain, 
                    password, 
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT, 
                    ref token ) != 0 )
                {
                    if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
                    {
                        tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception( Marshal.GetLastWin32Error() );
                    }
                }
                else
                {
                    throw new Win32Exception( Marshal.GetLastWin32Error() );
                }
            }
            else
            {
                throw new Win32Exception( Marshal.GetLastWin32Error() );
            }
        }
        finally
        {
            if ( token!= IntPtr.Zero )
            {
                CloseHandle( token );
            }
            if ( tokenDuplicate!=IntPtr.Zero )
            {
                CloseHandle( tokenDuplicate );
            }
        }
    }

    /// <summary>
    /// Reverts the impersonation.
    /// </summary>
    private void UndoImpersonation()
    {
        if ( impersonationContext!=null )
        {
            impersonationContext.Undo();
        }   
    }

    private WindowsImpersonationContext impersonationContext = null;

    // ------------------------------------------------------------------
    #endregion
}

Then using:

using (new Impersonator("username", "domain", "password"))
        {
            Process.Start("explorer.exe", @"/root,\\server01-Prod\abc");
        }

I get an "Access Denied" error.

This user supposely has access to this share. I can map a drive, use "net use" but this code will not work. Now I am thinking it is the code. Does anyone see anything? Is there a better way of doing this?

Adi Lester
  • 24,731
  • 12
  • 95
  • 110
Kyle Johnson
  • 763
  • 1
  • 13
  • 31
  • Where is this running from? If this is an app hosted on IIS, then the default IIS user may not have the rights to impersonate – oleksii Mar 28 '12 at 14:55
  • I remember I had a similar problem, try running this code in a simple console application using your admin user. I am not actually sure you will be able to do this from a web application running on IIS. This is something to do with permissions of the ASP.NET user (as far as I remember) – oleksii Mar 28 '12 at 17:24

3 Answers3

6

try this :

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

Usage :

IntPtr userToken = IntPtr.Zero;

bool success = External.LogonUser(
  "john.doe", 
  "domain.com", 
  "MyPassword", 
  (int) AdvApi32Utility.LogonType.LOGON32_LOGON_INTERACTIVE, //2
  (int) AdvApi32Utility.LogonProvider.LOGON32_PROVIDER_DEFAULT, //0
  out userToken);

if (!success)
{
  throw new SecurityException("Logon user failed");
}

using (WindowsIdentity.Impersonate(userToken))
{
 Process.Start("explorer.exe", @"/root,\\server01-Prod\abc");
}
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • I am using the same piece of code and seeing this behavior which I can't seem to be able to explain: sometimes LogonUser returns exit code 0 and the Win32Exceptionis: "System.ComponentModel.Win32Exception: The directory name is invalid". But then re-running the same function 10 minutes later works. Do you happen to know why? I could provide you with more context if necessary. – Tu Hoang May 20 '13 at 23:23
2

If I'm understanding correctly, your intention is to run the process in the impersonation context.

The doc from CreateProcess (which is used by Process.Start) says: If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token. To run the new process in the security context of the user represented by the impersonation token, use the CreateProcessAsUser or CreateProcessWithLogonW function.

So, you're using the wrong API for doing that.

Peter
  • 66
  • 4
1

Instead of using your Impersonator class, what happens when you call Process.Start and pass in a ProcessStartInfo instance that contains the username, password and domain that you want to run the process as?

Perhaps, if that works, then your Impersonator class should create a ProcessStartInfo instance and use that to create new processes (encapsulate that within the class itself).

var psi = new ProcessStartInfo("explorer.exe", @"/root,\\server01-Prod\abc");
psi.Domain = domain;
psi.UserName = username;
psi.Password = password;
psi.WorkingDirectory = workingDir;

Process.Start(psi);

Also, per the MSDN docs...

Setting the Domain, UserName, and the Password properties in a ProcessStartInfo object is the recommended practice for starting a process with user credentials.

You should also set the working directory when starting a process with different user creds.

David Hoerster
  • 28,421
  • 8
  • 67
  • 102
  • I do not get an error but I was expecting the explorer windows to open. psi.Domain = "domain"; psi.UserName = "usera"; psi.Password = pwd; psi.FileName = "explorer.exe"; psi.Arguments = @"/root,\\server01-pv\abc"; psi.ErrorDialog = true; psi.UseShellExecute = false; Process.Start(psi); – Kyle Johnson Mar 28 '12 at 16:19
  • What happens when you set `UseShellExecute = true`? – David Hoerster Mar 28 '12 at 16:31
  • Plus, if you set `ErrorDialog = true`, I thought you had to set `UseShellExecute = true`. And also set `WorkingDirectory` on the `ProcessStartInfo` instance. – David Hoerster Mar 28 '12 at 16:31
  • Did that and I got an error. The Process object must have the UseShellExecute property set to false in order to start a process as a user Code - psi.Domain = "domain"; psi.UserName = "usera"; psi.Password = pwd; psi.WorkingDirectory = @"C:\WINDOWS"; psi.FileName = "explorer.exe"; psi.Arguments = @"/root,\\server-01-PV\abc"; psi.ErrorDialog = true; psi.UseShellExecute = true; Process.Start(psi); – Kyle Johnson Mar 28 '12 at 17:09
  • @KyleJohnson psi.UseShellExecute = false, isn't? – Kiquenet Jul 05 '13 at 10:17