как писать тесты, которые выдают себя за разных пользователей? - PullRequest
3 голосов
/ 22 марта 2011

Мои приложения 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 ]

9 голосов
/ 22 марта 2011
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, но это не важно. Это не делает ничего особенного.

4 голосов
/ 30 марта 2011

Вы также можете установить текущий принципал напрямую, если этого достаточно для вашего варианта использования:

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

Основной восстанавливается после каждого метода тестирования в соответствии с этой страницей подключения . Обратите внимание, что этот метод не будет работать, если он используется с клиентами веб-служб, которые проверяют принципал (для этого случая использования решение Джима Боллы работает просто отлично).

2 голосов
/ 22 марта 2011

Вы должны использовать фиктивные объекты для симуляции зависимых объектов в разных состояниях. См. moq для примера насмешливого фреймворка:

Вам нужно будет абстрагироваться от бита, который обеспечивает текущего пользователя за интерфейсом. И передайте макет этого интерфейса тестируемому классу.

1 голос
/ 22 сентября 2018

Использование SimpleImpersonation .

Запустите Install-Package SimpleImpersonation, чтобы установить пакет nuget.

Тогда

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

Это самое простое и элегантное решение.

1 голос
/ 14 мая 2014

Еще одна вещь, которую нужно добавить к решению Маркуса, вам также может понадобиться установить HttpContext.Current.User в Thread.CurrentPrincipal, который вы создаете / олицетворяете для определенных вызовов RoleManager (например, Roles.GetRolesForUser (Identity.Name)). Если вы используете версию метода без параметров, это не нужно, но у меня есть инфраструктура авторизации, которая требует передачи имени пользователя.

Вызов этой подписи метода с олицетворенным Thread.CurrentPrincipal завершится ошибкой с «Метод поддерживается только в том случае, если параметр имени пользователя совпадает с именем пользователя в текущей идентификации Windows». Как сообщение предполагает, что в коде WindowsTokenRoleProvider есть внутренняя проверка на «HttpContext.Current.Identity.Name». Метод не работает, если они не совпадают.

Вот пример кода для ApiController, демонстрирующий авторизацию Action. Я использую олицетворение для модульного и интеграционного тестирования, чтобы можно было проводить тестирование под разными ролями AD, чтобы обеспечить безопасность до развертывания.

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

Надеюсь, это поможет кому-то еще:)

...