Writing Test Code with Impersonation

Some of your (unit) tests may be required to run with predefined credentials, and if all your tests should run as one single identity your might as well use the command line runas.exe tool to start your testrunner (or whatever your favorite naming for this is). In some (rare?) occasions you may need to do tests with a large number of different credentials. There is a number of ways to do this and here I will show you two methods: P/Invoke and Windows Communication Foundation (WCF). I don’t feel like explaining a lot today so I’ll just drop you some sample code you can play with.

P/Invoke

using System;

using System.Security.Principal;

using System.Runtime.InteropServices;

using System.Threading;

namespace Test

{

    class Program

    {

        static void Main(string[] args)

        {

            User u = new User("joe",string.Empty,"P\"ssw0rd");

            Thread t = new Thread(DoWorkAs);

            t.Start(u);

            Console.ReadLine();

        }

        private static void DoWorkAs(object o)

        {

            User u = o as User;

            //Print user of application

     Console.WriteLine(WindowsIdentity.GetCurrent().Name);

            IntPtr hToken = IntPtr.Zero;

            IntPtr hTokenDuplicate = IntPtr.Zero;

            if (Win32.LogonUser(u.UserName, u.Domain, u.Password, 2 /*LOGON32_LOGON_INTERACTIVE*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out hToken))

            {

                if (Win32.DuplicateToken(hToken, 2, out hTokenDuplicate))

                {

                    WindowsIdentity windowsIdentity = new WindowsIdentity(hTokenDuplicate);

                    WindowsImpersonationContext impersonationContext = windowsIdentity.Impersonate();

                    // domain\username

                    Console.WriteLine(WindowsIdentity.GetCurrent().Name);

                    //INVOKE UNIT TEST HERE - or call test controller or whatever you prefer to call it today...

                    //revert

                    impersonationContext.Undo();

                    Console.WriteLine(WindowsIdentity.GetCurrent().Name);

                }

          }

            if (hToken != IntPtr.Zero) Win32.CloseHandle(hToken);

            if (hTokenDuplicate != IntPtr.Zero) Win32.CloseHandle(hTokenDuplicate);

        }

    }

    public class User

    {

        public User(string u, string d, string p)

        {

            Domain = d;

            UserName = u;

            Password = p;

        }

        public string UserName;

        public string Domain;

        public string Password;

    }

    public class Win32

    {

        // P/Invoke snask

        [DllImport("advapi32.dll", SetLastError = true)]

        public static extern bool LogonUser(

            string lpszUsername,

            string lpszDomain,

            string lpszPassword,

            int dwLogonType,

            int dwLogonProvider,

            out IntPtr phToken);

        [DllImport("advapi32.dll", SetLastError = true)]

        public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int

           SECURITY_IMPERSONATION_LEVEL, out IntPtr DuplicateTokenHandle);

        [DllImport("kernel32.dll", SetLastError = true)]

        public static extern bool CloseHandle(IntPtr hHandle);

    }

}

(Note: In this example I could have used the System.Net.NetworkCredential class instead of the homemade User class)

WCF

using System;

using System.ServiceModel;

using System.Security.Principal;

using System.Threading;

using System.Net;

namespace TestWCF

{

    [ServiceContract(Namespace = "NotDefault")]

    public interface IImpersonateTest

    {

        [OperationContract]

        void RunAsTest();

    }

    public class ImpersonateTest : IImpersonateTest

    {

        [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]

        public void RunAsTest()

        {

            Console.WriteLine(

                string.Format("Inside a WCF Service - caller: {0}",

                WindowsIdentity.GetCurrent().Name));

            //INVOKE UNIT TEST HERE - or call test controller or whatever you prefer to call it today...

        }

    }

    class Program

    {

        static string _address = "net.pipe://localhost/ImpersonateTest";

        static NetNamedPipeBinding _binding = new NetNamedPipeBinding();

        static void Main(string[] args)

        {

            Thread t = new Thread(Server);

            ManualResetEvent mre = new ManualResetEvent(false);

            t.Start(mre);

            mre.WaitOne();

            EndpointAddress epa = new EndpointAddress(_address);

            ChannelFactory<IImpersonateTest> factory = new ChannelFactory<IImpersonateTest>(_binding,_address);

            factory.Credentials.Windows.ClientCredential = new NetworkCredential("joe", "P\"ssw0rd", String.Empty);

            IImpersonateTest proxy = factory.CreateChannel();

            proxy.RunAsTest();

            ((ICommunicationObject)proxy).Close();

            Console.WriteLine("Press <ENTER> to exit.");

            Console.ReadLine();

            t.Abort();

            t.Join();

            Console.WriteLine("Done...");

        }

        static void Server(object o)

        {

            ManualResetEvent mre = o as ManualResetEvent;

            ServiceHost sh = new ServiceHost(typeof(ImpersonateTest));

            try

            {

                sh.AddServiceEndpoint(typeof(IImpersonateTest), _binding, _address);

                sh.Authorization.ImpersonateCallerForAllOperations = true;

                sh.Open();

                mre.Set(); //Ready - client can continue

                Console.WriteLine("Service host opened");

                Thread.Sleep(System.Threading.Timeout.Infinite);

            }

            catch (ThreadAbortException)

            {

                //Abort is called at some point.

            }

            finally

            {

                sh.Close();

                Console.WriteLine("Service host closed");

            }

        }

    }

}