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