Ik heb een nieuw project waar ik aan werk en ik wil dat een Oracle-taak privileges intrekt die ik heb verleend aan IT-personeel dat ouder is dan 30 dagen. Ons IT-personeel heeft af en toe toegang nodig tot enkele productietabellen om problemen op te lossen. We verlenen SELECT privileges op de tafels die die persoon nodig heeft, maar niemand vertelt me ooit wanneer ze klaar zijn met hun taak en die privileges blijven daar voor altijd. Ik wilde een systeem hebben dat automatisch privileges intrekt die ouder zijn dan 30 dagen, zodat ik er niet aan hoef te denken om het te doen. Voordat ik privileges kon intrekken, had ik een manier nodig om die privileges te volgen. Dus ik heb een trigger gemaakt die wordt geactiveerd wanneer een GRANT wordt uitgegeven en de details in een tabel registreert. Later zal een Oracle-taak die tabel scannen en de privileges intrekken die hij vindt die te oud zijn. Mijn triggercode is als volgt:
create or replace trigger sys.grant_logging_trig after grant on database declare priv dbms_standard.ora_name_list_t; who dbms_standard.ora_name_list_t; npriv pls_integer; nwho pls_integer; begin npriv := ora_privilege_list(priv); if (ora_sysevent = 'GRANT') then nwho := ora_grantee(who); else nwho := ora_revokee(who); end if; for i in 1..npriv loop for j in 1..nwho loop insert into system.grant_logging values ( systimestamp, ora_login_user, ora_sysevent, who(j), priv(i), ora_dict_obj_owner, ora_dict_obj_name ); end loop; end loop; end; /
Bovenstaande code is niet origineel. Ik vond een goed voorbeeld op internet en heb het een en ander aangepast. Nadat ik de code 3 weken had getest, rolde ik de trigger in productie. Het duurde maar een paar dagen voordat ik een foutmelding kreeg.
SQL> CREATE USER bob IDENTIFIED BY password; ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-04088: error during execution of trigger 'SYS.GRANT_LOGGING_TRIG' ORA-00604: error occurred at recursive SQL level 2 ORA-06502: PL/SQL: numeric or value error ORA-06512: at line 28
Hmmm... Ik maak een gebruiker aan die niets toekent. Maar we kunnen zeker zien dat mijn trigger een probleem heeft met het uitvoeren. Dus waarom wordt deze trigger geactiveerd als ik alleen maar een gebruiker maak? Een simpele SQL-trace liet me zien wat er aan de hand was met die recursieve SQL. Achter de schermen geeft Oracle namens mij het volgende uit:
VERLENEN PRIVILEGES VAN GEBRUIKER "BOB" aan PUBLIEK;
Ok ... dus op dit moment weet ik dat er een GRANT wordt uitgegeven wanneer ik een gebruiker aanmaak, maar waarom mislukt dit? Ik heb deze trigger getest met systeemrechten en het werkte prima. Toegegeven, ik heb geen INHERIT PRIVILEGES getest, dus dit is een beetje een randgeval.
Na een behoorlijke hoeveelheid debugging-inspanningen, heb ik vastgesteld dat de functieaanroep ora_privilege_list een lege set retourneert naar de verzameling met de naam "priv". Als zodanig wordt npriv ingesteld op een NULL-waarde. Omdat NPRIV NULL is, heeft de regel waar staat "for i in 1..npriv" niet veel zin, vandaar de fout.
Naar mijn mening zou ora_privilege_list één item moeten retourneren, "INHERIT PRIVILEGES" en ik geloof dat het die lijst niet teruggeeft als een bug. Als ora_privilege_list echter een lege verzameling retourneert, dan zou de uitvoer van de functie nul moeten zijn en zou npriv een meer juiste waarde krijgen. Voor educatieve doeleinden is ora_privilege_list een synoniem voor DBMS_STANDARD.PRIVILEGE_LIST.
Dat gezegd hebbende, ik heb geen controle over de Oracle-functie. En ik wil niet wachten tot Oracle hun code in DBMS_STANDARD verandert in wat ik denk dat het zou moeten zijn. Dus ik zal gewoon mijn trigger coderen om het probleem aan te pakken. Het toevoegen van twee eenvoudige regels loste mijn probleem op (hieronder vetgedrukt weergegeven).
create or replace trigger sys.grant_logging_trig after grant on database declare priv dbms_standard.ora_name_list_t; who dbms_standard.ora_name_list_t; npriv pls_integer; nwho pls_integer; begin npriv := ora_privilege_list(priv); if (ora_sysevent = 'GRANT') then nwho := ora_grantee(who); else nwho := ora_revokee(who); end if; if to_char(npriv) is not null then for i in 1..npriv loop for j in 1..nwho loop insert into system.grant_logging values ( systimestamp, ora_login_user, ora_sysevent, who(j), priv(i), ora_dict_obj_owner, ora_dict_obj_name ); end loop; end loop; end if; end; /
Dus de oplossing is vrij eenvoudig. Voer de twee FOR-lussen alleen uit als NPRIV niet null is.