I'm trying to test an ASP.NET MVC4 web application connecting with different users at the same time using impersonation. So, the idea for the test was to execute several instances of the driver each with a different impersonated user. It sounds conceptually easy...
However, if after calling LogonUser with a different user from the current, you run the IEDriverServer, an application fault dialog is displayed when it is starting. This is the information registered in the EventLog:
Faulting application name: IEDriverServer.exe, version: 2.44.0.0, time stamp: 0x54496690
Faulting module name: IED7543.tmp, version: 0.0.0.0, time stamp: 0x5449668c
Exception code: 0xc0000005
Fault offset: 0x000000000009d609
Faulting process id: 0x760
Faulting application start time: 0x01d00e3d12f93475
Faulting application path: ...\bin\IEDriverServer.exe
Faulting module path: C:\Users\user\AppData\Local\Temp\IED7543.tmp
Report Id: 5383a54d-7a30-11e4-b39c-000c29b46927
Here is the code I'm using for impersonation. It is based in all the examples I have found, so I there are not surprises... We also tried to used the SimpleImpersonation packege with the same result:
public class ImpersonationHelper
{
public enum LogonType
{
Interactive = 2,
Network = 3,
Batch = 4,
Service = 5,
Unlock = 7,
Cleartext = 8, // Win2K or higher
NewCredentials = 9 // Win2K or higher
};
public enum LogonProvider
{
Default = 0,
Winnt35 = 1,
Winnt40 = 2,
Winnt50 = 3
};
public enum ImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out IntPtr phToken);
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel, ref IntPtr hNewToken);
public sealed class Impersonator : IDisposable
{
public string Domain { get; private set; }
public string User { get; private set; }
public string Password { get; private set; }
public LogonType Type { get; private set; }
public LogonProvider Provider { get; private set; }
private WindowsImpersonationContext _context;
private IntPtr _token;
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public Impersonator(string domain, string user, string password,
LogonType type = LogonType.Interactive,
LogonProvider provider = LogonProvider.Default)
{
Domain = domain;
User = user;
Password = password;
Type = type;
Provider = provider;
_token = IntPtr.Zero;
Logon();
}
public void Dispose()
{
Undo();
}
private void Logon()
{
try
{
if (!LogonUser(User, Domain, Password, (int)Type, (int)Provider, out _token))
{
int ret = Marshal.GetLastWin32Error();
throw new Exception(String.Format("LogonUser failed with error code : {0}", ret));
}
_context = WindowsIdentity.Impersonate(_token);
}
catch (Exception exception)
{
var message = exception.Message;
Undo();
}
}
private void Undo()
{
try
{
if (_token != IntPtr.Zero)
{
CloseHandle(_token);
_token = IntPtr.Zero; // Clean _token so Undo() could be called several times
}
if (_context != null)
{
_context.Undo();
_context = null; // Clean _context so Undo() could be called several times
}
}
catch (Exception exception)
{
var message = exception.Message;
// Releasing resources failed...
}
}
}
}
The code that raises the exception is:
using (new ImpersonationHelper.Impersonator("WIN-NKLTTMMUEPD", "tester", "tester"))
{
using (IWebDriver driver = new InternetExplorerDriver())
{
driver.Url = _appAddress;
return null;
}
}
Does anybody know how can I prevent this error from appearing? Thank you very much in advance.
I was not able to reproduce your IEDriver
crash.
However, as you have put a bounty on this question, I didn't mind spending a bit of time trying to find a solution. I have never implemented Windows Authentication or needed to test impersonation before, however I have experience with UI Automation Tests.
Time-boxed to an hour:
I'm not sure how exactly you have implemented your tests, however .NET/MVC projects I would highly recommend that you use Seleno. It is a wrapper around the Selenium Web Driver (what you are currently using), however it makes UI testing less painful/brittle by forcing the convention of implementing Page Objects and Page Components and reading and writing web page data using strongly typed view models.
Also I would recommend using the SimpleImpersonation library as it is a safer/cleaner implementation of the Win32 API LogonUser function, e.g. it uses SafeHandle
and dispose path is cleaner.
The test that I wrote,
[Test]
public void GivenImpersonatedUser_IsLoggedInCorrectly()
{
const string domain = "domain"; // . for local machine
const string username = "impersonate.username";
const string password = "strongp@ssw0rd1";
const LogonType logonType = LogonType.Interactive;
using (Impersonation.LogonUser(domain, username, password, logonType))
{
var page = Host.Instance.NavigateToInitialPage<HomePage>();
Assert.That(page.LoginPanel.LoggedInUserName, Is.EqualTo(string.Format("{0}\\{1}", domain, username)));
}
}
This is the Seleno Page Object, named HomePage
, which uses a reusable Component LoginPanel
.
public class HomePage : Page
{
public LoginPanel LoginPanel
{
get { return GetComponent<LoginPanel>(); }
}
}
public class LoginPanel : UiComponent
{
public string LoggedInUserName
{
get { return Find.Element(By.Id("login-username")).Text; }
}
}
Seleno Host configuration, using IEDriver.
public static class Host
{
private static readonly InternetExplorerDriver Driver = new InternetExplorerDriver();
public static readonly SelenoHost Instance = new SelenoHost();
static Host()
{
Instance.Run(c =>
{
c.WithRemoteWebDriver(() => Driver);
c.ProjectToTest(new WebApplication(ProjectLocation.FromFolder("WebApplication1"), 61285));
c.UsingLoggerFactory(new ConsoleFactory());
c.UsingCamera("C:\\screenshots");
});
}
var codeBase = assembly.CodeBase;
var uri = new UriBuilder(codeBase);
var path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
}
A few other things I've learned:
- You will get error code 1326
if your username or password is incorrect.
- You can use Process Hacker to confirm that the webdriver process is launched with the correct authentication token.
As I said, I wasn't able to answer you exact problem, however try using the above and comment back - happy to help further.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With