Reputation: 125
I'm learning Oracle and I'm trying to add Row Level Security to a demo table. I can run successfully this first example:
CREATE OR REPLACE FUNCTION get_emp_pred
(schema_v IN VARCHAR2, tbl_v IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN 'upper(ename) = SYS_CONTEXT(''USERENV'', ''CURRENT_USER'')
OR upper(manager) = SYS_CONTEXT(''USERENV'', ''CURRENT_USER'')';
END;
BEGIN
SYS.DBMS_RLS.ADD_POLICY(
object_schema => 'hector',
object_name => 'emp',
policy_name => 'emp_vpd1',
function_schema => 'vpdadmin',
policy_function => 'get_emp_pred',
statement_types => 'select'
);
END;
In this example every user can see the rows where column 'ename' or column 'manager' value matches his own user name. Now my problem is when I try to filter not comparing by a user in a column but for some arbitrary column value like country or salary. For example I want to show rows to Bob where salary > 1000, to Anne where salary < 1000 and show all the rows to other users.
I tried this function and other variants without result:
CREATE OR REPLACE FUNCTION get_emp_pred
(schema_v IN VARCHAR2, tbl_v IN VARCHAR2) RETURN VARCHAR2 AS
l_predicate VARCHAR2(100);
BEGIN
IF 'BOB' = SYS_CONTEXT('USERENV', 'CURRENT_USER') THEN
l_predicate := 'salary > 1000';
ELSIF 'ANNE' = SYS_CONTEXT('USERENV', 'CURRENT_USER') THEN
l_predicate := 'salary < 1000';
ELSE
l_predicate := '1=1';
END IF;
RETURN l_predicate;
END;
I suppose you can not evaluate this conditions at runtime, but if I write one policy for every user and predicate it doesn't work neither. For example this function with its policy for Bob, other for Anne, etc:
CREATE OR REPLACE FUNCTION get_emp_pred_bob
(schema_v IN VARCHAR2, tbl_v IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN '''BOB'' = SYS_CONTEXT(''USERENV'', ''CURRENT_USER'') AND salary > 1000';
END;
If I have only the Bob policy it works for Bob and return 0 rows for other users, but when I add a second policy for Anne I get 0 rows for everybody, maybe because policyBob && policyAnne returns always false.
I want to know if this kind of filtering is possible.
Upvotes: 1
Views: 1630
Reputation: 191275
Your second get_emp_pred
function isn't doing what you expect because it is a (default) definer's rights function. That means that within the function, current_user
is the owner, VPDADMIN, not the user performing the query.
From the documentation current_user
gives:
The name of the database user whose privileges are currently active. This may change during the duration of a database session ... to reflect the owner of any active definer's rights object. When no definer's rights object is active,
CURRENT_USER
returns the same value asSESSION_USER
.
So if you change current_user
to session_user
in the second function then it should do what you want:
CREATE OR REPLACE FUNCTION get_emp_pred
(schema_v IN VARCHAR2, tbl_v IN VARCHAR2) RETURN VARCHAR2 AS
l_predicate VARCHAR2(100);
BEGIN
IF 'BOB' = SYS_CONTEXT('USERENV', 'SESSION_USER') THEN
l_predicate := 'salary > 1000';
ELSIF 'ANNE' = SYS_CONTEXT('USERENV', 'SESSION_USER') THEN
l_predicate := 'salary < 1000';
ELSE
l_predicate := '1=1';
END IF;
RETURN l_predicate;
END;
Your first function doesn't suffer from that because the user is evaluated in the predicate as part of the query, not within the function.
when I add a second policy for Anne I get 0 rows for everybody, maybe because policyBob && policyAnne returns always false.
Yes, both policies are applied, and you end up with mutually exclusive predicates applied to the query, so now rows can match.
You could make this work with a single policy and function that returns a predicate that ORs together all the options; something like:
RETURN q'[(SYS_CONTEXT('USERENV', 'CURRENT_USER') = 'BOB' AND salary > 1000)
OR (SYS_CONTEXT('USERENV', 'CURRENT_USER') = 'ANNE' AND salary < 1000)
OR (SYS_CONTEXT('USERENV', 'CURRENT_USER') NOT IN ('BOB', 'ANNE'))'];
But using PL/SQL logic to return different predicates is simpler - as long as it doesn't impact performance. It's possible a static policy with a single fixed complex predicate could perform better.
Upvotes: 1