I recently discussed the wrong way to impersonate credentials in .NET. All of the code examples I have encountered have introduced a fatal design flaw: restoring an impersonated principal when impersonating yet another principal as in ASP.NET with impersonate=true. The following illustrates correct code based on my current understanding.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace SoftwareIsHardwork.Samples
{
public class ImpersonationScope : IDisposable
{
#region Constructors/Destructors
public ImpersonationScope(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
{
IntPtr logonToken = IntPtr.Zero;
IntPtr logonTokenDuplicate = IntPtr.Zero;
Debug.WriteLine("Enter impersonation scope");
if ((object)userName == null)
throw new ArgumentNullException("userName");
if ((object)domainName == null)
throw new ArgumentNullException("domainName");
if ((object)password == null)
throw new ArgumentNullException("password");
if (userName == "")
throw new ArgumentOutOfRangeException("userName");
if (domainName == "")
throw new ArgumentOutOfRangeException("domainName");
if (password == "")
throw new ArgumentOutOfRangeException("password");
try
{
Debug.WriteLine("Incoming identity: " + WindowsIdentity.GetCurrent().Name);
this.processWindowsImpersonationContext = WindowsIdentity.Impersonate(IntPtr.Zero);
Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
//if (Win32NativeMethods.RevertToSelf())
{
if (LogonUser(userName,
domainName,
password,
(int)logonType,
(int)logonProvider,
ref logonToken) != 0)
{
if (DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
{
this.impersonatedWindowsIdentity = new WindowsIdentity(logonTokenDuplicate);
this.threadWindowsImpersonationContext = this.impersonatedWindowsIdentity.Impersonate();
Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
catch
{
this.Dispose();
throw;
}
finally
{
if (logonToken != IntPtr.Zero)
{
CloseHandle(logonToken);
logonToken = IntPtr.Zero;
}
if (logonTokenDuplicate != IntPtr.Zero)
{
CloseHandle(logonTokenDuplicate);
logonTokenDuplicate = IntPtr.Zero;
}
Debug.WriteLine("Leave constructor");
}
}
#endregion
#region Fields/Constants
private readonly WindowsIdentity impersonatedWindowsIdentity;
private readonly WindowsImpersonationContext processWindowsImpersonationContext;
private readonly WindowsImpersonationContext threadWindowsImpersonationContext;
private bool disposed;
#endregion
#region Methods/Operators
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(
string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
public void Dispose()
{
if (this.disposed)
return;
try
{
Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
if ((object)this.threadWindowsImpersonationContext != null)
{
this.threadWindowsImpersonationContext.Undo();
this.threadWindowsImpersonationContext.Dispose();
}
if ((object)this.impersonatedWindowsIdentity != null)
this.impersonatedWindowsIdentity.Dispose();
Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
if ((object)this.processWindowsImpersonationContext != null)
{
this.processWindowsImpersonationContext.Undo();
this.processWindowsImpersonationContext.Dispose();
}
Debug.WriteLine("Outgoing identity: " + WindowsIdentity.GetCurrent().Name);
}
finally
{
this.disposed = true;
GC.SuppressFinalize(this);
Debug.WriteLine("Leave impersonation scope");
}
}
#endregion
#region Classes/Structs/Interfaces/Enums/Delegates
public enum ImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
}
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
}
#endregion
}
}
Sample usage scenario:
using (scope = new ImpersonationScope(username, domain, password, ImpersonationScope.LogonType.LOGON32_LOGON_INTERACTIVE, ImpersonationScope.LogonProvider.LOGON32_PROVIDER_DEFAULT))
{
// code under impersonation
}
2 comments:
Nice... most of the sample out there don't even perform any clean up at all.
One question though:
Why did you comment out the if (Win32NativeMethods.RevertToSelf()) ?
RevertToSelf is not needed because when you impersonate principal IntPtr.Zero, it is essentially reverting to self but remembering who your were impersonating prior. (Think: linked list of impersonation contexts to ensure your return to where you came from.) It was more of a reminder, plus a throw back to the incorrect examples on the web.
Post a Comment