Как автоматически скорректировать права на все существующие записи при изменении прав по умолчанию?

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

Представьте, что сдали заказчику проект, он поработал и вдруг осознаёт: права по умолчанию надо поменять. И поменял. Система легко кастомизируется, о настройках прав в ТС тонны отличных руководств. Но на уже созданные по «старым» настройкам записи, понятное дело, эти изменения не повлияли. И вот тогда заказчик может обратиться к разработчику с пожеланием сделать так, чтобы на уже созданные записи права были розданы точно так же, как он настроил.

К сожалению далеко не всегда известно, что именно менялось и для каких таблиц. Выяснить это можно, но время, время…. Требовалось такое решение, которое позволило бы быстро, одним щелчком раздать права на все существующие записи всех таблиц в точности так, как настроены права по умолчанию сейчас.

В предлагаемом скрипте происходит перебор всех администрируемых по записям таблиц и перенастройка прав согласно существующим правам по-умолчанию. Подразумевается, что таблицы прав называются по схеме ‘имя администрируемой таблицы + ’Right’.

[tbl_Service].[Code] + 'Right'

Права нарочно не перераздаются на записи в таблицах групп, так как по умолчанию права на группы имеют только авторы записей. Следовательно из выборки исключены все таблицы, оканчивающиеся на ‘Group’. Если такое ограничение не требуется – закомментируйте строку

NOT [tbl_Service].[Code] LIKE '%Group'

В выборке учитывается и тот момент, что [tbl_TableDefaultRight] может содержать различный «мусор» от таблиц, у которых снят признак администрирования.
Предлагаемый скрипт в его нынешнем виде генерирует строки insert и update, которые можно проанализировать прежде чем выполнять. Но если требуется сразу выполнить – снимите комментарий в обоих строках

EXECUTE sp_executesql @InsertQueryExecutable
....
EXECUTE sp_executesql @UpdateQueryExecutable

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

/* ---------------------------------------------
Установка прав на все администрируемые таблицы
        согласно текущим правам по умолчанию
==============================================*/

declare @Code nvarchar(250)
declare @AdminUnitID uniqueidentifier
declare @CanRead int
declare @CanWrite int
declare @CanDelete int
declare @CanChangeAccess int

declare @UpdateQueryExecutable nvarchar(4000)
declare @UpdateQuery nvarchar(4000)
SET @UpdateQuery =
'update [@CodeRight]
set  [CanRead] = @CanRead
        , [CanWrite] = @CanWrite
        , [CanDelete] = @CanDelete
        , [CanChangeAccess] = @CanChangeAccess
where [AdminUnitID] = '
'@AdminUnitID'''

declare @InsertQueryExecutable nvarchar(4000)
declare @InsertQuery nvarchar(4000)
SET @InsertQuery =
'insert into [@CodeRight]
(
        [RecordID]
        , [AdminUnitID]
        , [CanRead]
        , [CanWrite]
        , [CanDelete]
        , [CanChangeAccess]
)
select [ID]
        , '
'@AdminUnitID''
        , @CanRead
        , @CanWrite
        , @CanDelete
        , @CanChangeAccess
from [@Code]
where
not exists
(       select [a].[ID]
        from [@CodeRight] as [a]
        where [AdminUnitID] = '
'@AdminUnitID''
        and [a].[RecordID] = [@Code].[ID]
)'


declare DefaultRightCursor cursor FOR
        SELECT
                  [tbl_Service].[Code]
                , [tbl_TableDefaultRight].[SubjectAdminUnitID]
                , MAX([tbl_TableDefaultRight].[CanRead])
                , MAX([tbl_TableDefaultRight].[CanWrite])
                , MAX([tbl_TableDefaultRight].[CanDelete])
                , MAX([tbl_TableDefaultRight].[CanChangeAccess])
        FROM [tbl_TableDefaultRight]
        INNER JOIN [tbl_Service]
        ON
                [tbl_Service].[ID] = [tbl_TableDefaultRight].[TableServiceID]
        INNER JOIN [tbl_AdminUnit]
        ON
                [tbl_AdminUnit].[ID] = [tbl_TableDefaultRight].[SubjectAdminUnitID]
        WHERE
        --Не использовать для групп - у них права по-умолчанию имеет только автор
        NOT [tbl_Service].[Code] LIKE '%Group'
        AND
        --Учитывать только существующие таблицы прав
        --(иногда бывает "мусор" от таблиц, на которые раньше раздавались права, а теперь нет)
        EXISTS (
                SELECT * FROM
                INFORMATION_SCHEMA.TABLES
                WHERE INFORMATION_SCHEMA.TABLES.TABLE_NAME = [tbl_Service].[Code] + 'Right'
        )
        GROUP BY [tbl_Service].[Code], [tbl_TableDefaultRight].[SubjectAdminUnitID]
        ORDER BY [tbl_Service].[Code]

open DefaultRightCursor
fetch next FROM DefaultRightCursor INTO
  @Code
, @AdminUnitID
, @CanRead
, @CanWrite
, @CanDelete
, @CanChangeAccess
while @@fetch_status = 0
begin
        SET @InsertQueryExecutable = REPLACE(@InsertQuery, '@Code', @Code)
        SET @InsertQueryExecutable = REPLACE(@InsertQueryExecutable, '@AdminUnitID', @AdminUnitID)
        SET @InsertQueryExecutable = REPLACE(@InsertQueryExecutable, '@CanRead', @CanRead)     
        SET @InsertQueryExecutable = REPLACE(@InsertQueryExecutable, '@CanWrite', @CanWrite)   
        SET @InsertQueryExecutable = REPLACE(@InsertQueryExecutable, '@CanDelete', @CanDelete) 
        SET @InsertQueryExecutable = REPLACE(@InsertQueryExecutable, '@CanChangeAccess', @CanChangeAccess)     
        print @InsertQueryExecutable
        --EXECUTE sp_executesql @InsertQueryExecutable
        print '---------------------------------------'
        SET @UpdateQueryExecutable = REPLACE(@UpdateQuery, '@Code', @Code)
        SET @UpdateQueryExecutable = REPLACE(@UpdateQueryExecutable, '@AdminUnitID', @AdminUnitID)
        SET @UpdateQueryExecutable = REPLACE(@UpdateQueryExecutable, '@CanRead', @CanRead)     
        SET @UpdateQueryExecutable = REPLACE(@UpdateQueryExecutable, '@CanWrite', @CanWrite)   
        SET @UpdateQueryExecutable = REPLACE(@UpdateQueryExecutable, '@CanDelete', @CanDelete) 
        SET @UpdateQueryExecutable = REPLACE(@UpdateQueryExecutable, '@CanChangeAccess', @CanChangeAccess)     
        print @UpdateQueryExecutable
        --EXECUTE sp_executesql @UpdateQueryExecutable
        print '--===================================--'

fetch next FROM DefaultRightCursor INTO
  @Code
, @AdminUnitID
, @CanRead
, @CanWrite
, @CanDelete
, @CanChangeAccess
end
close DefaultRightCursor
deallocate DefaultRightCursor

Нравится

Поделиться

6 комментариев

Анатолий, у вас два существенных недочета:
1. Вы никак не анализируете, кто создал запись, на которую вы раздаете записи по умолчанию - через колонку RecordId, необходимо смотреть значение CreatedBy. Ведь это самое важно в них - права по умолчанию, могут существенно отличаться от того в какие роли входит текущий пользователь. Возможно конечно это было желание заказчика, но я не у видел в ТЗ этого пункта :) Или у заказчика права по умолчанию настраивались только на корневую роль?
2. Вы обновляете доступ по записям, которые возможно кто-то добавил сам - не через механизм прав по умолчанию. В таком случае, если это опять же было требование заказчика, то вы должны были удалить весь доступ на записи и просто выполнить вставку, новых записей по умолчанию, но с учетом п. 1

Здравствуйте, Александр!

Вы совершенно правы, обе проблемы присутствуют. В первой я действительно не отдавал себе отчёта, надо будет обязательно смотреть кто создал запись и, по крайней мере, в какие админгруппы входит создатель сейчас. Сделаю как в триггерах на вставку. К слову сказать - угадали, в конфигурации на которой был применён этот скрипт все права настраивались на корневую роль... Но это скорее исключение, чем правило, значит таким скриптом можно наломать дров :)

А вот о втором пункте я думал, но смирился с неизбежным. Я просто не представляю как отличить добавленные вручную записи от записей, созданных автоматически. Поэтому счёл что вручную добавляют именно то, чего по умолчанию нет - потому и не очищал все права по RecordID, а только обновлял и добавлял.

Анатолий, думаю во втором пункте намного проще удалить все записи с детали доступ и просто вставлять все согласно настроек доступа по умолчанию.

Выкладываю свой вариант перераздачи прав на записи согласно правам по умолчанию (может кому пригодиться и сэкономит время).

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

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

Сначала удаляются все права на записи для групп пользователей (см. деталь "Доступ" контрагента, счета, продажи и т.д.)
Далее добавляются права на все записи на группы пользователей согласно правам доступа по умолчанию (см. раздел "Администрирование" вкладку "Права доступа по умолчанию").

И вот сам SQL-код, в котором я использую транзакции для отката изменений в случае ошибки, а также вывожу какие таблицы были обработаны и информацию об ошибке, если таковая возникла.

Как и в примере из топика, нужно снять комментарии из строк с EXECUTE.

DECLARE @RightsTableCode nvarchar(100);
DECLARE @TableCode nvarchar(100);
 
DECLARE @DeleteQuery nvarchar(4000);
DECLARE @DeleteQueryExec nvarchar(4000);
 
DECLARE @InsertQuery nvarchar(max);
DECLARE @InsertQueryExec nvarchar(max);
 
DECLARE @ServiceTableID uniqueidentifier;
 
SET @DeleteQuery = 'DELETE FROM @RightsTableCode
	WHERE EXISTS (SELECT ID FROM tbl_AdminUnit AS AU WHERE AU.IsGroup = 1 AND AU.ID = @RightsTableCode.AdminUnitID)';
 
SET @InsertQuery = 'INSERT INTO @RightsTableCode(
    ID
    ,RecordID
    ,AdminUnitID
    ,CanRead
    ,CanWrite
    ,CanDelete
    ,CanChangeAccess)
  SELECT
    newid()
    ,I.ID
    ,D.SubjectAdminUnitID
    ,D.CanRead
    ,D.CanWrite
    ,D.CanDelete
    ,D.CanChangeAccess
  FROM (
    SELECT
      D.SubjectAdminUnitID
      ,MAX(D.CanRead) AS CanRead
      ,MAX(D.CanWrite) AS CanWrite
      ,MAX(D.CanDelete) AS CanDelete
      ,MAX(D.CanChangeAccess) AS CanChangeAccess
    FROM tbl_TableDefaultRight AS D
    LEFT OUTER JOIN tbl_AdminUnit AS AU ON AU.ID = D.SubjectAdminUnitID
    WHERE (D.TableServiceID = ''@ServiceTableID'')
		AND (AU.IsGroup = 1)
  GROUP BY D.SubjectAdminUnitID)
	AS D, @TableCode I';
 
DECLARE c_Something CURSOR FOR
	SELECT Code FROM tbl_Service
	WHERE ServiceTypeCode = 'Table' AND Code LIKE 'tbl_%Right' AND Code NOT LIKE 'tbl_%GroupRight';
 
OPEN c_Something;
FETCH NEXT FROM c_Something INTO @RightsTableCode
WHILE @@FETCH_STATUS =0
BEGIN
	PRINT @RightsTableCode;
	SET @TableCode = REPLACE(@RightsTableCode, 'Right', '');
	PRINT @TableCode;
 
	SET @ServiceTableID = (SELECT ID FROM tbl_Service WHERE Code = @TableCode AND ServiceTypeCode = N'Table');
 
	IF (@ServiceTableID IS NOT NULL) 
		BEGIN
		BEGIN TRAN
		BEGIN TRY
 
			-- delete group rights
			SET @DeleteQueryExec = REPLACE(@DeleteQuery, '@RightsTableCode', @RightsTableCode);
			PRINT @DeleteQueryExec;
			--EXECUTE sp_executesql @DeleteQueryExec;
 
			-- insert group rights
			SET @InsertQueryExec = REPLACE(@InsertQuery, '@RightsTableCode', @RightsTableCode);
			SET @InsertQueryExec = REPLACE(@InsertQueryExec, '@TableCode', @TableCode);
			SET @InsertQueryExec = REPLACE(@InsertQueryExec, '@ServiceTableID', @ServiceTableID);
			PRINT @InsertQueryExec;
			--EXECUTE sp_executesql @InsertQueryExec;
 
		COMMIT TRAN
		END TRY
		BEGIN CATCH
			SELECT 
				ERROR_NUMBER() AS ErrorNumber,
				ERROR_SEVERITY() AS ErrorSeverity,
				ERROR_STATE() as ErrorState,
				ERROR_PROCEDURE() as ErrorProcedure,
				ERROR_LINE() as ErrorLine,
				ERROR_MESSAGE() as ErrorMessage;
 
			ROLLBACK TRAN
		END CATCH 
		END;
 
    FETCH NEXT FROM c_Something INTO @RightsTableCode;
END
CLOSE c_Something
DEALLOCATE c_Something

Далее есть идея добавить действие в разделе "Администрирование", назвать его "Обновить/перераздать права на записи согласно правам по умолчанию". Перебор таблиц и записей вынести в Террасофт, чтобы иметь возможность отображать прогресс бар с двумя полосами:
первая - прогресс по таблицам (вывод названия таблицы и кол/общ_кол)
вторая - прогресс по записям текущей таблицы (вывод кол/общ_кол)

Возможно, еще добавить логирование для учета ошибок.
Такое действие позволит администратору Террасофт, который не знает SQL, получить удобный инструмент перераспределения прав с возможностью анализа прогресса и успешности выполнения.

Реализованная идея описана здесь.

Показать все комментарии

Возникла потребность раздавать на отчеты запреты сохранения, копирования

Возникла необходимость реализовать в рамках проекта отчеты. Часть отчетов должна быть сделана в Excel, часть - в FastReport, часть с помощью OLAP-ов. Однако столкнулись с необходимостью специфических прав доступа на данные отчеты: часть пользователей должна иметь право только на просмотр отчетов (ни копировать, ни сохранять на компьютер нельзя), а часть пользователей может делать с этими же отчетами все, что душа пожелает.
Есть ли идеи по реализации стоящей перед нами задачи? А также, как массово раздать такие права доступа на отчеты, невзирая формат реализации?

Нравится

4 комментария

Добрый день, Катерина.
Строго говоря, если пользователь увидел на экране своего рабочего компьютера некий отчёт, то дальнейшие попытки запретить ему что-то сделать с этим отчётом уже предпринимать поздно - пользователь легко может сделать снимок экрана, сохранить его в файл и затем использовать по своему усмотрению. Попытки вмешиваться в работу других приложений (Excel) также чреваты большим количеством трудностей, неудобств для пользователя и непредвиденных трудозатрат.
В данной ситуации наиболее рациональный путь - ограничивать доступность отчётов с помощью детали Доступ в разделе Отчеты. Только в случае, если пользователь не может построить отчёт, он не сможет сохранить результат, а иначе сможет и ничто ему не помешает. Для случаев с разными типами отчетов задача запретить сохранить построенный отчёт еще более сложная, т.к. пользователю ничто не помешает копировать текст из документов или делать снимки экрана.
Насчет массовой раздачи прав (для детали Доступ) в любом разделе можно выделить несколько записей и добавить права сразу для всех выделенных записей.

Дима, а в нашем случае как раз ни один пользователь не может сделать снимок экрана. Это запрещено, я не уточнила, что клиент работает на терминале.

Для отчетов FastReport можно теоретически блокировать кнопки сохранения и печати, а насчет отчетов Word и Excel вопрос открыт, как реализовать такое требование пока не представляю.

"Катерина Левченко" написал:ни один пользователь не может сделать снимок экрана

А как же старый добрый фотоаппарат?

"Гамора Дмитрий" написал:
если пользователь увидел на экране своего рабочего компьютера некий отчёт, то дальнейшие попытки запретить ему что-то сделать с этим отчётом уже предпринимать поздно

Согласен с Дмитрием.

Показать все комментарии

Всем доброго дня,

Наконец нашли время закончить модуль "Событийные права", и пока он регистрируется в каталоге расширений, опубликую список его возможностей. :)

Итак, модуль "Событийные права" предназначен для автоматической выдачи прав на запись в зависимости от значений ее полей.

Возможности:
- можно указать любое количество правил к любому датасету Террасофт;
- для проверки события (и соответственно срабатывания правила) можно указать:
  - фильтр по любым полям, выбираемым в датасете (можно указывать сложные условия);
  - фильтр по текущему пользователю.
- в рамках каждого правила можно указать комбинации различных действий по выдаче прав:
  - выдача прав группе
  - удаление прав у группы
  - удаление всех прав на запись
  - установка прав у существующих записей о правах
  - установить права по умолчанию (как при добавлении новой записи).
- можно указать, что действия по выдаче прав должны срабатывать не только для основной записи, но и для связанных записей (например, в правиле для "Продажи" можно указать, чтобы эти правила срабатывали также для клиента и контакта, указанного в продаже).

Скриншоты делать не стал, вместо этого записал небольшой видеообзор, скачать можно тут:
http://www.labitec.ru/files/EventRight20100504.rar

Если есть вопросы - задавайте :)

Нравится

Поделиться

17 комментариев

"Валерий Андрусик" написал:- можно указать, что действия по выдаче прав должны срабатывать не только для основной записи, но и для связанных записей (например, в правиле для "Продажи" можно указать, чтобы эти правила срабатывали также для клиента и контакта, указанного в продаже).

После видео стало непонятно зачем :)
Если отдел продаж услуг не видел этого клиента до момента продажи, то событийные права не помогут

Можно привести пример?

Я не совсем понял ситуацию... Попробую на примере, есть вот такая постановка:
- есть группы пользователей "Отдел продаж" и "Отдел производства". Первая группа имеет права по умолчанию на чтение в разделе Контрагенты (и соответственно видит всех контрагентов). Вторая группа по умолчанию контрагентов не видит;
- новую продажу регистрируют пользователи Отдела продаж, указывая клиента;
- при переводе Продажи в состояние "В работе" необходимо выдать права на чтение Продажи и на чтение записи о Клиенте группе "Отдел производства";
- при переводе "Продажи" в состояние "Выполнено" необходимо отобрать права у "Отдел производства" как на продажу, так и на клиента.
.
Такой сценарий реализуем в рамках модуля. Можем рассмотреть другой пример, если интересно :)
--------------------------------------------
Лабитек
Центр разработки приложений

Валерий, как вы решили проблему с тем, что у изменяющего запись пользователя может не быть достаточно прав для изменения доступа к записи?

Часть работы выполняет процедура на сервере (с правами владельца базы).
--------------------------------------------
Лабитек
Центр разработки приложений

Какую именно часть, если не секрет? Для корректной с точки зрения безопасности реализации описанной функциональности подобная процедура должна создаваться для каждого правила и при раздаче прав доступа дополнительно проверять указанное в правиле условие. Если же процедура просто принимает на входе идентификатор записи, идентификатор объекта администрирования и уровень прав доступа, то воспользовавшись ей можно получить полный доступ практически ко всем записям в администрируемой таблице.

Собственно секрета нет, так как нет "дыры" в безопасности :).
Коротко схема выглядит так:
- по правилам, которые вводит администратор Террасофт, к таблице создается триггер, содержащий логику назначения прав в зависимости от значения полей записи (я ошибочно написал, что используется хранимая процедура);
- так как пользователь может запустить эту логику, только сохраняя запись, и при этом сработают только правила, определенные администратором, то пользователь не может намеренно "навредить" в базе.
.
Кстати, я погорячился, написав ранее, что данный модуль может работать только с MSSQL 2005 и выше, или Oracle. Сейчас нет ограничения на используемую СУБД.

--------------------------------------------
Лабитек
Центр разработки приложений

Спасибо, Валерий, все понятно. Собственно, вопрос и возник из-за упоминания хранимой процедуры.

Вы не думали над реализацией такой функциональности, как раздача прав доступа на связанные объекты, где связь идет один ко многим (например, раздавать доступ на все счета по продаже при определенных изменениях продажи)?

Еще одна задача, с которой часто сталкиваемся, - при изменении ответственного за что-либо раздавать ему определенные права доступа (т.е. нужно на основании изменяемого поля определять целевой AdminUnitID).

"Виталий Гдуля" написал:Вы не думали над реализацией такой функциональности, как раздача прав доступа на связанные объекты, где связь идет один ко многим (например, раздавать доступ на все счета по продаже при определенных изменениях продажи)?

Да, такую функциональность мы реализуем в следующих версиях - она прямо напрашивается :)

"Виталий Гдуля" написал:Еще одна задача, с которой часто сталкиваемся, - при изменении ответственного за что-либо раздавать ему определенные права доступа (т.е. нужно на основании изменяемого поля определять целевой AdminUnitID).

Похожую функциональность сейчас выполняет действие модуля "Установить права по умолчанию" - AdminUnitID пользователя определяется по полю, которое указано в действии, ему даются права, указанные флажками в действии, и плюс создаются права по умолчанию. Если я правильно понял Вашу задачу, надо убрать последнюю часть (права по умолчанию). Я думаю, мы реализуем в ближайшее время (как новый тип действия, конечно).
--------------------------------------------
Лабитек
Центр разработки приложений

Валерий, появился еще один вопрос. При генерации триггеров с условиями, аналогичными указанным в правиле, учитываете ли вы сложные фильтры для работы с датами, такие как "Дата" = "Сегодня" или "Дата" = "Текущая неделя"?

Виталий, пока что в правилах по полям "Дата" фильтры выставить нельзя.
И я не смог придумать ситуацию, когда требуется фильтр по дате для событийных прав :)
А в Ваших проектах была такая ситуация, можете привести пример?

Вы правы, ситуаций именно для событийной раздачи прав не было. Часто встречаются ситуации, когда нужно массово отнять/раздать права доступа при наступлении определенной даты. Например, через неделю после завершения работы закрывать к ней доступ, кроме чтения. Но это уже задача для другого модуля.

Расширили возможности модуля в плане работы со связанными записями, а также добавили новые виды действий.
http://community.terrasoft.ua/catalog/4879

Добрый день! Этот модуль уже доступен на Terrasoft XRM Bank?

Модуль доступен под все версии Террасофт, но только при использовании СУБД MSSQL

Спасибо, но у нас Oracle

"Валерий Андрусик" написал:Модуль доступен под все версии Террасофт, но только при использовании СУБД MSSQL

Я так понимаю, под Firebird тоже модуля нет?
Озадачился тем, что под Firebird не находится аналога 'EXECUTE AS User' из SQL2005+ и не удается красиво обойти те самые ограничения прав простых пользователей через ХП

Нет, пока только под MSSQL

Показать все комментарии