Geeks With Blogs
MoonWalker Biztalk Driven Addiction

 

Usually as a developer I am logged with a user with more rights than a usual user. Even if I am not using the Admin account often I have to create one or more user with associated groups to simulate my target environment and log with those user and test my application. This is time consuming for me and i want to be sure I can retest those cases as often and as fast as I want. The idea may seem strange as those tests looks more like integration tests, but i don't want to deploy  my application, test manually with different accounts the expected behavior and play ping pong between integration and dev environment to correct the issues.

 

So I need to test the behavior of my code according to the rights of the user running it (this is all about Authorization). This can occur when access to classes or methods are limited to some users, when  external resources (DB, Files…) with specific rights are involved.

 

To industrialize this in Unit Tests I am using an Helper class with Unmanaged code.

 

Create an Unmanaged project with the following code :

 

using System;

using System.Runtime.InteropServices;

using System.Security.Permissions;

 

[assembly: SecurityPermissionAttribute(SecurityAction.RequestMinimum, UnmanagedCode = true)]

[assembly: PermissionSetAttribute(SecurityAction.RequestMinimum, Name = "FullTrust")]

namespace Unmanaged.Helpers.SafeNativeMethods

{

public static class Impersonalisator

{

const Int32 LOGON32_PROVIDER_DEFAULT = 0;

const Int32 LOGON32_LOGON_INTERACTIVE = 2;

 

[DllImport("advapi32.dll", SetLastError = true)]

private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,

int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

 

[DllImport("kernel32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]

private unsafe static extern int FormatMessage(int dwFlags, ref IntPtr lpSource,

int dwMessageId, int dwLanguageId, ref String lpBuffer, int nSize, IntPtr* Arguments);

 

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]

public extern static bool CloseHandle(IntPtr handle);

 

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]

public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,

int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

 

public static bool LogonUser(String userName, String domain, String password, ref IntPtr identityToken)

{

return LogonUser(userName, domain, password, 2, 0, ref identityToken);

}

}

}

 

This helper class will manage the impersonation.

 

In my Test Classes, I create a new test project referencing the helper class we created above and I am writing my tests as follow:

 

Negative Test :  Trying to log with an inexistent user.

 

[TestMethod]

public void TestAsUnknownUser()

{

// Create the security Context

IntPtr myToken = IntPtr.Zero;

String userName = "FakeUser";

WindowsIdentity newId;

WindowsImpersonationContext impersonatedUser;

 

Assert.IsFalse(Impersonalisator.LogonUser(userName, "VILFHA", "FakeUserPwd", ref myToken));

 

// Reset the Security Context

impersonatedUser.Undo();

 

// Check the identity.

Console.WriteLine("After Undo: " + WindowsIdentity.GetCurrent().Name);

 

// Free the tokens.

if (myToken != IntPtr.Zero)

Impersonalisator.CloseHandle(myToken);

}

 

 

Positive and Negative Test. According to how the code is implemented  the content  of the test will change. The aim is to use the different user privileges on all the securable. The content of the Assert statements will vary and or the  [ExpectedException(typeof(namespace.NamedException))] attribute can also be used.

 

 

[TestMethod]

public void TestKnownUser()

{

// Create the security Context

IntPtr myToken = IntPtr.Zero;

String userName = "UserRole1";

WindowsIdentity newId;

WindowsImpersonationContext impersonatedUser;

 

Boolean loginSuccessfull = Impersonalisator.LogonUser(userName, "VILFHA", "UserRole1Pwd", ref myToken);

 

Assert.IsTrue(loginSuccessfull);

 

if (loginSuccessfull)

{

newId = new WindowsIdentity(myToken);

impersonatedUser = newId.Impersonate();

 

Console.WriteLine("Impersonalisation worked. Current user : {0}", WindowsIdentity.GetCurrent().Name);

}

else

{

// To ensure we keep a trace if something wrong happen when impersonating

int ret = Marshal.GetLastWin32Error();

Console.WriteLine("LogonUser failed with error code : {0}", ret);

throw new System.ComponentModel.Win32Exception(ret);

}

 

// Check if the impersonation ran fine (This double check the impersonation)

Assert.IsTrue(WindowsIdentity.GetCurrent().Name.Contains(userName), String.Format("Current identity is {0}. Impersonation was expecting {1}", WindowsIdentity.GetCurrent().Name, userName));

 

// Perform Test with the current Identity

//TODO: Add Code to test access

 

// Reset the Security Context

impersonatedUser.Undo();

 

// Check the identity.

Console.WriteLine("After Undo: " + WindowsIdentity.GetCurrent().Name);

 

// Free the tokens.

if (myToken != IntPtr.Zero)

Impersonalisator.CloseHandle(myToken);

}

 

Possible Optimizations

As I used those tests, I realized a way to implement tests more easily is to create one Test Class per user and create the impersonation context in the ClassInitialize() methods. It makes the code much more readable.

Before using this solution I have been looking for an Attribute to give the credentials i wanted to use for the test.  I  was thinking it would be nice to add that feature, but at the moment I m mitigated. I don't think Unit Test are a good place to test security yet in my case this was the best place to do it.

 

Sources:

MSDN : http://msdn.microsoft.com/en-us/library/system.security.securitycontext.aspx

Posted on Tuesday, June 30, 2009 11:25 AM UnitTest , TDD , Identity | Back to top


Comments on this post: Unit Test and Impersonation

# re: Unit Test and Impersonation
Requesting Gravatar...
Great write-up. I'd definitely want to integrate the initialization and cleanup of the impersonation in methods decorated with the TestInitialize and TestCleanup attributes. You could explore adding the context to the TestContext based on the TestName.
Left by Anthony Trudeau on Jul 06, 2009 11:49 PM

# re: Unit Test and Impersonation
Requesting Gravatar...
valid for impersonator SqlConnection Trusted connection ?
Left by pregunton on Dec 14, 2016 8:12 PM

Your comment:
 (will show your gravatar)


Copyright © Frederic Hautecoeur | Powered by: GeeksWithBlogs.net