2011-03-22 2 views
3

내 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); 
    } 
+0

을 내가 정확히 하나 개의 클래스의 속성을 테스트하고있어 비록? 오버라이드하는 o/s 보안 서브 시스템에 대한 종속성이 있다는 사실입니까? –

+0

@ LasseV.Karlsen. 왜 사용자를 가장하는 것이 단위 테스트가되지 않는지 설명 할 수 있습니까? 사용자에게 사용 권한이 없을 때 실패해야하는 방법이있는 경우 코드 적용 범위를 달성하기 위해 해당 시나리오에 대한 단위 테스트를하고 싶습니다. – Mashmagar

+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의 구현을 포함하지 않았다 그러나 그것은 중요하지 않습니다. 특별한 일은하지 않습니다.

2

서로 다른 지역에서의 종속 개체를 시뮬레이션하기 위해 모의 객체를 사용한다. 조롱 프레임 워크의 예는 moq을 참조하십시오.

인터페이스 뒤에 현재 사용자를 제공하는 비트를 추상화해야합니다. 그리고 그 인터페이스의 모의를 테스트중인 클래스에 전달하십시오.

4

또한 그 사용 사례에 대한 충분한 있는지 직접 현재 주를 설정할 수 있습니다

System.Threading.Thread.CurrentPrincipal 
    = new WindowsPrincipal(new WindowsIdentity("[email protected]")); 

교장은이 connect page에 따라 각 시험 방법 후 복원됩니다. 주범을 확인하는 웹 서비스 클라이언트와 함께 사용하는 경우이 방법은 작동하지 않습니다 (이 사용 사례의 경우 Jim Bolla의 솔루션은 잘 작동 함).

1

Markus의 솔루션에 추가 할 또 다른 사항은 HttpContext.Current.User를 RoleManager에 대한 특정 호출에 대해 생성하거나 가장하는 Thread.CurrentPrincipal로 설정해야 할 수도 있습니다 (예 : Roles.GetRolesForUser (Identity.Name)) 매개 변수없는 버전의 메서드를 사용하는 경우이 메서드는 필요하지 않지만 전달할 사용자 이름이 필요한 권한 부여 인프라가 있습니다.

가장 된 Thread.CurrentPrincipal을 사용하여 해당 메서드 서명을 호출하면 "사용자 이름 매개 변수가 현재 Windows ID의 사용자 이름과 일치하는 경우에만 메서드가 지원됩니다"와 함께 실패합니다. 메시지에서 알 수 있듯이 WindowsTokenRoleProvider 코드에 "HttpContext.Current.Identity.Name"에 대한 내부 검사가 있습니다. 일치하지 않으면 메소드가 실패합니다.

여기에 액션의 승인을 보여주는 ApiController에 대한 샘플 코드입니다. 유닛 및 통합 테스트에 가장 (impersonation)을 사용하므로 배포 전에 보안이 작동하는지 확인하기 위해 다양한 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("[email protected]")); 
     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)); 
    } 
} 

희망이 다른 사람 도움 :