use Process.Start while Impersonating (Window Application)
Asked Answered
K

2

8

I'm trying to use Process.Start() under Impersonation, i have google for few days, most answer i come across was under ASP.net, but I'm developing for Window Application, so I'm having difficulty to find the root cause.

This is my impersonate code

     private void impersonateValidUser(string userName, string domain, string password)
        {
            WindowsIdentity tempWindowsIdentity = null;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;            
            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 );
                    mImpersonationContext = tempWindowsIdentity.Impersonate();
                }
            }
        } 

and i'm trying to open document through my program (none .exe, such as .txt, .doc)

    using (new Impersonator(DomainUserID, Domain, Password))
        Process.Start(filePath);

So far I'm able to detect the directory/file with the impersonate user, which suppose to be invisible to my current login user since i did not grant it the access. But whenever i try to open the document, i get error

    System.ComponentModel.Win32Exception (0x80004005): Access is denied

Please correct me if I'm wrong, so in this scenario, I'm not suppose to set the UseShellExecute to false (and processStartInfo with username and password input too?) because it's for executable file(?), and i do come across with CreateProcessAsUser function as well, and this function doesn't seems to be applicable to my case too, from the example i read it's for the .exe file too.

Would be appreciate if anyone could enlightenment me.

update: impersonate class

public class Impersonator : IDisposable
{
    #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);
    #endregion

    #region Constants
    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0; 
    #endregion

    #region Attributes
    private WindowsImpersonationContext mImpersonationContext = null;
    #endregion

    #region Public methods.

    public Impersonator( string userName, string domainName, string password)
    {
        impersonateValidUser(userName, domainName, password);
    }

    #endregion

    #region IDisposable member.
    public void Dispose()
    {
        undoImpersonation();
    }
    #endregion

    #region Private member.

    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 );
                        mImpersonationContext = 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 ( mImpersonationContext != null )
        {
            mImpersonationContext.Undo();
        }   
    }

    #endregion
}
Krumm answered 17/12, 2015 at 9:27 Comment(5)
Can you post all the code of your Impersonator class pleaseJuryrigged
Hi, thx for reply, i updated my class in the questionKrumm
Sorry I haven't seen your update, i will try to compare this two implementation.Juryrigged
sorry i mess up the format just now, i just updated the question. I will test the code you post and update here later, cheers :-DKrumm
It requires you to PInvoke RunProcessAsUser which is way far more difficult to get that right. Try to ping Microsoft for advisory services.Wil
A
3

You can't use UseShellExecute = true when impersonating. This is related to the way how shell execution works in Windows. Basically everything is passed to the shell which looks up how to handle the verb ("open" in your case) and then starts the application under the user owning the shell, which is not the impersonated user - the impersonated user doesn't actually have a shell if there is no session!

Although you use a different mechanism for impersonating a user the documentation for UseShellExecute still applies in your case:

UseShellExecute must be false if the UserName property is not null or an empty string, or an InvalidOperationException will be thrown when the Process.Start(ProcessStartInfo) method is called.

To solve this issue it might be the easiest to look up the registered application yourself as described in this answer: Finding the default application for opening a particular file type on Windows. With the path to the associated application you can then start the executable as the other user.

Armijo answered 17/12, 2015 at 10:40 Comment(0)
J
1

Here is the complete class I use to impersonate user:

/// <summary>
/// Provide a context to impersonate operations.
/// </summary>
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonate : IDisposable
{
    /// <summary>
    /// Initialize a new instance of the ImpersonateValidUser class with the specified user name, password, and domain.
    /// </summary>
    /// <param name="userName">The user name associated with the impersonation.</param>
    /// <param name="password">The password for the user name associated with the impersonation.</param>
    /// <param name="domain">The domain associated with the impersonation.</param>
    /// <exception cref="ArgumentException">If the logon operation failed.</exception>
    public Impersonate(string userName, SecureString password, string domain)
    {
        ValidateParameters(userName, password, domain);
        WindowsIdentity tempWindowsIdentity;
        IntPtr userAccountToken = IntPtr.Zero;
        IntPtr passwordPointer = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if (Kernel32.RevertToSelf())
            {
                // Marshal the SecureString to unmanaged memory.
                passwordPointer = Marshal.SecureStringToGlobalAllocUnicode(password);

                if (Advapi32.LogonUser(userName, domain, passwordPointer, LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT, ref userAccountToken))
                {
                    if (Advapi32.DuplicateToken(userAccountToken, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            return;
                        }
                    }
                }
            }
        }
        finally
        {
            // Zero-out and free the unmanaged string reference.
            Marshal.ZeroFreeGlobalAllocUnicode(passwordPointer);

            // Close the token handle.
            if (userAccountToken != IntPtr.Zero)
                Kernel32.CloseHandle(userAccountToken);
            if (tokenDuplicate != IntPtr.Zero)
                Kernel32.CloseHandle(tokenDuplicate);
        }

        throw new ArgumentException(string.Format("Logon operation failed for userName {0}.", userName));
    }

    /// <summary>
    /// Reverts the user context to the Windows user represented by the WindowsImpersonationContext.
    /// </summary>
    public void UndoImpersonation()
    {
        impersonationContext.Undo();
    }

    /// <summary>
    /// Releases all resources used by <see cref="Impersonate"/> :
    /// - Reverts the user context to the Windows user represented by this object : <see cref="System.Security.Principal.WindowsImpersonationContext"/>.Undo().
    /// - Dispose the <see cref="System.Security.Principal.WindowsImpersonationContext"/>.
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (impersonationContext != null)
            {
                //UndoImpersonation();
                impersonationContext.Dispose();
                impersonationContext = null;
            }
        }
    }

    private void ValidateParameters(string userName, SecureString password, string domain)
    {
        if (string.IsNullOrWhiteSpace(userName))
        {
            throw new ArgumentNullException("userName");
        }
        if (password == null || password.Length == 0)
        {
            throw new ArgumentNullException("password");
        }
        if (string.IsNullOrWhiteSpace(userName))
        {
            throw new ArgumentNullException("domain");
        }
    }

    private WindowsImpersonationContext impersonationContext;

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

how to convert a string to a secure string :

var secure = new SecureString();
foreach (char c in "myclearpassword")
{
    secure.AppendChar(c);
}

SO You can use it like that:

using (var imp = new Impersonate(DomainUserID, SecurePassword, Domain))
{
    Process.Start(filePath);
}
Juryrigged answered 17/12, 2015 at 9:47 Comment(13)
for the impersonate user, i grant it as Admin role, and grant full control on the document, anything else i need to grant for the user?Krumm
Do the user have permission on the folder ?Juryrigged
Yes, i grant the user full control, i can add and remove through the impersonation, but i having problem open it :-/Krumm
Have you already seen this post :#3936956Juryrigged
Yes, i seen this post b4, if my understanding is correct, CreateProcessAsUser only applicable to executable file, and ProcessStartInfo.UserName + Password properties force me to set UseShellExecute to false which only applicable to .exe too.Krumm
there is this other one that seems to do what you need #4624613Juryrigged
That's for .exe too :-( I found lot of answer on the internet is forcing the UseShellExecute to false.Krumm
can this possible to be my environment issue somehow? If i login with the impersonate ID, i will be able to open those document with the impersonate. Somehow the Process.Start is recognizing my login user instead of my impersonate userKrumm
Can you explain me your require,ent, may be we can find a workaroundJuryrigged
I have a Document Manager for user to keep their documents, but we need to implement a security features to prevent the login user access to the physical location directly to modify the file, so I'm trying to make the document cache only available to one dummy account, and whenever user open our program, they will be able to access to the document through the impersonation with the dummy account, while their own ID having no access to the physical document/path.Krumm
Can you try with UseShellExecute = false and LoadUserProfile = trueJuryrigged
Another approach would be to store the document in a database (SQL server), is it possible in your case ?Juryrigged
i tested UseShellExecute = False and LoadUserProfile = True, that doesn't work. We have many customer currently with a lot of documents in physical path, its not possible to stream all them into database ;-/Krumm

© 2022 - 2024 — McMap. All rights reserved.