In my program I need to impersonate users on the domain network to perform various tasks. Obviously I can only be one user at a time, and there are multiple threads that would like to be either the same or a different user.
I thought it would be neat to generalize the solution, so if you know of a better way to impersonate multiple users that's great, but it's not what I'm looking for in this question. (Note, impersonation is not per-process, so you don't need this for impersonation and I'm not using it anymore)
Can anyone think of a cleaner way of doing it? I really like that I can wrap all accesses in a using
block, but I don't like that the underlying class has to implement IPretendDisposable
and structure it's Dispose method with an if
statement like that.
public interface IPretendDispose : IDisposable
{
Func<bool> DisposeHandler { get; set; }
}
public abstract class SharedDisposable<TSharedObject, TKey> where TSharedObject : IPretendDispose
{
protected abstract TSharedObject Create(TKey key);
private TSharedObject _currentSharedObject;
private TKey _currentKey;
private readonly ReaderWriterLockSlim _domainLockSlim = new ReaderWriterLockSlim();
public TSharedObject GetObject(TKey key)
{
var earlyReturn = false;
_domainLockSlim.EnterReadLock();
try
{
if (key.Equals(_currentKey))
{
earlyReturn = true;
return _currentSharedObject;
}
}
finally
{
if (!earlyReturn)
{
_domainLockSlim.ExitReadLock();
}
}
//We use this flag otherwise we won't have exited the write lock before we enter the read lock.
//It is ok to enter the read lock while we are still in the upgradeablereadlock. That's how you downgrade.
var success = false;
_domainLockSlim.EnterUpgradeableReadLock();
try
{
//We've waited for our chance to change the instance.
//First check if another waiting thread made the change for us.
//Like double-checked locking
if (key.Equals(_currentKey))
{
success = true;
return _currentSharedObject;
}
_domainLockSlim.EnterWriteLock();
try
{
if (_currentSharedObject != null)
{
_currentSharedObject.DisposeHandler = () => true;
_currentSharedObject.Dispose();
}
_currentKey = key;
_currentSharedObject = Create(key);
_currentSharedObject.DisposeHandler = () =>
{
_domainLockSlim.ExitReadLock();
return false;
};
success = true;
return _currentSharedObject;
}
finally
{
//The spot that needs to execute before
_domainLockSlim.ExitWriteLock();
}
}
finally
{
if (success)
{
_domainLockSlim.EnterReadLock();
}
_domainLockSlim.ExitUpgradeableReadLock();
}
}
}
public class DomainImpersonator : IPretendDispose
{
private readonly WindowsImpersonationContext _impersonationContext;
public DomainImpersonator(string userName, string domainName, string password)
{
var token = IntPtr.Zero;
var tokenDuplicate = IntPtr.Zero;
try
{
if (RevertToSelf()
&& LogonUser(userName, domainName, password, Logon32LogonInteractive, Logon32ProviderDefault, ref token) != 0
&& DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
var tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
_impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
}
}
public Func<bool> DisposeHandler { get; set; }
public void Dispose()
{
if (DisposeHandler == null || DisposeHandler())
{
_impersonationContext?.Undo();
}
}
[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 int DuplicateToken(
IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
private const int Logon32LogonInteractive = 2;
private const int Logon32ProviderDefault = 0;
}
public class SharedDomainImpersonator : SharedDisposable<DomainImpersonator, Tuple<string, string>>
{
//Assume this is set safely before-hand
public readonly Dictionary<Tuple<string, string>, string> DomainImpersonationProfiles = new Dictionary<Tuple<string, string>, string>();
protected override DomainImpersonator Create(Tuple<string, string> key)
{
string password;
if (!DomainImpersonationProfiles.TryGetValue(key, out password))
{
throw new Exception("This won't break the locking mechanism.");
}
return new DomainImpersonator(key.Item1, key.Item2, password);
}
}