sql >> Base de Datos >  >> RDS >> Oracle

ORA-6502 con disparador de registro de concesión

Tengo un nuevo proyecto en el que estoy trabajando en el que quiero que un trabajo de Oracle revoque los privilegios que otorgué al personal de TI que tiene más de 30 días. Nuestro personal de TI necesita acceso ocasional a algunas mesas de producción para solucionar problemas. Otorgamos privilegios SELECT en las mesas que esa persona necesita, pero nadie me dice nunca cuándo ha terminado con su tarea y esos privilegios se quedan ahí para siempre. Quería tener un sistema que revocara automáticamente los privilegios de más de 30 días para que no tuviera que acordarme de hacerlo. Antes de poder revocar los privilegios, necesitaba una forma de rastrear esos privilegios. Así que creé un activador que se dispara cada vez que se emite un GRANT y registra los detalles en una tabla. Más tarde, un trabajo de Oracle escaneará esa tabla y revocará los privilegios que considere demasiado antiguos. Mi código de activación es el siguiente:

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

El código anterior no es original. Encontré un buen ejemplo en Internet y modifiqué algunas cosas. Después de probar el código durante 3 semanas, puse el gatillo en producción. Solo me llevó unos días recibir un error.

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... Estoy creando un usuario que no concede nada. Pero podemos ver con seguridad que mi gatillo tiene problemas para ejecutarse. Entonces, ¿por qué se dispara este disparador si todo lo que estoy haciendo es crear un usuario? Un simple seguimiento de SQL me mostró lo que estaba pasando con ese SQL recursivo. Detrás de escena, Oracle emite lo siguiente en mi nombre:

CONCEDER PRIVILEGIOS DE HEREDAD AL USUARIO "BOB" a PUBLIC;

Ok... entonces, en este punto, sé que se emite un GRANT cuando creo un usuario, pero ¿por qué falla esto? Probé este activador con privilegios del sistema y funcionó bien. De acuerdo, no probé INHERIT PRIVILEGES, por lo que este es un caso extremo.

Después de una buena cantidad de esfuerzo de depuración, determiné que la llamada a la función ora_privilege_list devuelve un conjunto vacío a la colección denominada "priv". Como tal, npriv se establece en un valor NULL. Debido a que NPRIV es NULL, la línea donde dice "for i in 1..npriv" no tiene mucho sentido, de ahí el error.

En mi opinión, ora_privilege_list debería devolver un elemento, "HEREDAR PRIVILEGIOS" y creo que no devolver esa lista es un error. Sin embargo, si ora_privilege_list va a devolver una colección vacía, entonces la salida de la función debería ser cero y luego npriv obtendría un valor más adecuado. Para fines educativos, ora_privilege_list es sinónimo de DBMS_STANDARD.PRIVILEGE_LIST.

Dicho todo esto, no puedo controlar la función de Oracle. Y no quiero esperar a que Oracle cambie su código en DBMS_STANDARD a lo que creo que debería ser. Así que solo codificaré mi disparador para manejar el problema. Agregar dos líneas simples resolvió mi problema (visto a continuación en negrita).

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

Así que la solución es bastante simple. Solo realice los dos bucles FOR si NPRIV no es nulo.