Постановка задачи
Представим следующую ситуацию, есть каталог A, к которому имеет доступ только пользователь userA, есть каталог B, соответственно доступ к нему имеет только пользователь userB, требуется скопировать файлы из каталога A в каталог B, учитывая что программа может быть запущена пользователями userC, userD, userF,… и т.д.
Набросок решения
В качестве одного из способов решения можно использовать следующую схему:
программа запускается от имени любого пользователя, затем авторизуется под пользователем userA, читает файл в память, возвращается к начальному пользователю, авторизуется под пользователем userB, пишет файл в конечный каталог, возвращается к начальному пользователю. При больших размерах файлов можно придумать разбиение на блоки, но это уже не относится к данной задаче.
Решение
Понадобится три несложных класса(на самом деле достаточно и первого).
[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class MySafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private MySafeTokenHandle()
: base(true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
override protected bool ReleaseHandle()
{
return NativeMethods.CloseHandle(handle);
}
}
[SuppressUnmanagedCodeSecurity()]
internal static class NativeMethods
{
#region P/Invoke
internal const int LOGON32_LOGON_INTERACTIVE = unchecked((int)2);
internal const int LOGON32_PROVIDER_DEFAULT = unchecked((int)0);
[DllImport("advapi32.dll")]
internal static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
out MySafeTokenHandle phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int DuplicateToken(MySafeTokenHandle hToken,
int impersonationLevel,
out MySafeTokenHandle hNewToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern bool CloseHandle(IntPtr handle);
#endregion
}
/// <summary>
/// Класс реализует перевод исполнения программы на права указанного пользователя
/// </summary>
[SecurityPermissionAttribute(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
public class Impersonation : IDisposable
{
WindowsImpersonationContext impersonationContext;
/// <summary>
/// Вход от имени указанного пользователя
/// </summary>
/// <param name="userName">Имя пользователя</param>
/// <param name="domain">Домен</param>
/// <param name="password">Пароль</param>
/// <returns>false - если не удалось выполнить вход по указанным данным</returns>
public void ImpersonateUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
MySafeTokenHandle token;
MySafeTokenHandle tokenDuplicate;
if (NativeMethods.LogonUserA(userName, domain, password, NativeMethods.LOGON32_LOGON_INTERACTIVE, NativeMethods.LOGON32_PROVIDER_DEFAULT, out token) != 0)
{
using (token)
{
if (NativeMethods.DuplicateToken(token, 2, out tokenDuplicate) != 0)
{
using (tokenDuplicate)
{
if (!tokenDuplicate.IsInvalid)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate.DangerousGetHandle());
impersonationContext = tempWindowsIdentity.Impersonate();
return;
}
}
}
}
}
else
throw new Exception("LogonUser failed: " + Marshal.GetLastWin32Error().ToString());
}
/// <summary>
/// При освобождении рессурсов вернем предыдущего пользователя
/// </summary>
public void Dispose()
{
impersonationContext.Undo();
GC.SuppressFinalize(this);
}
}
Второй класс отвечающий за исполнение кода:
/// <summary>
/// Реализует исполнение указанного кода от имени конкретного пользователя
/// </summary>
public class ImpersonationExecutor
{
public string USR { get; set; }
public string DOMAIN { get; set; }
public string PWD { get; set; }
/// <summary>
/// Конструктор
/// </summary>
/// <param name="userName">Имя пользователя</param>
/// <param name="domainName">Домен</param>
/// <param name="password">Пароль</param>
public ImpersonationExecutor(string userName, string domainName, string password)
{
USR = userName;
DOMAIN = domainName;
PWD = password;
}
/// <summary>
/// Исполнение кода
/// </summary>
/// <typeparam name="T">Тип передаваемого аргумента</typeparam>
/// <param name="action">Делегат</param>
/// <param name="arg">Аргумент</param>
public void ExecuteCode<T>(Action<T> action, T arg)
{
using (Impersonation user = new Impersonation())
{
user.ImpersonateUser(USR, DOMAIN, PWD);
action(arg);
}
}
}
И наконец, очень маленькая фабрика:
public class ImpersonationFactory
{
public static ImpersonationExecutor Create(string UserName, string Domain, string Password)
{
return new ImpersonationExecutor(UserName, Domain, Password);
}
}
Пример использования
В качестве примера решим задачу поставленную в постановке.
string from = "from";
string to = "to";
byte[] buffer = null;
ImpersonationFactory.Create("userA", "domainA", "passwordA").ExecuteCode<string>(delegate(string path) { buffer = File.ReadAllBytes(path); }, from);
ImpersonationFactory.Create("userB", "domainB", "passwordB").ExecuteCode<string>(delegate(string path) { File.WriteAllBytes(path, buffer); }, to);
Итог
Задача решена. Как вариант можно было использовать разные приложения(службы/сервисы) работающие под разными пользователями, но гибкость потеряется. Буду рад увидеть другие способы.
Поиском по хабру этого конкретного решения не видел.
UPD: исправлен класс Impersonation, по комментарию хабрапользователя bobermaniac