Нужно разрешить редактировать только отдельные поля

IBX, FIBPlus, UIB, ADO, .Net и прочее-прочее-прочее, в общем все, что относится к созданию приложений, работающих с InterBase, Firebird и Yaffil - клиент-серверных, трехзвенных, консольных и т.п.

Модератор: kdv

Tokolist

Нужно разрешить редактировать только отдельные поля

Сообщение Tokolist » 05 сен 2006, 23:25

Здравствуйте эксперты!
Мне нужно разрешить некоторым пользователям редактировать только отдельные поля (разделять таблицу на несколько с связью «один к одному» не подходит, поскольку в будущем планируется разрешать или запрещать редактировать поля из той же таблицы, а пересоздавать таблицы проблематично). Использую TIBQuery в связке с TIBUpdateSQL. Например, разрешено редактировать только поле field3, а есть поля field1, field2, field3 и BLOB-поле notes. Но если в ModifySQL записать

Код: Выделить всё

update "table"
set
  "field1" = :"field1",
  "field2" = :"field2",
  "field3" = :"field3",
  "notes" = :notes
where
  "id" = :"OLD_id"
получим ошибку. Как решение пришло в голову динамически создавать запрос в событии BeforePost компонента TIBQuery. В запрос добавляются только поля, которые изменялись пользователем.

Код: Выделить всё

ibqQuery: TIBQuery;
ibusUpdateSQL: TIBUpdateSQL;

procedure TfrmForm.ibqQueryBeforePost(DataSet: TDataSet);

  function SQLGetUpdatedField(Field: TField; out SQL: string): boolean;
  begin
    Result := Field.NewValue <> Field.OldValue;
    if Result then
      SQL := Field.Origin + '=:' + Field.FieldName;
  end;

var
  TmpStr: string;
begin

  ibusUpdateSQL.ModifySQL.Clear;
  ibusUpdateSQL.ModifySQL.Add('UPDATE "table" SET ');

  if SQLGetUpdatedField(ibqQuery.FieldByName('field1'), TmpStr) then
    ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');

  if SQLGetUpdatedField(ibqQuery.FieldByName('field2'), TmpStr) then
    ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');

  if SQLGetUpdatedField(ibqQuery.FieldByName('field3'), TmpStr) then
    ibusUpdateSQL.ModifySQL.Add(TmpStr + ', ');


  ibusUpdateSQL.ModifySQL.Add('"notes" = :notes ');
  ibusUpdateSQL.ModifySQL.Add('WHERE "id" = :OLD_id');

end;
Вопросы:
1. Есть ли способ лучше?
2. В BLOB-поле проверка Field.NewValue <> Field.OldValue не работает (поэтому приходится разрешать его редактирование). Как тогда отследить изменялось ли оно?
3. Как можно оптимизировать приведенный код.

Заранее спасибо!

PS. Еще приходит мысль об использовании триггера, в котором проверяется имя пользователя. Но этот вариант тоже не совсем подходит.

stix-s
Заслуженный разработчик
Сообщения: 557
Зарегистрирован: 13 дек 2005, 11:52

Re: Нужно разрешить редактировать только отдельные поля

Сообщение stix-s » 06 сен 2006, 05:49

slv2 писал(а): 1. Есть ли способ лучше?
Раздать права на модификацию полей по пользователям (по ролям)
Использовать компоненты FIB+

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Re: Нужно разрешить редактировать только отдельные поля

Сообщение Merlin » 06 сен 2006, 12:52

stix-s писал(а):
slv2 писал(а): 1. Есть ли способ лучше?
Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
stix-s писал(а): Использовать компоненты FIB+
И што буит?

2 slv2 - В принципе твоя идея правильная (я, кстати, не уверен насчёт того, что IBX позволяет пересобирать модифай-запросы при открытом селектном, но чём чёрт не шутит, у меня он уж очень древний, может что там и сдвинулось с мёртвой точки). А конкретная реализация - ну она очень разной может быть и утверждать что в каком случае оптимальней - это занятие или для рискового или для очень неленивого, всё ж перепробовать надо. Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет. А может и чтение лучше отдельным запросом. Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.

stix-s
Заслуженный разработчик
Сообщения: 557
Зарегистрирован: 13 дек 2005, 11:52

Re: Нужно разрешить редактировать только отдельные поля

Сообщение stix-s » 06 сен 2006, 13:17

Merlin писал(а):
stix-s писал(а): Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Merlin писал(а):
stix-s писал(а): Использовать компоненты FIB+
И што буит?
ничего не будет, те поля которые нельзя модифицировать, останутся неизменными

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Re: Нужно разрешить редактировать только отдельные поля

Сообщение Merlin » 06 сен 2006, 13:49

stix-s писал(а):
Merlin писал(а):
stix-s писал(а): Раздать права на модификацию полей по пользователям (по ролям)
И што? Он это и сделал, после чего стал получать отлуп на препаре запроса.
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Merlin писал(а):
stix-s писал(а): Использовать компоненты FIB+
И што буит?
ничего не будет, те поля которые нельзя модифицировать, останутся неизменными
Значит, Бузз берёт на себя работу сервера по проверке прав, а потом ещё и молча выполняет не то, что прошено? Верится с трудом. Хотя,
коммерческий продукт, кто платит деньги, тот и заказывает музыку... Вот по причине наличия такого рода "фич" я на него так и не перебрался. Хотя в данном конкретном случае таки берут серьёзные сомнения о точности наблюдений за FIB+, уж больно некооректное поведение...

Tokolist

Сообщение Tokolist » 06 сен 2006, 21:45

(я, кстати, не уверен насчёт того, что IBX позволяет пересобирать модифай-запросы при открытом селектном, но чём чёрт не шутит, у меня он уж очень древний, может что там и сдвинулось с мёртвой точки).
Работает. Честное пионерское :) . (Delphi 5 Update 1, IBX 5.04)
А конкретная реализация - ну она очень разной может быть и утверждать что в каком случае оптимальней - это занятие или для рискового или для очень неленивого, всё ж перепробовать надо.
Как бы ты решил данную проблему?
Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет.
Ты имеешь ввиду использовать не DB-компонент, а простой контрол и при посте загружать данные из него в поле?
Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
В принципе подходит. Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Использовать компоненты FIB+
Хотелось бы все же ограничится использованием IBX.

Merlin
Динозавр IB/FB
Сообщения: 1502
Зарегистрирован: 27 окт 2004, 11:44

Сообщение Merlin » 06 сен 2006, 22:14

slv2 писал(а): Как бы ты решил данную проблему?
Данная проблема возникает главным образом при заточке интерфейса на редактирование в гридах, через edit-post. Я такой технологией почти не пользуюсь. При редактировании поднимается отдельная форма, запись рефрешится в её не db-aware контролы, сохранение результатов у меня крайне редко идёт только в одну таблицу и даже в одной транзакции. То есть выполняется несколько автономных TIBSQL или TIBQuery.ExecSQL, которые частенько собираются динамически по обстоятельствам. или вызываются TIBStoredProc. После чего в навигационном датасете рефреш. Ты делаешь примерно то же самое вокруг технологии Edit-Post, что тебе в твоей конкретной задаче удобнее - мне отсюда не видно :)
slv2 писал(а):
Насчёт блобов - выдели алгоритмически их редактирование в отдельную ветку, проще будет.
Ты имеешь ввиду использовать не DB-компонент, а простой контрол и при посте загружать данные из него в поле?
Я вообще с блобами почти не работаю, я тут слабый советчик. А если работаю, то в навигационном запросе их не тащу, а только при активизации формы редактирования, именно от этой записи, ну а там уже всё в моих руках, как хочу, так и верчу.
slv2 писал(а):
Или фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
В принципе подходит. Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Например, оставь в своей технологии редактирование обычных полей как есть, а блоб поднимай в отдельную форму по вызову функции редактирования именно его, с кнопками OK-Cancel, и сохраняй там отдельным запросом. Можно наверное озаботиться параноидальной проверкой содержимого блоба даже при нажатии OK, сравнить его с исходным, я такими делами не занимаюсь - свип ночью с мусором разберётся. Но вообще-то я, как уже говорил, насчёт блобов небольшой спец, у меня там только шаблоны отчётов.

Tokolist

Сообщение Tokolist » 07 сен 2006, 22:53

При редактировании поднимается отдельная форма, запись рефрешится в её не db-aware контролы, сохранение результатов у меня крайне редко идёт только в одну таблицу и даже в одной транзакции. То есть выполняется несколько автономных TIBSQL или TIBQuery.ExecSQL, которые частенько собираются динамически по обстоятельствам. или вызываются TIBStoredProc.
Круто :shock:
фиксипуй факт редактирования скажем в TAG-е поля на каком-нить его событии и потом поо нему ориентируйся при сохранении.
Ты был прав. Но борланд об этом уже позаботился ибо можно сделать проверку так
TBlobField(Field).Modified
А я ведь знал об этом свойстве (склероз блин :) )
В свое время пробовал с компонентом TpFIBDataSet - не ругался, просто втихую запрос модифицировал и все.
Скачал FIB Plus... Порился в исходниках. Оказалось он делает (помоему по умолчанию) почти такую же проверку как я на предмет изменения полей и динамически генерирует ModifySQL если установлено соответсвующее свойство.
Но работать все равно буду с IBX (все равно его не брошу потомушо он хороший :) ).
Короче написал я функцию

Код: Выделить всё

{
DataSet - датасет с которым работаем
FieldNames - список полей, разделенных двоеточием которые нужно проверить на предмет изменения (не всегда ведь нужно проверять все поля, например при джоине)
Quote - заключать названия параметров в кавычки?
}
function SQLGetUpdatedFields(DataSet: TDataSet; const FieldNames: string;
  Quote: boolean): string;
var
  i: Integer;
  TmpStr: string;
  Fields: TStrings;

  function GetUpdatedField(Field: TField; out SQL: string): boolean;
  begin
    if Field.IsBlob then
      Result := TBlobField(Field).Modified
    else
      Result := Field.NewValue <> Field.OldValue;

    if Result then
    begin
      if Quote then
        SQL := Field.Origin + '=:' + '"' + Field.FieldName + '"'
      else
        SQL := Field.Origin + '=:' + Field.FieldName;
    end;
  end;

begin
  Result := '';
  Fields := TStringList.Create;
  try
    Fields.Text := StringReplace(FieldNames, ':', #13, [rfReplaceAll]);
    for i := 0 to Fields.Count - 1 do
    begin
      if GetUpdatedField(DataSet.FieldByName(Fields[i]), TmpStr) then
      begin
        if Result <> '' then
          Result := Result + ', ' + #13;
        Result := Result + TmpStr;
      end;
    end;
  finally
    Fields.Free;
  end;

end;
Разрешаю использовать на свой страх и риск (GNU GPL и все такое)
Теперь как использовать

Код: Выделить всё

procedure TfrmForm.ibqQueryBeforePost(DataSet: TDataSet);
var
  UpdatedFields: string;
begin
  UpdatedFields := SQLGetUpdatedFields(ibqQuery,
    'field1:field2:field3:notes', true); //Получаем список измененных полей
  if UpdatedFields = '' then
  begin
    ibqQuery.Cancel; //Если пуст отменяем редактирование
    Abort;  //И аборт (в смысле прекращаем операцию)
  end;
    
  with ibusUpdateSQL.ModifySQL do
  begin
    Clear;
    Add('UPDATE "table" SET ');
    Text := Text + UpdatedFields;
    Add(' WHERE "id" = :"OLD_id"');
  end;
end;
Спасибо за интересную дискуссию!

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 07 сен 2006, 23:41

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

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Сообщение СанЕк » 15 сен 2006, 13:40

я такую проблему решал следующи образом

допустиместь таблица

Код: Выделить всё

CREATE TABLE ISDELIJA (
    SP3       INTEGER NOT NULL,
    NUMCLASS  INTEGER DEFAULT 0 NOT NULL,
    SIFR      VARCHAR(50) COLLATE PXW_CYRL,
    UNAME     VARCHAR(70) COLLATE PXW_CYRL,
    FULLNAME  VARCHAR(100) COLLATE PXW_CYRL,
    OPTCENA   NUMERIC(10,2) DEFAULT 0 NOT NULL,
    NCP       NUMERIC(10,2) DEFAULT 0 NOT NULL,
    CENA      NUMERIC(10,2) DEFAULT 0 NOT NULL
);
на нее есть триггер

Код: Выделить всё

CREATE TRIGGER ISDELIJA_BU0 FOR ISDELIJA
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE FieldName varchar(31);
begin
for SELECT a.RDB$FIELD_NAME FROM RDB$RELATION_FIELDS A Where (a.RDB$RELATION_NAME='ISDELIJA')
and(A.RDB$FIELD_NAME not in (SELECT B.RDB$FIELD_NAME FROM RDB$USER_PRIVILEGES B where
(B.RDB$PRIVILEGE='U')and(B.RDB$RELATION_NAME='ISDELIJA')And(B.RDB$USER=Current_user)and(not B.RDB$FIELD_NAME is Null)))
Into :fieldname do begin
If (:fieldname='SIFR') then new.SIFR=old.SIFR; else
If (:fieldname='UNAME') then new.UNAME=old.UNAME; else
If (:fieldname='FULLNAME') then new.FULLNAME=old.FULLNAME; else
If (:fieldname='OPTCENA') then new.OPTCENA=old.OPTCENA; else
If (:fieldname='NCP') then new.NCP=old.NCP; else
If (:fieldname='NUMCLASS') then new.NUMCLASS=old.NUMCLASS;else
If (:fieldname='OPTCENA') then new.OPTCENA=old.OPTCENA;
end
end

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

Tokolist

Сообщение Tokolist » 23 окт 2006, 22:13

Решил заглянуть на форум...
2 СанЕк
У меня Ваш способ почему-то не сработал. Мне кажется, что ошибка возникает еще на препаре...
Кроме того:
- будет ли Ваш способ работать с блоб-полями?
- здесь также необходимо добавить проверку по ролях.
- несколько трудновато написать такой триггер для каждой таблицы (вот бы генератор).
Я решил все-таки контролировать этот процесс на стороне клиента.
Мне также не подошел приведенный мной способ, поскольку необходимо везде обрабатывать событие BeforePost.
По этому было решено создать наследника TIBQuery, который бы делал всю работу автоматически. От меня требуется только кинуть его на форму и заполнить необходимые свойства. Вот уже некоторое время работаю с этим компонентом и ошибок замечено не было (но человек не идеален :) ).
А так как я НЕ жадный :D , то даю прямую ссылку на скачивание (с исходником).
http://pelesh.pochta.ru/pibqueryex.zip (14 КБ)
Буду благодарен за любые комментарии и критику.
Принцип работы следующий. Обрабатывается виртуальный метод DataEvent на предмет изменения полей. Все измененные поля запоминаются в TList и перед постом эти данные используются для генерации ModifySQL компонента TIBUpdateSQLW.

CyberMax
Заслуженный разработчик
Сообщения: 638
Зарегистрирован: 31 янв 2006, 09:05

Сообщение CyberMax » 24 окт 2006, 07:48

2 slv2. Зачем вы так операцию на глазах делаете? Список измененных полей получают вот так:

Код: Выделить всё

for I := 0 to Fields.Count - 1 do
  if Fields[I].NewValue <> Fields[I].OldValue then
    ...
Плюс: если поле изменили, а потом вернули старое значение, датасет будет считаться немодифицированным, а у вас - модифицированным.

Tokolist

Сообщение Tokolist » 24 окт 2006, 22:05

2 CyberMax
Зачем вы так операцию на глазах делаете? Список измененных полей получают вот так:

Код: Выделить всё

for I := 0 to Fields.Count - 1 do 
  if Fields[I].NewValue <> Fields[I].OldValue then 
    ...
Я знаю. Я приблизительно так и делал

Код: Выделить всё

  function SQLGetUpdatedField(Field: TField; out SQL: string): boolean; 
  begin 
    Result := Field.NewValue <> Field.OldValue; 
    if Result then 
      SQL := Field.Origin + '=:' + Field.FieldName; 
  end;
Проблема в том, что это не работает с блоб-полями.
Плюс: если поле изменили, а потом вернули старое значение, датасет будет считаться немодифицированным, а у вас - модифицированным.
Я тоже так сначала думал (что это плохо):
Но если пользователь начал редактирование, а потом передумал и вернул поле в начальное состояние, то метка все равно ведь останется.
Но, если используется, например TDBNavigator и пользователь начал редактирование (одного поля), то пост-кнопка делается доступной и будет такой даже если пользователь вернул поле в начальное состояние (датасет переходит в состояние Edit).
Теперь предположим, что пользователь нажимает эту кнопку (или просто переходит к следующей записи).
Но ведь проверка покажет что поле не изменялось. Мы получаем не правильно сгенерированный запрос и соответственно ошибку.
Я это обходил так

Код: Выделить всё

  if UpdatedFields = '' then 
  begin 
    ibqQuery.Cancel; //Если пуст отменяем редактирование 
    Abort;  //И аборт (в смысле прекращаем операцию) 
  end;
Но как оказалось работает такое не совсем корректно.

А так мы получаем список полей которые редактировались вообще.
И это правильно ибо нечего пользователем редактировать поля которые редактировать им не разрешено. Кроме того, с блоб-полями никаких проблем.

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 25 окт 2006, 14:25

slv2 писал(а):А так мы получаем список полей которые редактировались вообще.
И это правильно ибо нечего пользователем редактировать поля которые редактировать им не разрешено.
Чушь, по-моему. Сначала открываем всё, а потом начинаем собирать статистику, куда пользователь полез, куда хотел полезть, куда по ошибке залез...
Может, поля недоступные для конкретного пользователя, делать ReadOnly, и не проверять "куда он хотел, но не полез"?

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Сообщение СанЕк » 25 окт 2006, 15:39

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

Tokolist

Сообщение Tokolist » 25 окт 2006, 21:39

2 WildSery
Чушь, по-моему. Сначала открываем всё, а потом начинаем собирать статистику, куда пользователь полез, куда хотел полезть, куда по ошибке залез...
Пользователь, который полез не туда, получит отлуп и уже больше туда не полезет (типа не влезай - убьет :) ).
Может, поля недоступные для конкретного пользователя, делать ReadOnly, и не проверять "куда он хотел, но не полез"?
Поверьте, я над этим вопросом думал не один час...
...Разве что триггер, предложенный СанЕк, переделать в запрос, чуточку модифицировать и добавить проверку по ролях. Потом при каждом открытии датасета через такой запрос получать список доступных полей и соответственно выставлять CanModify. Но зачем?
А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?

WildSery
Заслуженный разработчик
Сообщения: 1738
Зарегистрирован: 05 июн 2006, 16:19

Сообщение WildSery » 26 окт 2006, 11:55

slv2 писал(а):А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?
Тогда надо будет поменять только этот запрос.

Karp
Сообщения: 41
Зарегистрирован: 30 апр 2005, 16:30

Сообщение Karp » 26 окт 2006, 13:53

slv2 писал(а): Поверьте, я над этим вопросом думал не один час...
А если в следующих версиях сервера чего-нибудь изменится в плане хранения прав доступа?
Вот простое решение:
1) как и говорил Merlin редактировать не в гриде, а в отдельной форме;
2) завести табличку с правами для юзеров;
3) если есть права на редактирование поля, то соответствующий контрол доступен для ввода/изменения, если нет - то только просмотр значения.

права давать в виде набора битов, а на клиенте их проверять и делать доступными или недоступными нужные кнопки, контролы "и что у них есть ещё там" (с)
:)

Tokolist

Сообщение Tokolist » 27 окт 2006, 00:11

WildSery писал(а):Тогда надо будет поменять только этот запрос.
Согласен.
Karp писал(а):2) завести табличку с правами для юзеров;
3) если есть права на редактирование поля, то соответствующий контрол доступен для ввода/изменения, если нет - то только просмотр значения.

права давать в виде набора битов, а на клиенте их проверять и делать доступными или недоступными нужные кнопки, контролы "и что у них есть ещё там" (с)
Если я вас правильно понял то...
Плохо это с точки зрения безопасности. Всегда найдется продвинутый пользователь который вместо ввода данных наберет что-то типа UPDATE ... SET ... Сам когда-то обходил ограничения клиента таким образом. Но, слава богу, таких продвинутых пользователей у меня нет.

Или вы предлагаете раздавать гранты и еще использовать дополнительно табличку с правами. Тогда это накладно.

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

Хотя идея с доступностью/недоступностью мне нравится. Будет время - поработаю над этим.

P.S. Я использую не грид, а едиты. (хотя какая разница)

Karp
Сообщения: 41
Зарегистрирован: 30 апр 2005, 16:30

Сообщение Karp » 27 окт 2006, 10:49

slv2 писал(а):Плохо это с точки зрения безопасности. Всегда найдется продвинутый пользователь который вместо ввода данных наберет что-то типа UPDATE ... SET ...
а что, у вас юзера сами запросы пишут? оригинально
они и знать не должны про это, а если есть кто-то продвинутый, то у нас он не сможет этого сделать, т.к. не знает имени сервера и пути к базе (это для особо шибких - которые знают про эксперт или др. тулзы)
Или вы предлагаете раздавать гранты и еще использовать дополнительно табличку с правами. Тогда это накладно.
нет, гранты ролям на таблицы, а не на поля.
по битовой маске (считав права из базы) на клиенте проверять прва на поля - и enabled=ture на разрешённые поля

Хотя я считаю права на поля избыточностью
у нас каждое приложение имеет определённый набор раздаваемых юзерам прав, например: -ведение справочников; -редактирование записи; -доступ к отчётам (к тем, каие нужны или ко всем) ну и т.п.

и есть, например, права на изменение статуса документа - это из меню или по кнопульке - т.е. доступ только к одному полю

а при редактировании у нас и так есть часть полей, которые нельзя менять никому
:D

Ответить