Как стать автором
Обновить

Исполнение куска кода от имени конкретного пользователя

Время на прочтение4 мин
Количество просмотров2.9K

Постановка задачи



Представим следующую ситуацию, есть каталог 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
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+16
Комментарии11

Публикации

Истории

Работа

.NET разработчик
75 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн