【问题标题】:how to write tests that impersonates different users?如何编写模拟不同用户的测试?
【发布时间】:2011-07-20 19:39:20
【问题描述】:

我的 Winforms 应用根据在当前进程中找到的组成员设置权限。

我刚刚在 MSTEST 中做了一个单元测试。

我想以其他用户身份运行它,以便验证预期行为。

这就是我的目标:

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

【问题讨论】:

  • 即使我正在测试一个类的属性?是否存在对覆盖的 o/s 安全子系统的依赖项?
  • @LasseV.Karlsen。你能解释一下为什么模拟用户不能使其成为单元测试吗?如果我有一个在用户没有权限时应该失败的方法,我想对该场景进行单元测试以实现代码覆盖率。
  • 您依赖于周围环境,例如您的域/服务器上存在用户,该用户仍然处于活动状态,密码仍然正确等。相反,模拟检索权限的部分这样可以测试被测代码,而不必冒充用户。任何依赖于环境(即代码之外的东西)设置的测试恰到好处都是集成测试,而不是单元测试。

标签: c# winforms mstest impersonation


【解决方案1】:

使用SimpleImpersonation

运行 Install-Package SimpleImpersonation 以安装 nuget 包。

那么

var credentials = new UserCredentials(domain, username, password);
Impersonation.RunAsUser(credentials, LogonType.NewCredentials, () =>
{
    // Body of the unit test case. 
}); 

这是最简单优雅的解决方案。

【讨论】:

    【解决方案2】:

    要添加到 Markus 解决方案的另一件事是,您可能还需要将 HttpContext.Current.User 设置为您正在创建/模拟的对 RoleManager 的某些调用的 Thread.CurrentPrincipal(例如: Roles.GetRolesForUser(Identity.Name) )如果您使用该方法的无参数版本,则不需要,但我有一个授权基础设施,需要传递用户名。

    使用模拟的 Thread.CurrentPrincipal 调用该方法签名将失败,并显示 “仅当用户名参数与当前 Windows 标识中的用户名匹配时才支持该方法”。 正如消息所示,WindowsTokenRoleProvider 代码中有一个针对“HttpContext.Current.Identity.Name”的内部检查。如果它们不匹配,则该方法将失败。

    这里是 ApiController 的示例代码,用于演示操作的授权。我使用模拟进行单元和集成测试,因此我可以在不同的 AD 角色下进行 QA,以确保在部署之前安全工作。

    using System.Web
    
    List<string> WhoIsAuthorized = new List<string>() {"ADGroup", "AdUser", "etc"};
    
    public class MyController : ApiController {
        public MyController() {
         #if TEST 
            var myPrincipal = new WindowsPrincipal(new WindowsIdentity("testuser@contoso.com"));
            System.Threading.Thread.CurrentPrincipal = myPrincipal;
            HttpContext.Current.User = myPrincipal;
         #endif
        }
        public HttpResponseMessage MyAction() {
           var userRoles = Roles.GetRolesForUser(User.Identity.Name);
           bool isAuthorized = userRoles.Any(role => WhoIsAuthorized.Contains(role));
        }
    }
    

    希望这对其他人有帮助:)

    【讨论】:

      【解决方案3】:

      如果您的用例足够,您也可以直接设置当前主体:

      System.Threading.Thread.CurrentPrincipal 
          = new WindowsPrincipal(new WindowsIdentity("testuser@contoso.com"));
      

      每个测试方法后根据这个connect page恢复主体。 请注意,如果与检查主体的 Web 服务客户端一起使用此方法将不起作用(对于此用例,Jim Bolla 的解决方案工作得很好)。

      【讨论】:

        【解决方案4】:
        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);
        }
        

        我没有包括 ImpersonationException 的实现,但这并不重要。它没有做任何特别的事情。

        【讨论】:

          【解决方案5】:

          您应该使用 Mock 对象来模拟不同状态的依赖对象。 有关模拟框架的示例,请参阅 moq

          您需要抽象出在界面后面提供当前用户的位。并将该接口的模拟传递给被测类。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-10
            • 1970-01-01
            • 1970-01-01
            • 2020-06-30
            • 1970-01-01
            • 2019-11-27
            相关资源
            最近更新 更多