简体   繁体   English

Oracle SQL:在创建视图后触发添加授权

[英]Oracle SQL: Trigger to add grants after creating view

I am working with vendor application that uses Oracle database.我正在使用使用 Oracle 数据库的供应商应用程序。 To save the content it uses database tables, which are queried by using views.为了保存内容,它使用数据库表,这些表通过使用视图进行查询。 I do not have any control over that code.我对该代码没有任何控制权。 Because of security I gave access to those views to special reporting user, which can only select entries from it.出于安全考虑,我将这些视图的访问权限授予了只能从中选择条目的特殊报告用户。

Whenever some major change is made in the application it drops the appropriate view and creates it anew.每当在应用程序中进行一些重大更改时,它都会删除相应的视图并重新创建它。 Of course all grants are lost and since the changes are made rarely it is easy to forget to backup up and restore them afterwards.当然,所有授权都会丢失,并且由于很少进行更改,因此很容易忘记备份并在之后恢复它们。

I consulted DBA and he suggested to write a trigger to save grants in temporary table, after which the entries can be used to restore grants.我咨询了 DBA,他建议编写一个触发器将授权保存在临时表中,之后可以使用这些条目来恢复授权。 Saving part works fine as expected:保存部分按预期工作正常:

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;

As a result I get a table with all grants assigned to the said view.结果,我得到了一个表,其中包含分配给所述视图的所有授权。 For restoring I wanted to run similar trigger:为了恢复我想运行类似的触发器:

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;

I will note here that is just basic test of concept without any proper checks, so just focus on the big issue here.我会在这里注意到这只是概念的基本测试,没有任何适当的检查,所以只关注这里的大问题。

When I try to create a view now I get an error:当我现在尝试创建视图时,出现错误:

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.

This can only mean that the view is not created at the time when trigger tries to add grants.这只能意味着在触发器尝试添加授权时未创建视图。 Syntax wise I successfully ran the command:语法明智我成功运行了命令:

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

But when I try to run it using for, or just by itself in 'after create' trigger I get the same error.但是,当我尝试在“创建后”触发器中使用 for 或单独运行它时,我得到了同样的错误。 Does anyone knows how to approach this, and I would like to avoid jobs at all costs if possible.有谁知道如何解决这个问题,如果可能的话,我想不惜一切代价避免工作。

Ignoring whether this is a good idea... you're getting that error because you're overthinking your string manipulation.忽略这是否是一个好主意……您收到该错误是因为您对字符串操作的考虑过多。 What you're putting into your table looks OK.你放在桌子上的东西看起来不错。 The problem is when you get it back out.问题是当你把它拿出来的时候。 The value in the table is already a string, so you don't need to then enclose it in another set of quotes.表中的值已经是一个字符串,因此您无需再将其括在另一组引号中。

What you're actually running is the equivalent of:您实际运行的内容相当于:

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

which will also throw "ORA-00900: invalid SQL statement", rather than your standalone, working, version:这也将抛出“ORA-00900:无效的 SQL 语句”,而不是您的独立、工作版本:

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

If you swapped the order of your EXECUTE IMMEDIATE and DBMS_OUTPUT calls you'd see the problem statement before it ran, which would be more helpful - you'd see those quotes as part of the string.如果您交换EXECUTE IMMEDIATEDBMS_OUTPUT调用的顺序,您会运行之前看到问题陈述,这会更有帮助 - 您会看到这些引号作为字符串的一部分。

So in your second trigger, instead of doing:所以在你的第二个触发器中,而不是做:

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;

just do:做就是了:

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;

However, this still won't work;但是,这仍然行不通; it will now get ORA-30511: invalid DDL operation in system triggers.现在会得到 ORA-30511: invalid DDL operation in system triggers。 This is presumably because of the restrictions shown in the documentation :这大概是因为文档中显示的限制:

Trigger cannot do DDL operations on object that caused event to be generated.触发器不能对导致事件生成的对象进行 DDL 操作。

DDL on other objects is limited to compiling an object, creating a trigger, and creating, altering, and dropping a table.其他对象上的 DDL 仅限于编译对象、创建触发器以及创建、更改和删除表。

You said "it is easy to forget to backup up and restore them afterwards" but you're going to have to put a robust process around your upgrades to make sure that does happen.您说过“很容易忘记备份并在之后恢复它们”,但是您将不得不围绕升级制定一个强大的流程以确保这确实发生。

You can change your process to have a separate step at the end of every upgrade that is always run, which executes all of those stored statements for all objects - maybe skipping or ignoring errors from anything that wasn't recreated - and then drops the temp_priv table.您可以将您的流程更改为在每次运行的升级结束时有一个单独的步骤,该步骤为所有对象执行所有这些存储的语句 - 可能跳过或忽略任何未重新创建的错误 - 然后删除temp_priv桌子。

But you don't really want to (try to) create that temporary table in a trigger anyway - if two views are dropped, the first creates it, the second fails because it already exists.但是无论如何您都不想(尝试)在触发器中创建该临时表 - 如果删除了两个视图,第一个创建它,第二个失败,因为它已经存在。 A perhaps more realistic approach might be to create that table once now:一个或许更现实的方法可能是曾经,现在创建表:

create table TEMP_PRIV (PRIVILEGE_X VARCHAR2(4000));

and then utilise it for all subsequent upgrades, either by populating it with all grants for all views as a single step before the upgrade starts:然后将它用于所有后续升级,或者在升级开始之前将所有视图的所有授权作为一个步骤填充它:

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');

or if you're still worried about possibly forgetting that step then with a trigger to do it one view at a time as they are dropped:或者,如果您仍然担心可能会忘记该步骤,那么在它们被丢弃时使用触发器一次执行一个视图:

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;
/

Then at the end of the upgrade process reissue all the statements in the table and clear it down ready for next time:然后在升级过程结束时重新发出表中的所有语句并清除它以备下次使用:

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;

If you go with the simpler non-trigger approach then it will re-grant existing privileges, but that's OK.如果您采用更简单的非触发方法,那么它将重新授予现有权限,但没关系。 And the exception handler means it'll report but skip any views that were dropped and not recreated, if that ever happens.并且异常处理程序意味着它会报告但跳过任何已删除且未重新创建的视图,如果发生这种情况。 (You'll still have to deal with any new view of course; your after-create trigger wouldn't have helped with that anyway.) And note that I've truncated the table, rather than dropped it - so it's still there, empty, when the next upgrade comes around and wants to populate it. (当然,您仍然必须处理任何视图;无论如何,您的创建后触发器都无济于事。)请注意,我已经截断了表格,而不是删除它 - 所以它仍然存在,空,当下一次升级到来并想要填充它时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM