René Nyffenegger's collection of things on the web
René Nyffenegger on Oracle - Most wanted - Feedback -
 

Virtual Private Database (VPD) with Oracle

Virtual Private Database is also known as fine graind access control (FGAC). It allows to define which rows users may have access to.

A simple example

In this example, it is assumed that a company consists of different departments (with each having an entry in the departments table). An employee belongs to exactly on department. A department can have secrets that go into the department_secrets table.
create table department (
  dep_id int primary key,
  name    varchar2(30)
);

create table employee (
  dep_id references department,
  name    varchar2(30)
);

create table department_secrets (
  dep_id references department,
  secret varchar2(30)
);
Filling in some truly confidential secrets:
insert into department values (1, 'Research and Development');
insert into department values (2, 'Sales'                   );
insert into department values (3, 'Human Resources'         );

insert into employee values (2, 'Peter');
insert into employee values (3, 'Julia');
insert into employee values (3, 'Sandy');
insert into employee values (1, 'Frank');
insert into employee values (2, 'Eric' );
insert into employee values (1, 'Joel' );

insert into department_secrets values (1, 'R+D Secret #1'  );
insert into department_secrets values (1, 'R+D Secret #2'  );
insert into department_secrets values (2, 'Sales Secret #1');
insert into department_secrets values (2, 'Sales Secret #2');
insert into department_secrets values (3, 'HR Secret #1'   );
insert into department_secrets values (3, 'HR Secret #2'   );
For any employee, it must be possible to see all secrets of his department, but no secret of another department.
In order to make that happen with Oracle, we need to create a package, a trigger, and set a policy.
First, the package is created.
create or replace package pck_vpd
as
  p_dep_id department.dep_id%type;

  procedure set_dep_id(v_dep_id department.dep_id%type);

  function predicate (obj_schema varchar2, obj_name varchar2) return varchar2;
end pck_vpd;
/

create or replace package body pck_vpd as 
  
  procedure set_dep_id(v_dep_id department.dep_id%type) is
  begin
    p_dep_id := v_dep_id;
  end set_dep_id;


  function predicate (obj_schema varchar2, obj_name varchar2) return varchar2 is
  begin
    return 'dep_id = ' || p_dep_id;
  end predicate;
  
end pck_vpd;
/
Then the trigger is defined. This trigger fires whenever someone log on to the database. It finds the user's departement id (dep_id) and calls set_dep_id in the package.
create or replace trigger trg_vpd
  after logon on database
declare
  v_dep_id department.dep_id%type;
begin
  select dep_id into v_dep_id
  from employee where upper(name) = user;

  pck_vpd.set_dep_id(v_dep_id);
end;
/
Finally, the policy is defined. The policy states which procedure is used to add a where clause part to the where clause if someone executes a select statement.
begin
dbms_rls.add_policy  (
  user,
  'department_secrets',
  'choosable policy name',
  user,
  'pck_vpd.predicate',
  'select,update,delete');
end;
/
To test the setup, some users are created.
create user frank identified by frank default tablespace users temporary tablespace temp;
create user peter identified by peter default tablespace users temporary tablespace temp;
create user julia identified by julia default tablespace users temporary tablespace temp;
The necessary privileges are granted.
grant all on department_secrets to frank;
grant all on department_secrets to peter;
grant all on department_secrets to julia;

grant create session to frank;
grant create session to peter;
grant create session to julia;
A public synonym is created.
create public synonym department_secrets for department_secrets;
Frank (belonging to R+D) executes a query....
connect frank/frank;

select * from department_secrets;
    DEP_ID SECRET
---------- ------------------------------
         1 R+D Secret #1
         1 R+D Secret #2
Peter (belonging to Sales) executes a query....
connect peter/peter;

select * from department_secrets;
    DEP_ID SECRET
---------- ------------------------------
         2 Sales Secret #1
         2 Sales Secret #2