I wrote a handy little class called Impersonator a while ago to assist me in doing Windows impersonation. There are dozens of examples of this type of thing on the net. However, none of them were as "handy" as I prefer. By handy, I mean idiot-proof, of course. I like the IDisposable pattern a lot (I use the word pattern here loosely). IDisposable is usually meant to help idiots like me avoid mistakes. And in the case of changing the user identity on the running thread, forgetting to revert to the initial identity could be catastrophic. When combined with C#'s using statement, I get an extra measure of safety from forgetfulness or from rogue exceptions that might be thrown inside the impersonating code.
For the uninitiated, C#'s using statement makes IDisposable really hum by encapsulating the statement's code in a try/finally block where the IDisposable.Dispose method is called automatically and reliably. With my Impersonator class, you can now write code like this:
using (new Impersonator("domain", "username", "password"))
{
// do whatever you want in here as domain\username
// the security context will automatically revert on exit
// when IDisposable.Dispose is called on the Impersonator
}
Pretty simple, huh? The code compiled by the C# compiler would actually look something like this:
Impersonator X = null;
try
{
X = new Impersonator("domain", "username", "password");
// do whatever you want in here as domain\username
// the security context will automatically revert on exit
// because of the Dispose call in the finally block
}
finally
{
if (X != null)
X.Dispose();
}
Now, if IDisposable were a real design pattern, there might be better compiler support. Wouldn't it be great that for classes implementing IDisposable, they could NOT be instantiated outside of a using statement context? That might be a bit restrictive in some cases but think about how fool-proof that would be. If you were required to instantiate IDisposable types within a using statement, you could guarantee that a type, which could be doing radical things like changing the identity on the running thread, would always clean up after itself. Alas, the C# team at Microsoft has been ignoring my plea for help.
Here's the code for the Impersonator class for your enjoyment. Let me know if you like it. I have many other gems like this one lying around.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace gotnet.Security
{
/// <summary>
/// Impersonate a local or domain user. This class uses the IDisposable
/// pattern to automatically revert to the caller's original security
/// context safely. When instantiated from within C# code, this class
/// should always be constructed in a using statement to ensure that the
/// disposal routine runs. If you are not using VB.NET, you should call
/// the <see cref="Dispose"/> method in a Finally block.
/// </summary>
/// <example>
/// <code>
/// // enter the following block of code as user Fortunato
///
/// using (new Impersonator("MyDomain", "Amontillado", "pAs5w0Rd"))
/// {
/// // the running thread identity is now that of user Amontillado
/// // execute whatever you like here as the user Amontillado
/// // buy some bricks, some mortar, some good wine, etc.
/// }
///
/// // at this point, the thread identity has reverted to user Fortunato
/// </code>
/// </example>
public class Impersonator : IDisposable
{
#region // DllImports
[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 = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
#endregion // DllImports
private readonly IntPtr tokenHandle = new IntPtr(0);
private readonly WindowsImpersonationContext impersonatedUser;
/// <summary>
/// Impersonate a user. When instantiated from within C# code, this
/// class should always be constructed in a using statement to ensure
/// that the disposal routine runs. If you are not using VB.NET, you
/// should call the <see cref="Dispose"/> method in a Finally block.
/// </summary>
/// <param name="domainName">
/// The domain to authenticate against. May be a machine name.
/// </param>
/// <param name="userName">
/// The user name to authenticate with.
/// </param>
/// <param name="password">
/// The password to authenticate with.
/// </param>
/// <exception cref="Win32Exception">
/// The LogonUser operation failed.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// Windows returned the Windows NT status code STATUS_ACCESS_DENIED.
/// </exception>
/// <exception cref="OutOfMemoryException">
/// There is insufficient memory available.
/// </exception>
/// <exception cref="System.Security.SecurityException">
/// The caller does not have the correct permissions.
/// </exception>
public Impersonator(string domainName, string userName,
string password)
{
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
tokenHandle = IntPtr.Zero;
if (!LogonUser(userName, domainName, password,
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
ref tokenHandle))
{
int ret = Marshal.GetLastWin32Error();
throw new Win32Exception(ret);
}
WindowsIdentity newId = new WindowsIdentity(tokenHandle);
impersonatedUser = newId.Impersonate();
}
/// <summary>
/// Cleanup by reverting the user identity and closing any previously
/// obtained handles. This method will be called automatically if you
/// construct the Impersonator object from within a C# using statement.
/// If you are using VB.NET, be sure to call Dispose from within a
/// Finally block to make sure it happens. Failure to do so may leave
/// the running thread in an unusable state.
/// </summary>
/// <exception cref="System.Security.SecurityException">
/// An attempt is made to use this method for any purpose other than
/// to revert identity to self.
/// </exception>
public void Dispose()
{
if (impersonatedUser != null)
{
impersonatedUser.Undo();
impersonatedUser.Dispose();
}
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
}
}
}