Перераздача прав на существующие записи при изменении прав по умолчанию

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

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

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

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

В предлагаемом скрипте происходит перебор всех администрируемых по записям таблиц и перенастройка прав согласно существующим правам по-умолчанию. Подразумевается, что таблицы прав называются по схеме ‘имя администрируемой таблицы + ’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, получить удобный инструмент перераспределения прав с возможностью анализа прогресса и успешности выполнения.

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

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