martes, 1 de noviembre de 2011

Auditar Cambios a Estructuras de la BD


¿Cómo podemos tener una auditoría automática de los cambios estructurales en una base de datos Oracle?

Esto es importante saber en el caso de que se tenga que realizar una auditoría, saber que ocurrió antes de que se presentaran errores en el sistema o ver que usuarios han realizado los cambios.

El ejemplo siguiente consiste en la creación de una tabla (para mantener el log de los cambios) y un trigger (se encargará de registrar los cambios realizados). Esto nos permitirá el script antes del cambio y el SQL ejecutado que generó el cambio.

Procedimiento para la generación de la auditoríaCreación de la tabla.
CREATE TABLE RESPALDOS_OBJETOS
(
FECHA TIMESTAMP(6),
EVENTO VARCHAR2(30 BYTE),
PROPIETARIO VARCHAR2(30 BYTE),
TIPO_OBJETO VARCHAR2(64 BYTE),
NOMBRE_OBJETO VARCHAR2(60 BYTE),
USUARIO VARCHAR2(30 BYTE),
MAQUINA VARCHAR2(100 BYTE),
TERMINAL VARCHAR2(20 BYTE),
PROGRAMA VARCHAR2(64 BYTE),
MODULO VARCHAR2(60 BYTE),
CLIENTE VARCHAR2(64 BYTE),
OBJETO CLOB,
SQL CLOB
)
TABLESPACE TS_DATOS
PCTUSED 0
PCTFREE 0
INITRANS 1
MAXTRANS 255
NOCACHE
NOPARALLEL
MONITORING;

COMMENT ON TABLE RESPALDOS_OBJETOS IS 'Auditoria y respaldos de objetos de un esquema de base de datos';
COMMENT ON COLUMN RESPALDOS_OBJETOS.FECHA IS 'Fecha en que se realiza el cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.EVENTO IS 'si es un create, drop o replace';
COMMENT ON COLUMN RESPALDOS_OBJETOS.PROPIETARIO IS 'propietario del objeto';
COMMENT ON COLUMN RESPALDOS_OBJETOS.TIPO_OBJETO IS 'si es una tabla, vista, procedimiento, funcion, paquete, secuencia, etc';
COMMENT ON COLUMN RESPALDOS_OBJETOS.NOMBRE_OBJETO IS 'nombre del objeto';
COMMENT ON COLUMN RESPALDOS_OBJETOS.MAQUINA IS 'maquina en donde se realizo el cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.TERMINAL IS 'terminal en donde se realizo el cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.PROGRAMA IS 'programa en donde se realizo el cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.MODULO IS 'modulo en donde se realizo el cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.CLIENTE IS 'cliente en donde se realizo cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.OBJETO IS 'ddl del objeto antes del cambio';
COMMENT ON COLUMN RESPALDOS_OBJETOS.SQL IS 'Script que realiza el cambio al objeto';


Creación del trigger de base de datos
CREATE OR REPLACE TRIGGER CTRL_CONTROL_CAMBIOS
BEFORE ALTER OR CREATE OR DROP
ON DATABASE
--Este trigger se disparara cada vez q se realicen alteraciones en los objetos
--de base de datos indicados
DECLARE
/******************************************************************************
Realizado por Endara Manosalvas Victor
Objetivo : Registra los cambios DDL realizados en la base de datos
--
ora_sysevent : Tipo de operacion realizada
ora_dict_obj_owner : Propietario de los objetos afectados
ora_dict_obj_name : Nombre del objeto que interviene en la transaccion DDL
ora_dict_obj_type : Tipo de objeto
******************************************************************************/
--Variables
Ln_Existe NUMBER(5);
lb_tipo_objeto user_objects.object_type%type;
lcl_script clob:=null; --almacena el script antes de la modificacion del objeto
BEGIN
--Respaldos a objeto de bd
if Ora_Dict_Obj_Type IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'VIEW', 'TRIGGER','TABLE','SEQUENCE',
'TRIGGER','JOB','SINONYM', 'INDEX')
AND Ora_Sysevent IN ('CREATE','DROP','ALTER') then
for c_usr in ( select USERNAME, TERMINAL, PROGRAM, MODULE, MACHINE, CLIENT_INFO, SQL_ID
from v$session
where Sid = Sys_Context('USERENV','SID'))loop
if ora_dict_obj_type='PACKAGE BODY' then
lb_tipo_objeto:='PACKAGE';
else
lb_tipo_objeto:=Ora_Dict_Obj_Type;
end if;
--Con la ayuda de la utilidades del DBMS_METADATA obtendremos el DDL del objeto
begin
lcl_script:=DBMS_METADATA.GET_DDL(lb_tipo_objeto, ora_dict_obj_name,ora_dict_obj_owner);
exception when others then
lcl_script:=sqlerrm;
end;
insert into respaldos_objetos( FECHA, EVENTO, PROPIETARIO,
TIPO_OBJETO, NOMBRE_OBJETO, USUARIO,
MAQUINA, TERMINAL, PROGRAMA,
MODULO, CLIENTE, OBJETO,
SQL)
values( SYSTIMESTAMP,
ora_sysevent,
ora_dict_obj_owner,
ora_dict_obj_type,
ora_dict_obj_name,
c_usr.USERNAME,
c_usr.MACHINE,
c_usr.TERMINAL,
c_usr.PROGRAM,
c_usr.MODULE,
c_usr.CLIENT_INFO,
lcl_script,
(select SQL_FULLTEXT
from v$sql
where sql_id=c_usr.SQL_ID));
end loop;
end if;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
/


Ejemplo para la prueba de la auditoria
En este ejemplo procederemos a crea la tabla VENTAS, luego agregar el campo IMPUESTO y finalmente eliminaremos el campo ESTADO.
CREATE TABLE ventas (
id_venta NUMBER,
id_persona NUMBER,
fecha DATE,
valor NUMBER,
valor_neto NUMBER,
valor_total NUMBER,
estado VARCHAR2(1)
);


ALTER TABLE ventas ADD impuesto NUMBER;


ALTER TABLE ventas DROP COLUMN estado;


Ahora veamos lo que se registró en la tabla de auditoría creada RESPALDOS_OBJETOS.



  • FECHA: Fecha y hora en la que se ejecutó la sentencia
  • EVENTO: Tipo de evento ejecutado Alter, Replace, Create o Drop
  • MAQUINA: Desde que terminal se realizó el cambio.
  • SQL: Es la sentencia ejecutada que realizó la alteración.

     
Vemos como se registró de manera automática los cambios realizados en la base, primero la creación de la tabla, luego la creación del campo y por último la eliminación del campo.
Solo se muestran los campos FECHA, EVENTO, MAQUINA y SQL pero hay otras que muestran mas información como la OBJETO que contiene el script de cómo estaba el objeto antes del cambio.


Consideraciones:
El usuario que cree el trigger deberá tener los privilegios suficientes para crear y accesar a las vistas de datos (v$session y v$sql) y procedimientos DBMS_METADATA

lunes, 11 de abril de 2011

Control de Procedimientos Oracle II (Auditoria y Respaldo de Objetos)

En mi blog Control Procedimientos Oracle, se explicó la forma de controlar el acceso a los procedimientos almacenados oracle de tal manera que no vayan haber mas de un desarrollador trabajando sobre el mismo y que no se pierda código.

Ahora en este blog se tratará sobre el mismo tema de control de procedimientos, pero ahora es como auditamos y respaldamos nuestros objetos de esquema de base de datos.
Cual es el procedimiento apropiado para aplicar un cambió a un ubjeto de esquema de base de datos?
Como ver como estaba antes de que se aplique el cambio, o saber quien lo modificó? ante todas esas interrogantes en el mercado existen ese tipo de herramientas pero son algo costosas y digamos que para una pequeña o mediana empresa esta lejos del presuesto por lo que debemos ingeniarnos.

A continuación un ejemplo de como logre auditar y respaldar los objetos de esquema de base de datos de manera automática antes de la modificacón.

Creamos una tabla que servirá de auditoría y respaldo de los objetos.
SQL> connect owner_obj/owner_obj@desarrollo_200
Conectado.
SQL> create table respaldos_objetos
  2  (
  3    fecha          date,
  4    evento         varchar2(30 byte),
  5    propietario    varchar2(30 byte),
  6    tipo_objeto    varchar2(64 byte),
  7    nombre_objeto  varchar2(60 byte),
  8    usuario        varchar2(30 byte),
  9    maquina        varchar2(100 byte),
 10    terminal       varchar2(20 byte),
 11    programa       varchar2(64 byte),
 12    modulo         varchar2(60 byte),
 13    cliente        varchar2(64 byte),
 14    objeto         clob
 15  );

Tabla creada.
SQL>
SQL> comment on table respaldos_objetos is 'Auditoria y respaldos de objetos de un esquema de base de datos';

Comentario creado.
SQL> comment on column respaldos_objetos.fecha is 'Fecha en que se realiza el cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.evento is 'si es un create, drop o replace';
Comentario creado.
SQL> comment on column respaldos_objetos.propietario is 'propietario del objeto';
Comentario creado.
SQL> comment on column respaldos_objetos.tipo_objeto is 'si es una tabla, vista, procedimiento, funcion, paquete, secuencia, etc';
Comentario creado.
SQL> comment on column respaldos_objetos.nombre_objeto is 'nombre del objeto';
Comentario creado.
SQL> comment on column respaldos_objetos.maquina is 'maquina en donde se realizo el cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.terminal is 'terminal en donde se realizo  el cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.programa is 'programa en donde se realizo el cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.modulo is 'modulo en donde se realizo el cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.cliente is 'cliente en donde se realizo  cambio';
Comentario creado.
SQL> comment on column respaldos_objetos.objeto is 'ddl del objeto antes del cambio';
Comentario creado.
Creamos un trigger de base de datos que se dispare antes de un create, drop o replace
SQL> CREATE OR REPLACE TRIGGER CTRL_PROCEDIMIENTOS
  2  BEFORE ALTER OR CREATE OR DROP
  3  ON DATABASE
  4  DECLARE
  5    /******************************************************************************
  6       Realizado por Endara Manosalvas Victor
  7   
  8       Objetivo : control de alteraciones a los procedimientos de OWNER_OBJ
  9       --
 10       ora_sysevent         : Tipo de operacion realizada
 11       ora_dict_obj_owner  : Propietario de los objetos afectados
 12       ora_dict_obj_name   : Nombre del objeto que interviene en la transaccion DDL
 13       ora_dict_obj_type   : Tipo de objeto
 14    ******************************************************************************/
 15    --Variables
 16    Ln_Existe NUMBER(5);
 17    lb_tipo_objeto    user_objects.object_type%type;
 18    lcl_script        clob:=null; --almacena el script antes de la modificacion del objeto
 19  BEGIN
 20    --Respaldos a procedimientos de bd
 21    if Ora_Dict_Obj_Type IN ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'VIEW', 'TRIGGER')
 22      AND Ora_Sysevent IN ('CREATE','DROP')
 23    then
 24      for c_usr in (select USERNAME, TERMINAL, PROGRAM, MODULE, MACHINE, CLIENT_INFO from v$session where Sid = Sys_Context('USERENV','SID'))loop
 25          if ora_dict_obj_type='PACKAGE BODY' then
 26              lb_tipo_objeto:='PACKAGE';
 27          else
 28              lb_tipo_objeto:=Ora_Dict_Obj_Type;
 29          end if;
 30          --Con la ayuda de la utilidades del DBMS_METADATA obtendremos el DDL del objeto
 31          begin
 32              lcl_script:=DBMS_METADATA.GET_DDL(lb_tipo_objeto, ora_dict_obj_name,ora_dict_obj_owner);
 33          exception when others then
 34              lcl_script:=sqlerrm;
 35          end;
 36          insert into respaldos_objetos(FECHA, EVENTO, PROPIETARIO, TIPO_OBJETO, NOMBRE_OBJETO, USUARIO, MAQUINA, TERMINAL, PROGRAMA, MODULO, CLIENTE, OBJETO)
 37          values(sysdate, ora_sysevent, ora_dict_obj_owner, ora_dict_obj_type, ora_dict_obj_name, c_usr.USERNAME, c_usr.MACHINE, c_usr.TERMINAL, c_usr.PROGRAM, c_usr.MODULE, c_usr.CLIENT_INFO, lcl_script);
 38      end loop;
 39    end if;
 40  EXCEPTION
 41    WHEN OTHERS THEN
 42       RAISE;
 43  END;
 44  /

Disparador creado.

Para esta prueba crearemos la funcion f_prueba con el usuario OWNER_OBJ.
SQL> create or replace function f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  begin
  4      return 1;
  5  end;
  6  /

Función creada.
Hacemos un select a la tabla RESPALDOS_OBJETOS y vemos el evento CREATE realizado por OWNER_OBJ.

SQL> select fecha,usuario,maquina,propietario,evento,nombre_objeto from respaldos_objetos;
FECHA    USUARIO   MAQUINA  PROPIETARIO EVENTO  NOMBRE_OBJETO
-------- --------- -------- ----------- ------- -------------
11/04/11 OWNER_OBJ VENDARA  OWNER_OBJ   CREATE   F_PRUEBA


Luego el usuario DESA_1 realizará cambios saobre la función f_prueba de OWNER_OBJ.
SQL> connect desa_1/desa_1@desarrollo_200
Conectado.
SQL> create or replace function owner_obj.f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  --Modificado DESA_1
  4  begin
  5      return 1+5;
  6  end;
  7  /

Función creada.
SQL> connect owner_obj/owner_obj@desarrollo_200
Conectado.SQL> select fecha,usuario,maquina,propietario,evento,nombre_objeto from respaldos_objetos;
FECHA    USUARIO   MAQUINA  PROPIETARIO EVENTO  NOMBRE_OBJETO
-------- --------- -------- ----------- ------- --------------
11/04/11 OWNER_OBJ VENDARA  OWNER_OBJ   CREATE  F_PRUEBA     
11/04/11 DESA_1    VENDARA  OWNER_OBJ   CREATE  F_PRUEBA


Hacemos un select a la tabla RESPALDOS_OBJETOS filtrando solo lo que realizo el usuario DESA_1 y vemos el objeto que respaldo antes de aplicar el cambio.
SQL> select fecha,usuario,maquina,propietario,evento,nombre_objeto,objeto from respaldos_objetos where usuario='DESA_1';
FECHA    USUARIO  MAQUINA  PROPIETARIO EVENTO  NOMBRE_OBJETO OBJETO                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
-------- -------- -------- ----------- ------- ------------- ------------------------------------------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
11/04/11 DESA_1   VENDARA  OWNER_OBJ   CREATE  F_PRUEBA      CREATE OR REPLACE FUNCTION "OWNER_OBJ"."F_PRUEBA" return number as
            --Creacion OWNER_OBJ
            begin
                 return 1;
            end;


Borramos la función f_prueba con el usuario DESA_1 y revisamos el log en RESPALDOS_OBJETOS.
SQL> connect desa_1/desa_1@desarrollo_200
Conectado.
SQL> drop function owner_obj.f_prueba;
Función borrada.

Revisamos el log para ver las transacciones realizadas.

SQL> connect owner_obj/owner_obj@desarrollo_200
Conectado.
SQL> select fecha,usuario,maquina,propietario,evento,nombre_objeto from respaldos_objetos;

FECHA    USUARIO   MAQUINA  PROPIETARIO EVENTO NOMBRE_OBJETO
-------- --------- -------- ----------- ------ -------------
11/04/11 OWNER_OBJ VENDARA  OWNER_OBJ   CREATE F_PRUEBA
11/04/11 DESA_1    VENDARA  OWNER_OBJ   CREATE F_PRUEBA
11/04/11 DESA_1    VENDARA  OWNER_OBJ   DROP   F_PRUEBA


Revisamos el script almacenado antes de ser borrada por DESA_1.

SQL> select objeto from respaldos_objetos where usuario='DESA_1' and evento='DROP';
OBJETO
--------------------------------------------------------------------------------
  CREATE OR REPLACE FUNCTION "OWNER_OBJ"."F_PRUEBA" return number as
--Creacion OWNER_OBJ
--Modificado DESA_1
begin
    return 1+5;
end;


Esto lo he combinado con el trigger de control de procedimientos y me ha servido de mucha ayuda al momento de recuperar una versión anterior, ver como estaba antes de la modificación y dar con el responsable del cambio. Con esto ta no me dicen los desarrolladores el tipico de que "yo no fui!!" por que estan registrados en la auditoría.

Espero les sirva de mucho este pequeño aporte
Hacemos un select a la tabla RESPALDOS_OBJETOS.

domingo, 10 de abril de 2011

Oracle- Control Procedimientos Almacenados

Como  podemos tener el control para las modificaciones de los procedimientos almacenados en oracle?
Para poder crear o modificar procedimientos de base de datos, es necesario tener el privilegio create or replace procedure y si trabajamos en un ambiente en que estos procedimientos estan centralizados en un esquema por ejemplo OWNER_OBJ y tenemos varios desarrolladores trabajando en el esquema OWNER_OBJ, se necesita que los usuarios desarrolladores tengan el privilegio create or replace any procedure para que puedan crear o modficar los procedimientos de base de datos en el esquema OWNER_OBJ.

Como controlar que mas de un desarrollador trabaje sobre el mismo procedimiento?
Esto mas de una vez ha llevado a trabajar doble y perder tiempo en recuperar código ya desarrollado. Me he topado que muchos de los programadores (me incluyo) dejan los editores pl-sql, toad o sqlplus abierto por mucho tiempo y realizan cambios sobre esa versión no considerando que ya pudo ser modificada por otro usuario.

A nivel de oracle no existe un permiso que permita otorgar el privilegio
 de moficación (DDL) sobre un procedimiento en específico del OWNER_OBJ.

A continución un ejemplo de como poder controlar los procedimiento de base de datos:


Tenemos el usuario owner_obj, quien es el dueño de los objetos y tenemos a los usuarios desa_1 y desa_2 quienes son los desarrolladores que trabaran sobre los objetos de owner_obj.
--Creamos la funcion f_prueba para nuestro ejemplo
SQL> connect owner_obj/owner_obj
Conectado.
SQL> create or replace function f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  begin
  4      return 1;
  5  end;
  6  /

Función creada.
SQL> select text from user_source where name='F_PRUEBA' order by line;
TEXT                                                                           
--------------------------------------------------------------------------------
function f_prueba return number as                                             
--Creacion OWNER_OBJ                                                           
begin                                                                          
    return 1;                                                                  
end;   
                                                                        
--Ahora el usuario desa_1 modificara la función f_prueba
SQL> connect desa_1/desa_1
SQL> create or replace function owner_obj.f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  --modificado por desa_1
  4  begin
  5      return 1+2;
  6  end;
  7  /

Función creada.
SQL> select text from all_source where owner='OWNER_OBJ' and name='F_PRUEBA' order by line;
TEXT                                                                           
--------------------------------------------------------------------------------
function           f_prueba return number as                                   
--Creacion OWNER_OBJ                                                           
--modificado por desa_1                                                        
begin                                                                          
    return 1+2;                                                                  
end;                                                                           

6 filas seleccionadas.
Pero al mismo tiempo el usuario desa_2 tambien realiza cambios sobre la función y compilando despues de que lo hace el usuario desa_1

SQL> connect desa_2/desa_2
Conectado.
SQL> create or replace function owner_obj.f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  --modificado por desa_2
  4  begin
  5      return 1*3;
  6  end;
  7  /

Función creada.
SQL> select text from all_source where owner='OWNER_OBJ' and name='F_PRUEBA' order by line;
TEXT                                                                           
--------------------------------------------------------------------------------
function           f_prueba return number as                                   
--Creacion OWNER_OBJ                                                           
--modificado por desa_2                                                        
begin                                                                          
    return 1*3;                                                                
end;                                                                           

6 filas seleccionadas.

Esto causa que los cambios realizados por el usuario desa_1 se pierdan.

Ya que no existe un permiso que otorgue el privilegio de modificar un procedimiento específico a continución un ejemplo de como hacerlo.

--Se crea una tabla que almacenará el usuario con el procedimiento que podrá modificar.
SQL> connect owner_obj/owner_obj
Conectado.
SQL> CREATE TABLE PERMISOS_PROGRAMAS
  2  (
  3    ID_PERMISO_PROGRAMA  NUMBER                   NOT NULL,
  4    ID_USUARIO           VARCHAR2(30 BYTE)        NOT NULL,
  5    TIPO_PROGRAMA        VARCHAR2(1 BYTE)         NOT NULL,
  6    PROGRAMA             VARCHAR2(30 BYTE)        NOT NULL,
  7    FECHA_PERMISO        DATE                     NOT NULL
  8  );

Tabla creada.
SQL> comment on column permisos_programas.id_permiso_programa is 'Id del permiso';
Comentario creado.
SQL>
SQL> comment on column permisos_programas.id_usuario is 'usuario de base de datos';

Comentario creado.
SQL>
SQL> comment on column permisos_programas.tipo_programa is 'F: funcion, P: Procedimiento y Q: Paquete';

Comentario creado.
SQL>
SQL> comment on column permisos_programas.programa is 'Nombre del programa a controlar';

Comentario creado.
Se crea un trigger de de base de datos que se dispara al tratar de realizar un alter, drop o replace. Este es el que controlará los cambios
SQL> create or replace trigger ctrl_procedimientos
  2  before alter or create or drop
  3  on database
  4  declare
  5    /******************************************************************************
  6       Realizado por Endara Manosalvas Victor
  7       Objetivo : control de alteraciones a los procedimientos de OWNER_OBJ
  8       --
  9       ora_sysevent         : Tipo de operacion realizada
 10       ora_dict_obj_owner  : Propietario de los objetos afectados
 11       ora_dict_obj_name   : Nombre del objeto que interviene en la transaccion DDL
 12       ora_dict_obj_type   : Tipo de objeto
 13    ******************************************************************************/
 14    --Variables
 15    ln_existe number(5);
 16    lb_tipo_objeto    user_objects.object_type%type;
 17    lcl_script        clob:=null; --almacena el script antes de la modificacion del objeto
 18  begin
 19    -- Solo se controla el acceso a los siguientes objetos
 20    if ora_dict_obj_type in  ('FUNCTION', 'PACKAGE', 'PACKAGE BODY', 'PROCEDURE', 'VIEW', 'TRIGGER')
 21          and ora_sysevent in ('CREATE','DROP')
 22          and sys_context('USERENV','CURRENT_SCHEMA') in ( 'OWNER_OBJ') and
 23          user not in ('SYS','SYSTEM','SYSMAN','SYSAUX','OWNER_OBJ') then
 24       select count(*)
 25       into ln_existe
 26       from permisos_programas
 27       where id_usuario = user and
 28               programa = ora_dict_obj_name and
 29               tipo_programa = decode(ora_dict_obj_type,
 30                                              'FUNCTION',
 31                                              'F',
 32                                              'PACKAGE',
 33                                              'Q',
 34                                              'PACKAGE BODY',
 35                                              'Q',
 36                                              'PROCEDURE',
 37                                              'P',
 38                                              'VIEW',
 39                                              'V',
 40                                              'TRIGGER',
 41                                              'T');
 42       if ln_existe = 0 then
 43          raise_application_error(-20000,
 44                                          'Ud no tiene privilegio de ' || ora_sysevent ||
 45                                          ' sobrel el ' || ora_dict_obj_type || ' ' ||
 46                                          ora_dict_obj_name || chr(13) ||
 47                                          'Debe solicitar permiso a este objeto al administrador de la base de datos.');
 48       end if;
 49    end if;
 50  exception
 51    when others then
 52       raise;
 53  end;
 54  /

Disparador creado.
--Insertamos en la tabla de permisos el usuario y procedimiento a quien queremos dar el permiso. En este caso es al usuario desa_1 que daremos permiso a la función f_prueba.
SQL> insert into permisos_programas (
  2     id_permiso_programa, id_usuario, tipo_programa,
  3     programa, fecha_permiso)
  4  values (1,
  5   'DESA_1',
  6   'F',
  7   'F_PRUEBA',
  8   sysdate );

1 fila creada.
SQL> commit;
Confirmación terminada.
-- Ahora el usuario desa_1 trabajará sobre el la función f_prueba.
SQL> connect desa_1/desa_1
Conectado.
SQL> create or replace function owner_obj.f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  --modificado por desa_2
  4  --modificado por desa_1
  5  begin
  6      return 1*5;
  7  end;
  8  /

Función creada.
Pero al mismo momento el desalmado usuario desa_2 va a tratar de modificar la función despues de que el pobre  usuario desa_1 a trabajo esforzadamente sobre la función f_prueba.
SQL> connect desa_2/desa_2
Conectado.
SQL> create or replace function owner_obj.f_prueba return number as
  2  --Creacion OWNER_OBJ
  3  --modificado por desa_2
  4  --modificado por desa_2
  5  begin
  6      return 1*4;
  7  end;
  8  /
create or replace function owner_obj.f_prueba return number as
*
ERROR en línea 1:
ORA-00604: se ha producido un error a nivel 1 de SQL recursivo
ORA-20000: Ud no tiene privilegio de CREATE sobrel el FUNCTION F_PRUEBA
Debe
solicitar permiso a este objeto al administrador de la base de datos.
ORA-06512: en línea 49

Pero ohh, sorpresa le salió un mensaje al usuario desa_2 de que no puede realizar modificaciones a la función f_prueba por que no tiene permisos. El esta obligado a pedir permiso al administrador y el administrador deberá revisar que usuario tiene solicitado el procedimiento, función o paquete antes de darle el permiso para que no se vaya a perder código.

Ahora lo que queda es realizarle una interfaz gráfica (Como yo la hice) para que sea mas dinámico administrar los permisos.
Espero que les sea util este código