如何编写冒充不同用户的测试?

时间:2011-03-22 20:19:26

标签: c# winforms mstest impersonation

我的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);
    }

5 个答案:

答案 0 :(得分:9)

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的实现,但这并不重要。它没有做任何特别的事情。

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

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

以下是演示Action授权的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));
    }
}

希望这有助于其他人:)

答案 4 :(得分:1)

使用SimpleImpersonation

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

然后

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

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