Search code examples
c#winformsmstestimpersonation

how to write tests that impersonates different users?


My Winforms app set permissions based on the group membership found in the current process.

I just made a unit test in MSTEST.

I'd like to run it as other users so I can verify the expected behavior.

Here's what I'm kind of shooting for:

    [TestMethod]
    public void SecuritySummaryTest1()
    {
        Impersonate(@"SomeDomain\AdminUser", password);
        var target = new DirectAgentsSecurityManager();
        string actual = target.SecuritySummary;
        Assert.AreEqual(
            @"Default=[no]AccountManagement=[no]MediaBuying=[no]AdSales=[no]Accounting=[no]Admin=[YES]", actual);
    }
    [TestMethod]
    public void SecuritySummaryTest2()
    {
        Impersonate(@"SomeDomain\AccountantUser", password);
        var target = new DirectAgentsSecurityManager();
        string actual = target.SecuritySummary;
        Assert.AreEqual(
            @"Default=[no]AccountManagement=[YES]MediaBuying=[no]AdSales=[no]Accounting=[YES]Admin=[NO]", actual);
    }

Solution

  • public class UserCredentials
    {
        private readonly string _domain;
        private readonly string _password;
        private readonly string _username;
    
        public UserCredentials(string domain, string username, string password)
        {
            _domain = domain;
            _username = username;
            _password = password;
        }
    
        public string Domain { get { return _domain; } }
        public string Username { get { return _username; } }
        public string Password { get { return _password; } }
    }
    public class UserImpersonation : IDisposable
    {
        private readonly IntPtr _dupeTokenHandle = new IntPtr(0);
        private readonly IntPtr _tokenHandle = new IntPtr(0);
        private WindowsImpersonationContext _impersonatedUser;
    
        public UserImpersonation(UserCredentials credentials)
        {
            const int logon32ProviderDefault = 0;
            const int logon32LogonInteractive = 2;
            const int securityImpersonation = 2;
    
            _tokenHandle = IntPtr.Zero;
            _dupeTokenHandle = IntPtr.Zero;
    
            if (!Advapi32.LogonUser(credentials.Username, credentials.Domain, credentials.Password,
                                    logon32LogonInteractive, logon32ProviderDefault, out _tokenHandle))
            {
                var win32ErrorNumber = Marshal.GetLastWin32Error();
    
                // REVIEW: maybe ImpersonationException should inherit from win32exception
                throw new ImpersonationException(win32ErrorNumber, new Win32Exception(win32ErrorNumber).Message,
                                                 credentials.Username, credentials.Domain);
            }
    
            if (!Advapi32.DuplicateToken(_tokenHandle, securityImpersonation, out _dupeTokenHandle))
            {
                var win32ErrorNumber = Marshal.GetLastWin32Error();
    
                Kernel32.CloseHandle(_tokenHandle);
                throw new ImpersonationException(win32ErrorNumber, "Unable to duplicate token!", credentials.Username,
                                                 credentials.Domain);
            }
    
            var newId = new WindowsIdentity(_dupeTokenHandle);
            _impersonatedUser = newId.Impersonate();
        }
    
        public void Dispose()
        {
            if (_impersonatedUser != null)
            {
                _impersonatedUser.Undo();
                _impersonatedUser = null;
    
                if (_tokenHandle != IntPtr.Zero)
                    Kernel32.CloseHandle(_tokenHandle);
    
                if (_dupeTokenHandle != IntPtr.Zero)
                    Kernel32.CloseHandle(_dupeTokenHandle);
            }
        }
    }
    
    internal static class Advapi32
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL,
                                                 out IntPtr DuplicateTokenHandle);
    
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
                                            int dwLogonType, int dwLogonProvider, out IntPtr phToken);
    }
    
    internal static class Kernel32
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        [return : MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);
    }
    

    I didn't include the implementation of ImpersonationException but it's not important. It doesn't do anything special.