Поиск идентификаторов пользователя Windows в C # - PullRequest
4 голосов
/ 05 мая 2010

Context

Контекст в первую очередь - проблемы, которые я пытаюсь решить, приведены ниже.

[РЕДАКТИРОВАТЬ] Приложение в вопросах построено против .NET 3.5 SP1.

Один из наших клиентов задал вопрос о том, сколько времени нам потребуется, чтобы улучшить одно из наших приложений. Это приложение в настоящее время обеспечивает базовую аутентификацию пользователя в виде комбинаций имени пользователя и пароля. Этот клиент хотел бы, чтобы его сотрудники могли входить в систему, используя информацию о том, какая учетная запись пользователя Windows в данный момент вошла в систему во время запуска приложения.

Если я скажу им «нет», это не нарушит договор, но клиент может быть готов оплатить расходы на разработку, чтобы добавить эту функцию в приложение. Это стоит посмотреть.

Исходя из того, что я искал, кажется, что сохранение данных для входа пользователя в домен \ имя пользователя будет проблематичным, если эти данные будут изменены. Но SID пользователей Windows не должны меняться вообще. У меня сложилось впечатление, что было бы лучше записывать пользователей Windows по SID - не стесняйтесь, если я ошибаюсь.

У меня была скрипка с некоторыми вызовами Windows API. Изнутри C # захват SID текущего пользователя достаточно прост. Я уже могу взять SID любого пользователя и обработать его, используя LookupAccountSid, чтобы получить имя пользователя и домен для отображения. Для желающих мой код для этого находится в конце этого поста.

Однако это только вершина айсберга. Два вопроса ниже полностью вне моего опыта. Я не только не знаю, как их реализовать - я даже не знаю, как узнать, как их реализовать, или каковы подводные камни в различных системах.

Любая помощь, направленная в нужном направлении, будет очень признательна.

Выпуск 1)

Захватить локального пользователя во время выполнения не имеет смысла, если этому пользователю не был предоставлен доступ к приложению. Нам нужно будет добавить новый раздел в «консоль администратора» нашего приложения для добавления пользователей (или групп) Windows и назначения разрешений внутри приложения для этих пользователей.

Что-то вроде кнопки «Добавить учетную запись пользователя Windows», которая откроет всплывающее окно, которое позволит пользователю искать доступные учетные записи пользователей Windows в сети (а не только на локальной машине) для быть добавленным в список доступных логинов приложения.

Если в .NET или Windows уже есть компонент, который я мог бы сделать для меня в Шанхае, это сделало бы меня очень счастливым человеком.

Выпуск 2)

Я также хочу знать, как взять заданный SID пользователя Windows и сравнить его с заданной группой пользователей Windows (вероятно, взятой из базы данных). Я не уверен, как начать работу с этим, хотя я ожидаю, что это будет проще, чем вопрос выше.

Для заинтересованных

[STAThread]
static void Main(string[] args)
{
    MessageBox.Show(WindowsUserManager.GetAccountNameFromSID(WindowsIdentity.GetCurrent().User.Value));
    MessageBox.Show(WindowsUserManager.GetAccountNameFromSID("S-1-5-21-57989841-842925246-1957994488-1003"));
}

public static class WindowsUserManager
{
    public static string GetAccountNameFromSID(string SID)
    {
        try
        {
            StringBuilder name = new StringBuilder();
            uint cchName = (uint)name.Capacity;
            StringBuilder referencedDomainName = new StringBuilder();
            uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
            WindowsUserManager.SID_NAME_USE sidUse;

            int err = (int)ESystemError.ERROR_SUCCESS;
            if (!WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse))
            {
                err = Marshal.GetLastWin32Error();
                if (err == (int)ESystemError.ERROR_INSUFFICIENT_BUFFER)
                {
                    name.EnsureCapacity((int)cchName);
                    referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);

                    err = WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse) ?
                        (int)ESystemError.ERROR_SUCCESS :
                        Marshal.GetLastWin32Error();
                }
            }

            if (err != (int)ESystemError.ERROR_SUCCESS)
                throw new ApplicationException(String.Format("Could not retrieve acount name from SID. {0}", SystemExceptionManager.GetDescription(err)));

            return String.Format(@"{0}\{1}", referencedDomainName.ToString(), name.ToString());
        }
        catch (Exception ex)
        {
            if (ex is ApplicationException)
                throw ex;

            throw new ApplicationException("Could not retrieve acount name from SID", ex);
        }
    }

    private enum SID_NAME_USE
    {
        SidTypeUser = 1,
        SidTypeGroup,
        SidTypeDomain,
        SidTypeAlias,
        SidTypeWellKnownGroup,
        SidTypeDeletedAccount,
        SidTypeInvalid,
        SidTypeUnknown,
        SidTypeComputer
    }

    [DllImport("advapi32.dll", EntryPoint = "GetLengthSid", CharSet = CharSet.Auto)]
    private static extern int GetLengthSid(IntPtr pSID);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool ConvertStringSidToSid(
                string StringSid,
                out IntPtr ptrSid);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool LookupAccountSid(
      string lpSystemName,
      [MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
      StringBuilder lpName,
      ref uint cchName,
      StringBuilder ReferencedDomainName,
      ref uint cchReferencedDomainName,
      out SID_NAME_USE peUse);

    private static bool LookupAccountSid(
        string lpSystemName,
        string stringSid,
        StringBuilder lpName,
        ref uint cchName,
        StringBuilder ReferencedDomainName,
        ref uint cchReferencedDomainName,
        out SID_NAME_USE peUse)
    {
        byte[] SID = null;
        IntPtr SID_ptr = IntPtr.Zero;
        try
        {
            WindowsUserManager.ConvertStringSidToSid(stringSid, out SID_ptr);

            int err = SID_ptr == IntPtr.Zero ? Marshal.GetLastWin32Error() : (int)ESystemError.ERROR_SUCCESS;

            if (SID_ptr == IntPtr.Zero ||
                err != (int)ESystemError.ERROR_SUCCESS)
                throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}", stringSid, SystemExceptionManager.GetDescription(err)));

            int size = (int)GetLengthSid(SID_ptr);
            SID = new byte[size];

            Marshal.Copy(SID_ptr, SID, 0, size);
        }
        catch (Exception ex)
        {
            if (ex is ApplicationException)
                throw ex;

            throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}.", stringSid, ex.Message), ex);
        }
        finally
        {
            // Always want to release the SID_ptr (if it exists) to avoid memory leaks.
            if (SID_ptr != IntPtr.Zero)
                Marshal.FreeHGlobal(SID_ptr);
        }

        return WindowsUserManager.LookupAccountSid(lpSystemName, SID, lpName, ref cchName, ReferencedDomainName, ref cchReferencedDomainName, out peUse);
    }
}

1 Ответ

3 голосов
/ 05 мая 2010

Если вы используете версию 3.5 фреймворка, вы действительно хотите изучить System.DirectoryServices.AccountManagement . Я использовал его раньше для поиска учетных записей AD, и с ним гораздо проще иметь дело, чем с написанием собственного класса. Это также решит ваш вопрос №2. У меня нет кода под рукой, но если он вам нужен, я всегда могу его найти.

...