Here’s a problem I had recently, and I thought I’d post the code here so that (a) I don’t forget it; and (b) it helps other people in the same situation. So here’s my problem: I’m using impersonation inside an Asp.Net MVC app. It doesn’t work too well because I’m under AD, but it works to some extent. What I’m doing inside the web app is consuming WCF services. All is good, but…
But unfortunately, Asp.Net impersonation works fine only in Asp.Net, which is a problem because I also have unit tests. You see, unit tests run by themselves, ignoring IIS configurations and whatnot (the fact that you have an App.config instead of Web.config ought to be a hint). Also, unit tests are non-interactive, so you cannot go with ‘default’ authentication and pop up a username/password dialog when you need to test a particular combination. It’s just not practical.
So here’s the deal: we’ve got a DLL testing a WCF web service, and a need to provide a particular user for purposes of authentication. How to do it? Unfortunately, it requires unmanaged code, plus some unusual unit-testing hackery (nothing scary, mind you).
First, we need a way – an unmanaged way – of actually creating a user (as in, a WindowsImpersonationContext. This context is created from an unmanaged handle that must later be closed. The two methods that allow this are shown below.
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
You will not have much problem calling LogonUser from above unless you are trying to do it on an accout that does not allow interactive logon. This can be a headache to figure out, but luckily the exception we catch (later) will be quite descriptive.
Having these two methods, we can create the SetUp method for our unit test:
private IntPtr token;
private WindowsImpersonationContext ctx;
private const string domainName = "ACTIVEMESA"; // put your domain here
[SetUp]
public void SetUp()
{
// try making user
token = IntPtr.Zero;
bool ok = LogonUser("userName", domainName, "password", 4, 0, ref token);
if (!ok)
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
ctx = WindowsIdentity.Impersonate(token);
}
Okay, so as you can see, we attempt to log in the user via an Interactive Logon, which explains the 4 in the parameter (I don’t bother defining constants for one-off things). This particular user must have the Log on Locally permission (use gpedit.msc to set that). Alternatively, you can just add this user to the Administrators group… bad practice, I know, but if you’re inside AD like me, doing it the ‘right way’ can be hell and will cause you waste a lot of time.
So once we defined the SetUp, we can also define the TearDown, which is easy:
[TearDown]
public void TearDown()
{
CloseHandle(token);
ctx.Undo();
ctx.Dispose();
}
That’s it! You can now write your tests, and all of them will be executed under the particular user. You’ll get exceptions only if you provided the wrong username/password/domain combinations or if you are attempting to log in a user which doesn’t have interactive logon rights.