Постановка задачи
Представим следующую ситуацию, есть каталог 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
