DigitalJedi805
DigitalJedi805

Reputation: 1460

Why might this user impersonation code fail to grant me permission?

I have an application that is meant to mirror files from one location to another. In one segment of this program ( likely the most important ) the application opens impersonation contexts for both the source and destination; assuming that credentials have been provided. Once these contexts have been opened, the program executes the actual mirroring of files from one location to another, and then closes the aforementioned contexts.

It looks something like this:

protected virtual void MirrorChanges()
{
    if (this.Source == null)
        throw new InvalidOperationException();
    else if (!this.Source.Exists)
        throw new InvalidOperationException();
    else
    {
        if( this.SourceImpersonator != null )
            if(!this.SourceImpersonator.Open())
            {
                throw new Exception("FolderMirror cannot impersonate Source user. Please review the associated credentials.");
            }

        if( this.DestinationImpersonator != null )
            if(!this.DestinationImpersonator.Open())
            {
                throw new Exception("FolderMirror cannot impersonate Destination user. Please review the associated credentials.");
            }

        this.MirrorChanges(this.Source);

        if( this.DestinationImpersonator != null )
            this.DestinationImpersonator.Close();

        if( this.SourceImpersonator != null )
            this.SourceImpersonator.Close();

        return;
    }
}

Now; the question you're all begging - what's in the 'Open' and 'Close' methods of these supposed 'impersonators'? Much of this was taken from some other examples found about the internet, but here we go:

public class UserImpersonator
{
    public Boolean Open()
    {

        // Actively researching why the 'DuplicateToken' method is necessary in this method - like 
        // I said earlier, much of this is sourced from examples. I did some research and do note that 
        // there is a 'SECURITY_IMPERSONATION_LEVEL' variable in this method; but that strikes me as 
        // rather perplexing if this method's action is to simply 'duplicate a token' - but I'm off to 
        //the manual for reviewing that information.
        if (!LogonUser(this.Username, this.Domain, this.Password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50, ref this.PrimaryToken))
        {
            RaiseLastError();
            return false;
        }
        else if (!DuplicateToken(this.PrimaryToken, 2, ref this.MutatedToken))
        {
            RaiseLastError();
            return false;
        }
        else
        {
            try
            {
                this._TargetIdentity = new WindowsIdentity(this.MutatedToken);
                this._ImpersonationContext = this._TargetIdentity.Impersonate();

                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }
    }

    public void Close()
    {
        if( this._ImpersonationContext != null )
            this._ImpersonationContext.Undo();

        if( this.PrimaryToken != null )
            if (!CloseHandle(this.PrimaryToken))
                RaiseLastError();
    }

    // With some of this action, actually toward the top of the file...
    DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource, int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr* arguments);

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

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public extern static bool DuplicateToken(IntPtr existingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr duplicateTokenHandle);

}

Now - there are clearly some things I could tighten up, but to get to the root of the issue here - In a situation where I provide a known valid username, password, and domain - I am denied access to actually replace the file in the destination.

[Edit] I've now ( as any sane programmer would )( edits applied above ) wrapped conditionals around the 'Impersonator.Open' clauses to ensure that they sort out - with true evaluations. After stepping through execution of the 'Open' method I'm coming up with successes all the way through.

[Edit] It's probably valuable to mention that I've taken these open and close methods and placed them in an 'Execute' method that takes a Delegate as a parameter. At the time it seemed more appropriate than the current implementation; but after writing an extra thousand lines or so and still getting the same result ( most of which was for actually negotiating the files while int he context of the execute method ); I gave up and haven't come back to it. Here's that tidbit, in-case someone is interested.

public class UserImpersonator()
{
    public Object Execute(System.Delegate Method)
    {
        if (Method == null)
            throw new InvalidOperationException("Impersonator fixed-method already provided. Cannot implement another method on this impersonator." );
        else if (!this.Open())
            return null;
        else
        {
            try
            {
                this._Executing = true;

                Object ReturnValue = Method.DynamicInvoke();

                this.Close();

                this._Executing = false;

                return ReturnValue;
            }
            catch (Exception e)
            {
                return null;
            }
        }
    }
}

[Add] I should also mention that on my local system ( have yet to test in the deployment [server] environment ), if I provide totally invalid credentials, I still get access to both read and write. I presume that this means that I retain credentials that have already been opened - but if that's the case my [uneducated] assumption that 'stacking impersonation won't work' really falls apart.

I've taken quite a few passes at this to no avail; so if anyone has any suggestions or questions I am all ears.

Upvotes: 0

Views: 1616

Answers (1)

DigitalJedi805
DigitalJedi805

Reputation: 1460

Sometimes trial and error prevails.

The adjustment that is required to correct this issue is a modification of the LogonType, and LogonProvider variables in the LogonUser method. In this particular instance, the valid ( seemingly only working ) uses are with the LOGON32_PROVIDER_DEFAULT and LOGON32_LOGON_INTERACTIVE constants.

I personally do not know why[, but would be very interested in some greater detail than that found here].

After some general trial and error, for my application, the corrected code follows. I hope that this can serve as an [early revision of an] authoritative reference for the community; as most of the implementations are ... less than thorough.

Before the code dump, it would be remiss of me to not note the potential implementations of this code.

1. Create a new UserImpersonator using the UserImpersonator(String Username,String Password,String Domain,System.Delegate Method) constructor to define the credentials, and the method to be invoked; and then 'Execute' the UserImpersonator using either of the overloaded Execute methods that do not require a delegate. By nature, these methods refer directly to the method that was stored at construction, or set at a later time.
2. Create a new UserImpersonator using the UserImpersonator(String Username, String Password, String Domain) constructor, and perform one of a few actions:
    1. Set the UserImpersonator's Method and Execute it at a later time.
    2. Run an Execute overload that requires a delegate parameter on the UserImpersonator, and provide a delegate method for the impersonator to execute.
    3. Execute the 'UserImpersonator.Open' method to open the user context, execute your user-specific code, and then execute the 'UserImpersonator.Close' method to finalize the operation.

Here we go though:

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Security.Principal;
using System.Linq;
using System.Text;

namespace TopSekrit.Credentials
{
    public class UserImpersonator : IDisposable
    {
        #region Delegates
        // Port from generic event handler
        public delegate void SimpleEventDelegate(SimpleEventArgument Argument);
        #endregion

        #region Supporting Classes
        // Port from generic event handler
        public class SimpleEventArgument
        {
            public Object Source = null;
            public Object Message = null;

            public SimpleEventArgument(Object Source, String Message)
            {
                this.Source = Source;
                this.Message = Message;
            }
        }
        #endregion

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

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource, int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr* arguments);

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

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateToken(IntPtr existingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr duplicateTokenHandle);
        #endregion

        #region Constants
        // Logon Types as defined in Winbase.h
        const int LOGON32_LOGON_INTERACTIVE = 2;
        const int LOGON32_LOGON_NETWORK = 3;
        const int LOGON32_LOGON_BATCH = 4;
        const int LOGON32_LOGON_SERVICE = 5;
        const int LOGON32_LOGON_UNKNOWN = 6;
        const int LOGON32_LOGON_UNLOCK = 7;
        const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
        const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

        // Logon Providers as defined in Winbase.h
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_PROVIDER_WINNT35 = 1;
        const int LOGON32_PROVIDER_WINNT40 = 2;
        const int LOGON32_PROVIDER_WINNT50 = 3;
        #endregion

        #region Events
        public event SimpleEventDelegate OnImpersonationReset = null;

        public event SimpleEventDelegate OnImpersonationOpened = null;
        public event SimpleEventDelegate OnImpersonationClosed = null;

        public event SimpleEventDelegate OnImpersonatedExecutionStarted = null;
        public event SimpleEventDelegate OnImpersonatedExecutionFinished = null;
        #endregion

        #region Properties
        protected String _Username = String.Empty;

        public String Username
        {
            get
            {
                return this._Username;
            }

            set
            {
                if (this.IsExecuting)
                    throw new InvalidOperationException("Cannot set Username on UserImpersonator while impersonation is executing.");
                else if (this.IsOpen)
                    throw new InvalidOperationException("Cannot set Username on UserImpersonator while impersonation context is open.");
                else
                {
                    this._Username = value;

                    this.ResetImpersonation();
                }
            }
        }

        protected String _Password = String.Empty;

        public String Password
        {
            get
            {
                return this._Password;
            }

            set
            {
                if (this.IsExecuting)
                    throw new InvalidOperationException("Cannot set Password on UserImpersonator while impersonation is executing.");
                else if (this.IsOpen)
                    throw new InvalidOperationException("Cannot set Password on UserImpersonator while impersonation context is open.");
                else
                {
                    this._Password = value;

                    this.ResetImpersonation();
                }
            }
        }

        protected String _Domain = String.Empty;

        public String Domain
        {
            get
            {
                return this._Domain;
            }

            set
            {
                if (this.IsExecuting)
                    throw new InvalidOperationException("Cannot set Domain on UserImpersonator while impersonation is executing.");
                else if (this.IsOpen)
                    throw new InvalidOperationException("Cannot set Domain on UserImpersonator while impersonation context is open.");
                else
                {
                    this._Domain = value;

                    this.ResetImpersonation();
                }
            }
        }

        protected System.Delegate _Method = null;

        public System.Delegate Method
        {
            get
            {
                return this._Method;
            }

            set
            {
                this._Method = value;
            }
        }                                                               

        protected IntPtr PrimaryToken = IntPtr.Zero;

        protected IntPtr MutatedToken = IntPtr.Zero;

        protected WindowsIdentity _TargetIdentity = null;

        public WindowsIdentity TargetIdentity
        {
            get
            {
                return this._TargetIdentity;
            }
        }

        protected WindowsImpersonationContext _ImpersonationContext = null;

        public WindowsImpersonationContext ImpersonationContext
        {
            get
            {
                return this._ImpersonationContext;
            }
        }

        protected Boolean _IsExecuting = false;

        public Boolean IsExecuting
        {
            get
            {
                return this._IsExecuting;
            }
        }

        public Boolean IsOpen
        {
            get
            {
                if (this.PrimaryToken != null)
                    return true;
                else if (this.MutatedToken != null)
                    return true;
                else if (this.TargetIdentity != null)
                    return true;
                else if (this.ImpersonationContext != null)
                    return true;
                else
                    return false;
            }
        }

        protected int _LogonType = LOGON32_LOGON_INTERACTIVE;

        public int LogonType
        {
            get
            {
                return this._LogonType;
            }

            set
            {
                if (this.IsExecuting)
                    throw new InvalidOperationException("Cannot set LogonType on UserImpersonator while impersonation is executing.");
                else if (this.IsOpen)
                    throw new InvalidOperationException("Cannot set LogonType on UserImpersonator while impersonation context is open.");
                else
                {
                    this._LogonType = value;

                    this.ResetImpersonation();
                }
            }
        }

        protected int _LogonProvider = LOGON32_PROVIDER_DEFAULT;

        public int LogonProvider
        {
            get
            {
                return this._LogonProvider;
            }

            set
            {
                if (this.IsExecuting)
                    throw new InvalidOperationException("Cannot set LogonProvider on UserImpersonator while impersonation is executing.");
                else if (this.IsOpen)
                    throw new InvalidOperationException("Cannot set LogonProvider on UserImpersonator while impersonation context is open.");
                else
                {
                    this._LogonProvider = value;

                    this.ResetImpersonation();
                }
            }
        }
        #endregion

        #region Constructors
        public UserImpersonator(String Username,String Password,String Domain,System.Delegate Method,int LogonType,int LogonProvider)
        {
            if (String.IsNullOrEmpty(Username))
                throw new ArgumentNullException();
            else
            {
                this._Username = Username;
                this._Password = Password;
                this._Domain = Domain;

                this._Method = Method;

                this._LogonType = LogonType;
                this._LogonProvider = LogonProvider;

                return;
            }
        }

        public UserImpersonator(String Username, String Password, String Domain, System.Delegate Method, int LogonType)
            : this(Username, Password, Domain, Method, LogonType, LOGON32_PROVIDER_DEFAULT)
        {

        }

        public UserImpersonator(String Username, String Password, String Domain, System.Delegate Method)
            : this(Username,Password,Domain,Method,LOGON32_LOGON_INTERACTIVE)
        {

        }

        public UserImpersonator(String Username, String Password, String Domain)
            :this( Username, Password, Domain, null)
        {
        }

        public UserImpersonator(String Username, String Password)
            : this(Username, Password,String.Empty)
        {

        }

        public UserImpersonator(String Username)
            : this(Username, String.Empty)
        {

        }
        #endregion

        #region Impersonated Execution
        public virtual Object Execute()
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot Execute while another execution is already in progress.");
            else if (this.Method == null)
                throw new InvalidOperationException("UserImpersonator cannot Execute without a supplied, or stored Method to invoke.");
            else if (!this.Open())
                throw new InvalidOperationException("Could not open security context.");
            else
            {
                try
                {
                    this._IsExecuting = true;

                    Object ReturnValue = this.Method.DynamicInvoke();

                    this.Close();

                    this._IsExecuting = false;

                    return ReturnValue;
                }
                catch (Exception e)
                {
                    return null;
                }
            }
        }

        public virtual Object Execute(params object[] Arguments)
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot Execute while another execution is already in progress.");
            else if (this.Method == null)
                throw new InvalidOperationException("UserImpersonator cannot Execute without a supplied, or stored Method to invoke.");
            else if (!this.Open())
                throw new InvalidOperationException("Could not open security context.");
            else
            {
                try
                {
                    this._IsExecuting = true;

                    Object ReturnValue = this.Method.DynamicInvoke(Arguments);

                    this.Close();

                    this._IsExecuting = false;

                    return ReturnValue;
                }
                catch (Exception e)
                {
                    return null;
                }
            }
        }

        public virtual Object Execute(System.Delegate Method)
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot Execute while another execution is already in progress.");
            else if (Method == null)
                throw new InvalidOperationException("UserImpersonator cannot Execute without a supplied, or stored Method to invoke.");
            else if (!this.Open())
                throw new InvalidOperationException("Could not open security context.");
            else
            {
                try
                {
                    this._IsExecuting = true;

                    Object ReturnValue = Method.DynamicInvoke();

                    this.Close();

                    this._IsExecuting = false;

                    return ReturnValue;
                }
                catch (Exception e)
                {
                    return null;
                }
            }
        }

        public virtual Object Execute(System.Delegate Method, params object[] Arguments)
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot Execute while another execution is already in progress.");
            else if (Method == null)
                throw new InvalidOperationException("UserImpersonator cannot Execute without a supplied, or stored Method to invoke.");
            else if (!this.Open())
                throw new InvalidOperationException("Could not open security context.");
            else
            {
                try
                {
                    this._IsExecuting = true;

                    Object ReturnValue = Method.DynamicInvoke(Arguments);

                    this.Close();

                    this._IsExecuting = false;

                    return ReturnValue;
                }
                catch (Exception e)
                {
                    return null;
                }
            }
        }
        #endregion

        #region Impersonation / Depersonation
        public virtual Boolean Open()
        {
            if (this.IsOpen)
            {
                if( this.IsExecuting )
                    throw new InvalidOperationException("UserImpersonator cannot Open user context while a user context is already open and executing.");
                else
                {
                    this.Close();

                    return this.Open();
                }
            }
            else if (!LogonUser(this.Username, this.Domain, this.Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref this.PrimaryToken))
                throw this.GetLastException();
            else if (!DuplicateToken(this.PrimaryToken, 2, ref this.MutatedToken))
                throw this.GetLastException();
            else
            {
                try
                {
                    this._TargetIdentity = new WindowsIdentity(this.MutatedToken);
                }
                catch (Exception e)
                {
                    throw new Exception("UserImpersonator could not Open user context. An exception was encountered while creating the WindowsIdentity.\r\n" + e.Message + "\r\n" + e.StackTrace);
                }
                finally
                {
                    try
                    {
                        this._ImpersonationContext = this._TargetIdentity.Impersonate();
                    }
                    catch (Exception e)
                    {
                        throw new Exception("UserImpersonator could not Open user context. An exception was encountered while creating the WindowsImpersonationContext.\r\n" + e.Message + "\r\n" + e.StackTrace);
                    }
                    finally
                    {
                        this.FireImpersonationOpened();
                    }
                }
                return true;
            }
        }

        public virtual void Close()
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot Close impersonation context while in execution.");
            else
            {
                try
                {
                    if (this._TargetIdentity != null)
                    {
                        this._TargetIdentity.Dispose();
                        this._TargetIdentity = null;
                    }
                }
                catch (Exception e)
                {
                    throw new Exception("Exception encountered while disposing TargetIdentity on UserImpersonator.\r\n" + e.Message + "\r\n" + e.StackTrace);
                }
                finally
                {
                    try
                    {
                        if (this._ImpersonationContext != null)
                        {
                            this._ImpersonationContext.Undo();
                            this._ImpersonationContext.Dispose();
                            this._ImpersonationContext = null;
                        }
                    }
                    catch (Exception e)
                    {
                        throw new Exception("Exception encountered while undoing or disposing ImpersonationContext on UserImpersonator.\r\n" + e.Message + "\r\n" + e.StackTrace);
                    }
                    finally
                    {
                        try
                        {
                            if (this.MutatedToken != null)
                                if (!CloseHandle(MutatedToken))
                                    this.GetLastException();
                        }
                        catch (Exception e)
                        {
                            throw new Exception("Exception encountered while closing MutatedToken on UserImpersonator.\r\n" + e.Message + "\r\n" + e.StackTrace);
                        }
                        finally
                        {
                            try
                            {
                                if (this.PrimaryToken != null)
                                    if (!CloseHandle(this.PrimaryToken))
                                        this.GetLastException();
                            }
                            catch (Exception e)
                            {
                                throw new Exception("Exception encountered while closing PrimaryToken on UserImpersonator.\r\n" + e.Message + "\r\n" + e.StackTrace);
                            }
                            finally
                            {
                                this.FireImpersonationClosed();
                            }
                        }
                    }
                }
                return;
            }
        }

        protected virtual void ResetImpersonation()
        {
            if (this.IsExecuting)
                throw new InvalidOperationException("UserImpersonator cannot ResetImpersonation while impersonation is already executing.");
            else if (this.IsOpen)
            {
                this.Close();

                this.ResetImpersonation();

                return;
            }
            else
            {
                this.Open();

                this.FireImpersonationReset();

                return;
            }
        }
        #endregion

        #region Error Handling
        private Exception GetLastException()
        {
            return new ApplicationException(this.GetErrorMessage(Marshal.GetLastWin32Error()));
        }

        public unsafe string GetErrorMessage(int ErrorCode)
        {
            int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
            int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
            int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

            int messageSize = 255;
            string lpMsgBuf = "";
            int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;

            IntPtr ptrlpSource = IntPtr.Zero;
            IntPtr ptrArguments = IntPtr.Zero;

            int retVal = FormatMessage(dwFlags, ref ptrlpSource, ErrorCode, 0, ref lpMsgBuf, messageSize, &ptrArguments);

            if (retVal == 0)
                return string.Format("Failed to format message for error code '{0}'.", ErrorCode);

            return lpMsgBuf;
        }
        #endregion

        #region Disposability
        public virtual void Dispose()
        {
            this.Close();

            this._Username = null;
            this._Password = null;
            this._Domain = null;
            this._Method = null;

            return;
        }
        #endregion

        #region Event Firing
        protected virtual void FireImpersonationReset()
        {
            if (this.OnImpersonationReset != null)
                this.OnImpersonationReset(new Events.SimpleArgument(this, "Impersonation context has been reset."));
            return;
        }

        protected virtual void FireImpersonationOpened()
        {
            if (this.OnImpersonationOpened != null)
                this.OnImpersonationOpened(new Events.SimpleArgument(this, "Impersonation context has been opened."));
            return;
        }

        protected virtual void FireImpersonationClosed()
        {
            if (this.OnImpersonationClosed != null)
                this.OnImpersonationClosed(new Events.SimpleArgument(this, "Impersonation context has been closed."));
            return;

        }

        protected virtual void FireImpersonationExecutionStarted()
        {
            if (this.OnImpersonatedExecutionStarted != null )
                this.OnImpersonatedExecutionStarted(new Events.SimpleArgument(this, "Impersonated execution has started."));
            return;
        }

        protected virtual void FireImpersonationExecutionFinished()
        {
            if (this.OnImpersonatedExecutionFinished != null)
                this.OnImpersonatedExecutionFinished(new Events.SimpleArgument(this, "Impersonated execution has finished."));
            return;
        }
        #endregion
    }
}

[Edit]: Updated 8/12/14 - Added LogonType and LogonProvider constructors. Open method overrides to follow at my first availability.

My feeling is that this is one of the more thorough implementations of windows user impersonation - I hope you all find some use in it. Questions or comments are welcome; as I am sure this has room for improvement still.

Upvotes: 1

Related Questions