5 Stimmen

Wie schreibt man Tests, die verschiedene Benutzer verkörpern?

Meine Winforms-Anwendung legt die Berechtigungen auf der Grundlage der im aktuellen Prozess gefundenen Gruppenmitgliedschaft fest.

Ich habe gerade einen Einheitstest in MSTEST erstellt.

Ich möchte es als anderer Benutzer ausführen, damit ich das erwartete Verhalten überprüfen kann.

Das ist es, was ich anstrebe:

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

11voto

Jim Bolla Punkte 8075
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);
}

Ich habe die Implementierung von ImpersonationException nicht angegeben, aber das ist nicht wichtig. Sie tut nichts Besonderes.

5voto

Markus Bruckner Punkte 2822

Sie können den aktuellen Auftraggeber auch direkt festlegen, wenn dies für Ihren Anwendungsfall ausreichend ist:

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

Der Auftraggeber wird nach jeder Prüfmethode gemäß dieser Methode wiederhergestellt Seite verbinden . Beachten Sie, dass diese Methode nicht funktioniert, wenn sie mit Webdienst-Clients verwendet wird, die den Auftraggeber überprüfen (für diesen Anwendungsfall funktioniert die Lösung von Jim Bolla sehr gut).

2voto

gmn Punkte 3969

Sie sollten Mock-Objekte verwenden, um abhängige Objekte in verschiedenen Zuständen zu simulieren. Siehe moq für ein Beispiel für ein Mocking-Framework:

Sie müssten den Teil, der den aktuellen Benutzer hinter einer Schnittstelle bereitstellt, herausnehmen. Und eine Attrappe dieser Schnittstelle an die zu testende Klasse übergeben.

2voto

SharpCoder Punkte 17283

Verwenden Sie SimpleImpersonation .

ausführen. Install-Package SimpleImpersonation um das nuget-Paket zu installieren.

Dann

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

Dies ist die einfachste und eleganteste Lösung.

1voto

Wade Bee Punkte 118

Ein weiterer Punkt, der die Lösung von Markus ergänzt: Möglicherweise müssen Sie auch HttpContext.Current.User auf den Thread.CurrentPrincipal setzen, den Sie für bestimmte Aufrufe an den RoleManager erstellen/importieren (z. B.: Roles.GetRolesForUser(Identity.Name) ) Wenn Sie die parameterlose Version der Methode verwenden, ist dies nicht erforderlich, aber ich habe eine Autorisierungsinfrastruktur eingerichtet, die die Übergabe eines Benutzernamens erfordert.

Der Aufruf dieser Methodensignatur mit einem unpersönlichen Thread.CurrentPrincipal schlägt mit "Die Methode wird nur unterstützt, wenn der Parameter Benutzername mit dem Benutzernamen in der aktuellen Windows-Identität übereinstimmt. Wie die Meldung andeutet, gibt es eine interne Prüfung in der WindowsTokenRoleProvider-Code gegen "HttpContext.Current.Identity.Name". Die Methode schlägt fehl, wenn sie nicht übereinstimmen.

Hier ist Beispielcode für einen ApiController, der die Autorisierung einer Aktion demonstriert. Ich verwende Impersonation für Unit- und Integrationstests, damit ich QA unter verschiedenen AD-Rollen durchführen kann, um sicherzustellen, dass die Sicherheit vor der Bereitstellung funktioniert.

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

Ich hoffe, das hilft noch jemandem :)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X