Oracle
 sql >> Base de données >  >> RDS >> Oracle

ORA-6502 avec déclencheur de journalisation des subventions

J'ai un nouveau projet sur lequel je travaille et je souhaite qu'un travail Oracle révoque les privilèges que j'ai accordés au personnel informatique de plus de 30 jours. Notre personnel informatique a besoin d'accéder occasionnellement à quelques tables de production pour résoudre les problèmes. Nous accordons des privilèges SELECT sur les tables dont la personne a besoin, mais personne ne me dit jamais quand ils ont terminé leur tâche et ces privilèges restent là pour toujours. Je voulais qu'un système révoque automatiquement les privilèges de plus de 30 jours afin que je n'aie pas à me souvenir de le faire. Avant de pouvoir révoquer des privilèges, j'avais besoin d'un moyen de suivre ces privilèges. J'ai donc créé un déclencheur qui est déclenché chaque fois qu'un GRANT est émis et enregistre les détails dans une table. Plus tard, un travail Oracle analysera cette table et révoquera les privilèges qu'il trouve trop anciens. Mon code déclencheur est le suivant :

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

Le code ci-dessus n'est pas original. J'ai trouvé un bon exemple sur Internet et j'ai modifié quelques éléments. Après avoir testé le code pendant 3 semaines, j'ai mis le déclencheur en production. Il ne m'a fallu que quelques jours pour recevoir une erreur.

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… Je crée un utilisateur sans rien accorder. Mais nous pouvons certainement voir que mon déclencheur a un problème d'exécution. Alors pourquoi ce déclencheur se déclenche-t-il si je ne fais que créer un utilisateur ? Une simple trace SQL m'a montré ce qui se passait avec ce SQL récursif. Dans les coulisses, Oracle publie ce qui suit en mon nom :

ACCORDER DES PRIVILÈGES D'HÉRITAGE SUR L'UTILISATEUR « BOB » À PUBLIC ;

Ok… donc à ce stade, je sais qu'un GRANT est émis lorsque je crée un utilisateur, mais pourquoi cela échoue-t-il ? J'ai testé ce déclencheur avec les privilèges système et cela a très bien fonctionné. Certes, je n'ai pas testé INHERIT PRIVILEGES, donc c'est une sorte de cas marginal.

Après de nombreux efforts de débogage, j'ai déterminé que l'appel de la fonction ora_privilege_list renvoie un ensemble vide à la collection nommée "priv". En tant que tel, npriv est défini sur une valeur NULL. Parce que NPRIV est NULL, la ligne où est dit "pour i dans 1..npriv" n'a pas beaucoup de sens, d'où l'erreur.

À mon avis, ora_privilege_list devrait renvoyer un élément, "INHERIT PRIVILEGES" et je pense que le fait de ne pas renvoyer cette liste est un bogue. Cependant, si ora_privilege_list va retourner une collection vide, alors la sortie de la fonction devrait être zéro et alors npriv obtiendrait une valeur plus appropriée. À des fins éducatives, ora_privilege_list est un synonyme de DBMS_STANDARD.PRIVILEGE_LIST.

Tout cela étant dit, je ne peux pas contrôler la fonction Oracle. Et je ne veux pas attendre qu'Oracle change son code dans DBMS_STANDARD pour ce que je pense qu'il devrait être. Je vais donc simplement coder mon déclencheur pour gérer le problème. L'ajout de deux lignes simples a résolu mon problème (voir ci-dessous en gras).

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

La solution est donc assez simple. N'exécutez les deux boucles FOR que si NPRIV n'est pas nul.