Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Запись файла "от обычного пользователя"


Автор: kami 5.12.2013, 16:15
Доброго времени суток, уважаемые!

Возможно, я решил пойти не в том направлении для решения проблемы, посему - лучше опишу, что случилось:

Есть приложение, которое хранит... пусть будет "текущие настройки" в файле на C:\ProgramData\AppName\options.bin и по необходимости перезаписывает его.

Всё идет хорошо до тех пор, пока пользователь не запустит приложение (имеется ввиду первый запуск) "от администратора". При сохранении настроек "из-под админа" для обычных пользователей права на файл автоматом выставляются в ReadOnly, соответственно - все последующие запуски от обычных пользователей настройки не сохраняются и приложение вылетает.


Код простой (в этом примере поленился "правильно" получить путь CSIDL_COMMON_APPDATA):
Код

procedure TForm4.Form4OnCreate(Sender: TObject);
var
  FileName: string;
  FS: TFileStream;
begin
  FileName:='c:\ProgramData\SomeApp\options.bin';
  ForceDirectories(ExtractFileDir(FileName));
  FS:=TFileStream.Create(FileName, fmCreate);
  FS.Free;
end;

Порядок использования:
После компиляции первый запуск в обязательном порядке провести с повышением прав (т.е. не из-под IDE, если она не "от админа")
Все последующие запуски от обычного пользователя - сразу Access Denied.

Как быть?

P.S. Целевая ОС = Win7

Автор: Alexeis 5.12.2013, 17:43
Цитата(kami @  5.12.2013,  17:15 Найти цитируемый пост)
Есть приложение, которое хранит... пусть будет "текущие настройки" в файле на C:\ProgramData\AppName\options.bin и по необходимости перезаписывает его.

  Я думаю что проблема именно в этом, что настройки хранятся в папке с программой. Если их хранить в поддиректории APPDATA, они будут разные для всех пользователей. Насколько я помню есть еще директория C:\Users\All Users , в том случае если настройки должны быть одинаковыми для всех пользователей. Винда начиная с висты своеобразно работает с директорией C:\ProgramData\ ... Она считает что туда должен писать только инсталятор, который запущен с админскими правами, а программе туда соваться не к чему. Вероятно, как раз такая ситуация и воспроизвелась. Винда рассматривала запуск программы с админскими правами как инсталятор. 

Автор: kami 5.12.2013, 17:47
Цитата(Alexeis @  5.12.2013,  17:43 Найти цитируемый пост)
 Я думаю что проблема именно в этом, что настройки хранятся в папке с программой. 

Нет, это не папка с программой, т.е. не Program Files.
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762494(v=vs.85).aspx:
The file system directory that contains application data for all users. A typical path is C:\Documents and Settings\All Users\Application Data. This folder is used for application data that is not user specific. For example, an application can store a spell-check dictionary, a database of clip art, or a log file in the CSIDL_COMMON_APPDATA folder. This information will not roam and is available to anyone using the computer.

Добавлено через 1 минуту и 41 секунду
Проблема не в том, что я не могу вообще записать в файл. Если первый запуск приложения проходит без UAC, то всё нормально. Иначе - атрибуты безопасности блокируют обычным пользователям запись в этот файл.

Автор: Alexeis 5.12.2013, 18:06
Да, действительно, сейчас C:\Users\All Users является ярлыком на C:\ProgramData\ , значит по идее все верно делается. А если при установке программы добавить полные права группе "Пользователи"? Можно попробовать сначала сделать такое руками. После чего зайти с админскими правами, а затем с правами пользователя? 
  Ситуация очень похожа на то что винда реализует какую-то правильную логику с назначением прав, но непонятно какую.

Автор: Akella 7.12.2013, 12:35
Цитата(kami @  5.12.2013,  16:15 Найти цитируемый пост)
 первый запуск) "от администратора". 


После записи настроек, посмотри какие Windows выставляет права на файл....

Добавлено через 2 минуты и 8 секунд
Я думаю, что в первую очередь нужно создавать папку  C:\ProgramData\AppName с правами обычного пользователя. Чтобы Windows правильно установила права на запись.

Автор: kami 7.12.2013, 19:18
Цитата(Alexeis @  5.12.2013,  18:06 Найти цитируемый пост)
А если при установке программы добавить полные права группе "Пользователи"?

Да, если в явном виде выставить "полный доступ" к папке для простых смертных - то он наследуется и проблем в дальнейшем не наблюдается.

Цитата(Akella @  7.12.2013,  12:35 Найти цитируемый пост)
После записи настроек, посмотри какие Windows выставляет права на файл....

разрешения на запись для "Пользователей" хранятся в "Особых разрешениях", выставлено: "создание файлов/запись данных", "создание папок/дозапись данных", "запись атрибутов", "запись дополнительных атрибутов". Кстати, вот тут тоже засада - при создании файла "от обычного пользователя" права на изменение выставляются только ему, а остальным - ReadOnly.
Цитата(Akella @  7.12.2013,  12:35 Найти цитируемый пост)
Я думаю, что в первую очередь нужно создавать папку  C:\ProgramData\AppName с правами обычного пользователя. Чтобы Windows правильно установила права на запись.

не помогает. Права на файл, создаваемый "от админа" не наследуют разрешения от папки.

Автор: kami 7.12.2013, 19:48
А есть ли возможность принудительно выставить доступ к создаваемому файлу "без ограничений"?

Пробовал так:
Код

var
  FileHandle: THandle;
  sa: TSecurityAttributes;
  sd: TSecurityDescriptor;
begin
  InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
  SetSecurityDescriptorDACL(@sd, True, nil, True);

  sa.lpSecurityDescriptor := @sd;
  sa.nLength := SizeOf(TSecurityAttributes);
  sa.bInheritHandle := False;
  ...
  FileHandle:=CreateFile(@FileName[1], FILE_GENERIC_READ or FILE_GENERIC_WRITE, 0, @sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);


http://msdn.microsoft.com/en-us/library/windows/desktop/aa379583(v=vs.85).aspx написано:
Цитата

pDacl [in, optional]
...If this parameter is NULL, a NULL DACL is assigned to the security descriptor, which allows all access to the object. The DACL is referenced by, not copied into, the security descriptor.

Думал, поможет, ан нет - ничего не меняется...

Автор: Akella 9.12.2013, 12:33
Тут дело не в Delphi, а в самой ОС.
Так что нужно в другую папку писать или задать вопрос в соответствующем разделе по ОС.

Автор: kami 9.12.2013, 13:31
Цитата(Akella @  9.12.2013,  12:33 Найти цитируемый пост)
Так что нужно в другую папку писать

В какую, если эта папка как раз предназначена для хранения опций/настроек, независимых от залогиненного пользователя?


Цитата(Akella @  9.12.2013,  12:33 Найти цитируемый пост)
или задать вопрос в соответствующем разделе по ОС.

Не вижу причины писать в раздел MS Windows. Никто ради установки приложения не будет лезть в скрытую ProgramData, искать там нужную подпапку и править вручную права доступа. Тем более - если клиентских машин не один десяток.
Нужен именно программный механизм полного доступа к файлу, вне зависимости от залогиненного пользователя.

Автор: Frees 9.12.2013, 14:48
Цитата(Akella @  9.12.2013,  15:33 Найти цитируемый пост)
Так что нужно в другую папку писать или задать вопрос в соответствующем разделе по ОС.

Дело не в папке, а в том что владелец файла администратор и пользователь не может править файл админа.

Была такая же проблема, только в InnoSetup, инсталятор запущен от админа и файл потом нельзя было исправить.

Решилось установкой прав для всех польлзователей на папку в ProgramData

Код в InnoSetup
Код

[Dirs]  
Name: "{commonappdata}\CompanyName\ProductName"; Permissions: everyone-modify;  


Можно попробовать сделать что-то подобное из Delphi

Автор: Alexeis 9.12.2013, 15:03
  Вот вот, последнее решение мне кажется наиболее логичным. Только я бы все же давал разрешения не всем, а только группе пользователи. К примеру, сетевой доступ имеет права гостя, которые не выше чем у пользователя. Главное, раз директория предусматривает общий доступ всех пользователей, то значит выставление таких прав не нарушает идеологию.

Автор: kami 10.12.2013, 08:23
Господа, а если не затруднит - киньте в меня ссылкой, как убрать унаследованные разрешения и заменить их своими на папку/файл? А то мне чегой-то попадается только как это сделать мышкой...

Автор: Alexeis 10.12.2013, 09:56
  Ну нагуглить несложно. Например
 http://www.itlisting.org/5-windows/f2a3a2907d6e33b8.aspx - это для С++
Или для папки но на бейсике 
http://www.powerbasic.com/support/pbforums/showthread.php?t=7871
На делфи нашел только код получения привелегий
http://www.cyberforum.ru/cpp-builder/thread257643.html
Но это просто портированный фрагемент кода из MSDN.
  Сам такую задачу ни разу не решал.


Автор: kami 10.12.2013, 10:12
Цитата(Alexeis @  10.12.2013,  09:56 Найти цитируемый пост)
это для С++

Да без разницы - главное, что не на .Net, а то там бывают такие конструкции, к которым сложновасто подобрать WinAPI функции.

Буду пробовать.
Всем спасибо, тему можно считать закрытой, ибо направление получено.

Автор: Alexeis 10.12.2013, 10:17
  Если получиться, запуль сюда примерчик на делфи. Будем ссылаться в следующий раз на эту тему. 

Автор: kami 18.12.2013, 03:49
Цитата(Alexeis @  10.12.2013,  10:17 Найти цитируемый пост)
 Если получиться, запуль сюда примерчик на делфи.

Пожалуйста. Функция создает файл с правами: системе, админам, пользователям - полный доступ, "Всем" - только чтение.

Код

unit FileAllAccess;
interface

function CreateFileWithFullAccessSecurityAttributes(FileName: string): Boolean;

implementation
// код честно стырен:
// типы и константы - из jedi-api-lib, http://jedi-apilib.sourceforge.net
// алгоритм работы - MSDN, http://msdn.microsoft.com/en-us/library/windows/desktop/aa446595(v=vs.85).aspx
// ForceDirectoriesEx - подсмотрено (да и, чего греха таить, скопировано) у SysUtils
uses
  Windows, SysUtils, SysConst;
{$A+} { record alignment on 4 byte boundaries }
{$Z4} { enum size is 4 bytes } // эта супер-директива, которая изначально была
// спрятана в jedi-чего-то-там.inc, съела 2 часа моего времени - без нее
// возникает глобальный затык на SetEntriesInAclW (некорректный параметр)
const
  SECURITY_NT_AUTHORITY: SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0, 5));
  SECURITY_WORLD_SID_AUTHORITY: SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0, 1));

  SECURITY_LOCAL_SYSTEM_RID = $00000012;
  SECURITY_WORLD_RID = $00000000;
  SECURITY_BUILTIN_DOMAIN_RID = $00000020;
  DOMAIN_ALIAS_RID_ADMINS = $00000220;
  DOMAIN_ALIAS_RID_USERS = $00000221;

  NO_INHERITANCE = 0;

type
  MULTIPLE_TRUSTEE_OPERATION = (NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_IMPERSONATE);
  TRUSTEE_TYPE = (TRUSTEE_IS_UNKNOWN, TRUSTEE_IS_USER, TRUSTEE_IS_GROUP, TRUSTEE_IS_DOMAIN, TRUSTEE_IS_ALIAS, TRUSTEE_IS_WELL_KNOWN_GROUP,
    TRUSTEE_IS_DELETED, TRUSTEE_IS_INVALID, TRUSTEE_IS_COMPUTER);

  TRUSTEE_FORM = (TRUSTEE_IS_SID, TRUSTEE_IS_NAME, TRUSTEE_BAD_FORM, TRUSTEE_IS_OBJECTS_AND_SID, TRUSTEE_IS_OBJECTS_AND_NAME);

  PTRUSTEE_W = ^TRUSTEE_W;
  TRUSTEE_W = packed record
    pMultipleTrustee: PTRUSTEE_W;
    MultipleTrusteeOperation: MULTIPLE_TRUSTEE_OPERATION;
    TrusteeForm: TRUSTEE_FORM;
    TrusteeType: TRUSTEE_TYPE;
    ptstrName: LPWSTR;
  end;

  ACCESS_MODE = (NOT_USED_ACCESS, GRANT_ACCESS, SET_ACCESS, DENY_ACCESS, REVOKE_ACCESS, SET_AUDIT_SUCCESS, SET_AUDIT_FAILURE);

  PEXPLICIT_ACCESS_W = ^EXPLICIT_ACCESS_W;
  EXPLICIT_ACCESS_W = packed record
    grfAccessPermissions: DWORD;
    grfAccessMode: ACCESS_MODE;
    grfInheritance: DWORD;
    Trustee: TRUSTEE_W;
  end;

function SetEntriesInAclW(cCountOfExplicitEntries: ULONG; pListOfExplicitEntries: PEXPLICIT_ACCESS_W; OldAcl: PACL; var NewAcl: PACL): DWORD;
  stdcall; external 'advapi32.dll';

function ForceDirectoriesEx(Dir: string; const sa: TSecurityAttributes): Boolean;
var
  E: EInOutError;
begin
  Result := True;
  if Dir = '' then
    begin
      E := EInOutError.CreateRes(@SCannotCreateDir);
      E.ErrorCode := 3;
      raise E;
    end;
  Dir := ExcludeTrailingPathDelimiter(Dir);
  if (Length(Dir) < 3) or DirectoryExists(Dir) or (ExtractFilePath(Dir) = Dir) then
    Exit;
  Result := ForceDirectoriesEx(ExtractFilePath(Dir), sa) and CreateDirectory(PChar(Dir), @sa);
end;

function CreateFileX(FileName: string; const sa: TSecurityAttributes): Boolean;
var
  hFile: THandle;
begin
  hFile := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, @sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  Result := hFile <> INVALID_HANDLE_VALUE;
  if Result then
    CloseHandle(hFile);
end;

function CreateFileWithFullAccessSecurityAttributes(FileName: string): Boolean;
var
  res: Cardinal;

  pSIDSystem: PSID;
  pSIDAdmins: PSID;
  pSIDUsers: PSID;
  pSIDWorld: PSID;

  ACL: PACL;

  psd: PSecurityDescriptor;
  sa: TSecurityAttributes;

  ea: array [0 .. 3] of EXPLICIT_ACCESS_W;

  i: integer;
begin
  Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, pSIDSystem));
  try
    Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
        pSIDAdmins));
    try
      Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0,
          pSIDUsers));
      try
        Win32Check(AllocateAndInitializeSid(SECURITY_WORLD_SID_AUTHORITY, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, pSIDWorld));
        try
          FillChar(ea[0], SizeOf(EXPLICIT_ACCESS_W) * Length(ea), 0);
          i := 0;
          // "Всем" - только чтение, остальным - полный доступ.
          // если какой-то блок прав лишний - просто комментим его
          ea[i].grfAccessPermissions := GENERIC_ALL;
          ea[i].grfAccessMode := SET_ACCESS;
          ea[i].grfInheritance := NO_INHERITANCE;
          ea[i].Trustee.TrusteeForm := TRUSTEE_IS_SID;
          ea[i].Trustee.TrusteeType := TRUSTEE_IS_GROUP;
          ea[i].Trustee.ptstrName := pSIDSystem;
          Inc(i);

          ea[i].grfAccessPermissions := GENERIC_ALL;
          ea[i].grfAccessMode := SET_ACCESS;
          ea[i].grfInheritance := NO_INHERITANCE;
          ea[i].Trustee.TrusteeForm := TRUSTEE_IS_SID;
          ea[i].Trustee.TrusteeType := TRUSTEE_IS_GROUP;
          ea[i].Trustee.ptstrName := pSIDAdmins;
          Inc(i);

          ea[i].grfAccessPermissions := GENERIC_ALL;
          ea[i].grfAccessMode := SET_ACCESS;
          ea[i].grfInheritance := NO_INHERITANCE;
          ea[i].Trustee.TrusteeForm := TRUSTEE_IS_SID;
          ea[i].Trustee.TrusteeType := TRUSTEE_IS_GROUP;
          ea[i].Trustee.ptstrName := pSIDUsers;
          Inc(i);

          ea[i].grfAccessPermissions := GENERIC_READ;
          ea[i].grfAccessMode := SET_ACCESS;
          ea[i].grfInheritance := NO_INHERITANCE;
          ea[i].Trustee.TrusteeForm := TRUSTEE_IS_SID;
          ea[i].Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP;
          ea[i].Trustee.ptstrName := pSIDWorld;
          Inc(i);

          ACL := nil;
          res := SetEntriesInAclW(i, @ea, nil, ACL);
          if res <> ERROR_SUCCESS then
            RaiseLastOSError(res);
          try
            psd := AllocMem(SECURITY_DESCRIPTOR_MIN_LENGTH);
            if not Assigned(psd) then
              RaiseLastOSError;
            try
              if not InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION) then
                RaiseLastOSError;

              if not SetSecurityDescriptorDacl(psd, True, ACL, False) then
                RaiseLastOSError;

              sa.nLength := SizeOf(SECURITY_ATTRIBUTES);
              sa.lpSecurityDescriptor := psd;
              sa.bInheritHandle := False;

              // здесь и только здесь можно безопасно воспользоваться
              // сформированными правами доступа, а именно - sa.
              Result:=ForceDirectoriesEx(ExtractFileDir(FileName), sa);
              if Result then
                Result:=CreateFileX(FileName, sa);
              // а вот тут уже лучше не пробовать работать с sa.
            finally
              FreeMem(psd);
            end;
          finally
            LocalFree(Cardinal(ACL));
          end;
        finally
          FreeSid(pSIDWorld);
        end;
      finally
        FreeSid(pSIDUsers);
      end;
    finally
      FreeSid(pSIDAdmins);
    end;
  finally
    FreeSid(pSIDSystem);
  end;
end;
end.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)