Udostępnij za pośrednictwem


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");

            }

        }

    }

}

Comments

  • Anonymous
    April 14, 2007
    For people who prefer to utilize the VSTS unit testing framework (like me), wouldn't it be possible to call WindowsIdentity.Impersonate in a [TestInitialize] method, and WindowsImpersonationContext.Undo in a [TestCleanup] method? In any case, the level of complication just illustrates why you should implement security using IPrincipal whenever possible :)

  • Anonymous
    April 15, 2007
    Yes that would be possible and is the way I have done it at one point - note however that Impersonate does not work the way you might like it to do. WindowsIdentity has a couple of constructors and the one taking a Handle as argument is your friend. For that to work with .NET you need to use the P/Invoke solution shown above. See also: http://blogs.msdn.com/shawnfa/archive/2005/03/21/400088.aspx The reason for showing the WCF samlpe is that despite the somewhat involved code it presents a purely managed way of obtaining an impersonation context.