Create and Manage Contexts

1. Documentación en Tahiti -> Oracle Database, 11g Release 2 (11.2) -> Masters Book List -> Security Guide -> 6 Using Application Contexts to Retrieve User Information

2. Un contexto o contexto de aplicación no es más que un conjunto de pares nombre-valor que la base de datos almacena en memoria. Se identifica por una etiqueta, a partir de la cual se puede acceder al array de pares nombre-valor. Una aplicación crear y utilizar estos contextos para el control de acceso que hemos visto en el objetivo anterior o para reforzar la seguridad de la propia aplicación ya que el contexto está protegido por un procedimiento y no un usuario.

Lo más probable es que el verdadero propósito que hay detrás de este objetivo es reforzar el control de acceso utilizando contextos, así que vamos a hacer un ejercicio completo que combina contextos y control de acceso con VPD.

El ejercicio que vamos a hacer está sacado de la documentación practicamente integro ya que es muy interesante.

-- Creamos un usuario que será el administrador de las políticas VPD de nuestra BD
CREATE USER SYSADMIN_VPD IDNTIFIED BY "sysadmin_vpd";
GRANT CREATE SESSION, CREATE ANY CONTEXT,
     CREATE PROCEDURE, CREATE TRIGGER,
     ADMINISTER DATABASE TRIGGER TO sysadmin_vpd IDENTIFIED BY "sysadmin_vpd";
GRANT EXECUTE ON DBMS_SESSION TO sysadmin_vpd;
GRANT EXECUTE ON DBMS_RLS TO sysadmin_vpd;

-- Creamos dos usuarios con los que vamos a realizar las pruebas
GRANT CREATE SESSION TO tbrooke IDENTIFIED BY tbrooke;
GRANT CREATE SESSION TO owoods IDENTIFIED BY owoods;

-- Creamos un tabla de prueba en el esquema SCOTT con dos registros
ALTER USER SCOTT ACCOUNT UNLOCK IDENTIFIED BY "scott";
CONNECT scott/scott
DROP TABLE CUSTOMERS CASCADE CONSTRAINTS;
CREATE TABLE customers (
 cust_no    NUMBER(4),
 cust_email VARCHAR2(20),
 cust_name  VARCHAR2(20));

INSERT INTO customers VALUES (1234, 'TBROOKE', 'Thadeus Brooke');
INSERT INTO customers VALUES (5678, 'OWOODS', 'Oberon Woods');
COMMIT;

-- Damos permisos de SELECT al administrador VPD
GRANT SELECT ON customers TO sysadmin_vpd;

-- Creamos otra tabla de pedidos
DROP TABLE ORDERS_TAB;
CREATE TABLE orders_tab (
  cust_no  NUMBER(4),
  order_no NUMBER(4));

INSERT INTO orders_tab VALUES (1234, 9876);
INSERT INTO orders_tab VALUES (5678, 5432);
INSERT INTO orders_tab VALUES (5678, 4592);

-- Damos permisos de SELECT a los usuarios que hemos creado
GRANT SELECT ON orders_tab TO tbrooke;
GRANT SELECT ON orders_tab TO owoods;

-- Creamos el contexto con el administrador VPD
CONNECT SYSADMIN_VPD/sysadmin_vpd
CREATE OR REPLACE CONTEXT orders_ctx USING orders_ctx_pkg;

-- Creamos el paquete PL/SQL que fija el contexto de aplicación
-- La parte más importante es la fijación del contexto con "SET_CONTEXT"
CREATE OR REPLACE PACKAGE orders_ctx_pkg IS
  PROCEDURE set_custnum;
 END;
/
CREATE OR REPLACE PACKAGE BODY orders_ctx_pkg IS
  PROCEDURE set_custnum
  AS
    custnum NUMBER;
  BEGIN
     SELECT cust_no INTO custnum FROM SCOTT.CUSTOMERS
        WHERE cust_email = SYS_CONTEXT('USERENV', 'SESSION_USER');
     DBMS_SESSION.SET_CONTEXT('orders_ctx', 'cust_no', custnum);
  EXCEPTION
   WHEN NO_DATA_FOUND THEN NULL;
  END set_custnum;
END;
/

-- Definimos un TRIGGER que se lance al inicio de sesión en la BD
CREATE TRIGGER set_custno_ctx_trig AFTER LOGON ON DATABASE
 BEGIN
  sysadmin_vpd.orders_ctx_pkg.set_custnum;
 END;
/

-- Comprobamos que el contexto se aplica correctamente a los usuarios creados
CONN tbrooke/tbrooke
SELECT SYS_CONTEXT('orders_ctx', 'cust_no') custnum FROM DUAL;

-- Ahora creamos la política que limita el acceso de los usuarios a sus propios pedidos
-- Nos logamos como el admin. VPD
CONN SYSADMIN_VPD/sysadmin_vpd
-- Creamos la función que añade el predicado de control de los pedidos
CREATE OR REPLACE FUNCTION get_user_orders(
  schema_p   IN VARCHAR2,
  table_p    IN VARCHAR2)
 RETURN VARCHAR2
 AS
  orders_pred VARCHAR2 (400);
 BEGIN
  orders_pred := 'cust_no = SYS_CONTEXT(''orders_ctx'', ''cust_no'')';
 RETURN orders_pred;
END;
/
-- Creamos la política VPD
BEGIN
 DBMS_RLS.ADD_POLICY (
  object_schema    => 'scott',
  object_name      => 'orders_tab',
  policy_name      => 'orders_policy',
  function_schema  => 'sysadmin_vpd',
  policy_function  => 'get_user_orders',
  statement_types  => 'select');
END;
/

-- Es hora de probar que funciona correctamente
-- Primero comprobamos que podemos ver todos los registros como el administrador VPD
SELECT * FROM SCOTT.ORDERS_TAB;

-- Nos conectamos con los otros usuarios para validar la política
CONNECT tbrooke/tbrooke
SELECT * FROM SCOTT.ORDERS_TAB;

-- Validamos el segundo usuario
-- Cualquier otro usuario no vería ningún registro en la tabla (con permiso SELECT)
CONNECT owoods/owoods
SELECT * FROM SCOTT.ORDERS_TAB;

-- Limpiamos los objetos y usuarios creados
CONN SCOTT/scott
DROP TABLE ORDERS_TAB;
DROP TABLE CUSTOMERS;
CONNECT / AS SYSDBA
DROP CONTEXT orders_ctx;
DROP USER sysadmin_vpd CASCADE;
DROP USER tbrooke;
DROP USER owoods;