Oracle SQL: триггер для добавления грантов после создания представления - PullRequest
3 голосов
/ 19 марта 2020

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

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

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

create or replace TRIGGER RECORD_GRANTS_ONDROP 
BEFORE DROP ON MYUSER.SCHEMA 
BEGIN
    IF ora_dict_obj_owner = 'MYUSER' and ora_dict_obj_name not like 'TEMP_PRIV%' and ora_dict_obj_type='VIEW' then
        EXECUTE IMMEDIATE 'CREATE TABLE TEMP_PRIV AS SELECT ''GRANT '' || PRIVILEGE || '' ON MYUSER.'' || TABLE_NAME || '' TO '' || GRANTEE PRIVILEGE_x FROM USER_TAB_PRIVS WHERE GRANTEE not in (''MYUSER'',''PUBLIC'') AND TABLE_NAME=''' || ora_dict_obj_name || '''';
    ELSE null;
    END IF;
END;

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

create or replace TRIGGER RESTORE_GRANTS_AFTERCREATE
AFTER CREATE ON MYUSER.SCHEMA 
BEGIN
    IF ora_dict_obj_owner = 'MYUSER' and ora_dict_obj_type='VIEW' then
        FOR loop_counter IN (select '''' || privilege_x || '''' AS privilege_x from temp_priv)
        LOOP
            EXECUTE IMMEDIATE loop_counter.privilege_x;
            DBMS_OUTPUT.PUT_LINE(loop_counter.privilege_x);
        END LOOP;
    ELSE null;
    END IF;
    NULL;
END;

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

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

Error report -
ORA-00604: error occurred at recursive SQL level 1
ORA-00900: invalid SQL statement
ORA-06512: at line 5
00604. 00000 -  "error occurred at recursive SQL level %s"
*Cause:    An error occurred while processing a recursive SQL statement
           (a statement applying to internal dictionary tables).
*Action:   If the situation described in the next error on the stack
           can be corrected, do so; otherwise contact Oracle Support.

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

BEGIN
    EXECUTE IMMEDIATE 'GRANT SELECT ON MYUSER.CR_STATUS_TABLE TO SOMEUSER';
END

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

1 Ответ

2 голосов
/ 19 марта 2020

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

То, что вы на самом деле используете, эквивалентно:

EXECUTE IMMEDIATE '''GRANT SELECT ON MYUSER.CR_STATUS_TABLE TO SOMEUSER''';

, который также будет выдавать «ORA-00900: неверный SQL оператор», а не отдельную рабочую версию:

EXECUTE IMMEDIATE 'GRANT SELECT ON MYUSER.CR_STATUS_TABLE TO SOMEUSER';

Если вы поменяли местами порядок EXECUTE IMMEDIATE и DBMS_OUTPUT вызовы, которые вы увидели бы с оператором задачи до его выполнения, что было бы более полезно - вы бы увидели эти кавычки как часть строки.

Так что во втором триггере вместо делать:

FOR loop_counter IN (select '''' || privilege_x || '''' AS privilege_x from temp_priv)
LOOP
    EXECUTE IMMEDIATE loop_counter.privilege_x;
    DBMS_OUTPUT.PUT_LINE(loop_counter.privilege_x);
END LOOP;

просто делать:

FOR loop_counter IN (select privilege_x from temp_priv)
LOOP
    DBMS_OUTPUT.PUT_LINE(loop_counter.privilege_x);
    EXECUTE IMMEDIATE loop_counter.privilege_x;
END LOOP;

Однако это все равно не сработает; теперь он получит ORA-30511: недопустимая операция DDL в системных триггерах. Вероятно, это связано с ограничениями , показанными в документации :

Триггер не может выполнять операции DDL над объектом, вызвавшим генерирование события.

DDL для других Объекты ограничены компиляцией объекта, созданием триггера, созданием, изменением и удалением таблицы.

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

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

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

create table TEMP_PRIV (PRIVILEGE_X VARCHAR2(4000));

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

INSERT INTO TEMP_PRIVS (PRIVILEGE_X)
SELECT 'GRANT ' || PRIVILEGE || ' ON MYUSER.' || TABLE_NAME || ' TO ' || GRANTEE
FROM USER_VIEWS UV
JOIN USER_TAB_PRIVS UTP ON UTP.TABLE_NAME = UV.VIEW_NAME
WHERE UTP.GRANTEE not in ('MYUSER','PUBLIC');

или если вы все еще беспокоитесь о том, что можете забыть об этом шаге, то с помощью триггера, чтобы сделать его одним видом за раз, когда они отброшены:

create or replace TRIGGER RECORD_GRANTS_ONDROP 
BEFORE DROP ON MYUSER.SCHEMA 
BEGIN
    IF ora_dict_obj_owner = 'MYUSER' and ora_dict_obj_name not like 'TEMP_PRIV%' and ora_dict_obj_type='VIEW' then
      INSERT INTO TEMP_PRIV
      SELECT 'GRANT ' || PRIVILEGE || ' ON MYUSER.' || TABLE_NAME || ' TO ' || GRANTEE
      FROM USER_TAB_PRIVS
      WHERE GRANTEE not in ('MYUSER','PUBLIC')
      AND TABLE_NAME = ora_dict_obj_name;
    END IF;
END;
/

Затем в конце процесса обновления еще раз введите все операторы в таблицу и очистите их, готовые к следующему разу:

DECLARE
    missing_view EXCEPTION;
    PRAGMA EXCEPTION_INIT(missing_view, -942);
BEGIN
    FOR loop_counter IN (select privilege_x from temp_priv)
    LOOP
        BEGIN
            DBMS_OUTPUT.PUT_LINE(loop_counter.privilege_x);
            EXECUTE IMMEDIATE loop_counter.privilege_x;
        EXCEPTION
          WHEN missing_view THEN
              -- report but otherwise ignore
              DBMS_OUTPUT.PUT_LINE(SQLERRM);
        END;
    END LOOP;
END;
/

TRUNCATE TABLE temp_priv;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...