Oracle security policies
In this article, I would like to talk about a solution that allows you to organize access restriction to data at the DBMS level, moreover, in the case when the constrained table itself contains data that affects the restriction.
The example that I will give is greatly simplified, but the necessary tools used in the solution are provided exhaustively. Tested on Oracle 11g R2.
Suppose that the SCOTT user needed to organize access restriction to the data of some tables, but the criteria by which access should be granted look so that it is necessary to analyze the data of the restricted table. It would seem that it could be easier - let's go.
Establish a connection to the database on behalf of SCOTT and create tables, one of which will contain a list of users who have access, and the second the data itself:
Everything went well. Now, make the connection on behalf of SYS , create a predicate function and a security policy:
That's all, now the SCOTT user, when accessing the USER_DATA table , will see only the data that belongs to him in the ud_user_id column , according to the setting in the USER_ALLOWED table . It remains to verify this, again log in as SCOTT and:
In order to determine what the error is, you will need to view the trace file, which contains a detailed description:
In fact, I would call it an analogue of a mutation in a trigger. To solve the problem, you can use ... as practice has shown, not much. If you want, try it yourself, but given that the real task will be very different from the above example. Let's go back to the connection on behalf of SYS , and do:
The first thing that was done was to declare a new data type that reflects a single-column table of type NUMBER . This type will be used for a package variable in which a list of identifiers will be selected by which access will be granted. The function in the package is a wrapper that allows you to call back data. There will be no problems with the contents of the package variable between sessions, since a separate memory area is allocated for each session. Assuming that the purpose of the cursor in the predicate function is to determine the identifiers of the primary key of the table, it is more advisable to modify the generated constraint into the EXISTS construct .
Now check the result of the work done by connecting on behalf of SCOTT :
The example that I will give is greatly simplified, but the necessary tools used in the solution are provided exhaustively. Tested on Oracle 11g R2.
Suppose that the SCOTT user needed to organize access restriction to the data of some tables, but the criteria by which access should be granted look so that it is necessary to analyze the data of the restricted table. It would seem that it could be easier - let's go.
Establish a connection to the database on behalf of SCOTT and create tables, one of which will contain a list of users who have access, and the second the data itself:
CREATE TABLE scott.user_allowed
(
ua_id NUMBER,
oracle_user VARCHAR2(50)
);
Table created.
insert into scott.user_allowed (ua_id, oracle_user) values (1, 'SCOTT');
insert into scott.user_allowed (ua_id, oracle_user) values (2, 'OTHERUSER');
1 row created.
1 row created.
commit;
CREATE TABLE scott.user_data
(
ud_id NUMBER,
ud_user_id NUMBER,
ud_data VARCHAR2(2000)
);
Table created.
insert into scott.user_data (ud_id, ud_user_id, ud_data) values (1, 1, 'SCOTT DATA');
insert into scott.user_data (ud_id, ud_user_id, ud_data) values (2, 2, 'OTHERUSER DATA');
1 row created.
1 row created.
commit;
select ud_id, ud_user_id, ud_data from user_data;
2 rows selected.
Everything went well. Now, make the connection on behalf of SYS , create a predicate function and a security policy:
CREATE OR REPLACE FUNCTION sys.scott_predicate_allow
(
obj_schema IN VARCHAR2, obj_name IN VARCHAR2
)
RETURN VARCHAR2
IS
BEGIN
return 'ud_id in (SELECT ud_id FROM scott.user_data ud, scott.user_allowed ua WHERE ud.ud_user_id = ua.ua_id AND upper(ua.oracle_user) = upper(USER))';
END scott_predicate_allow;
/
Function created.
BEGIN
SYS.DBMS_RLS.ADD_POLICY
(
Object_schema => 'SCOTT'
,Object_name => 'USER_DATA'
,policy_name => 'SCOTT_ALLOW_USER_DATA'
,function_schema => 'SYS'
,policy_function => 'SCOTT_PREDICATE_ALLOW'
,statement_types => 'SELECT,INSERT,UPDATE,DELETE,INDEX '
,policy_type => dbms_rls.dynamic
,long_predicate => FALSE
,sec_relevant_cols => 'UD_ID,UD_USER_ID,UD_DATA'
,sec_relevant_cols_opt => NULL
,update_check => FALSE
,enable => TRUE
);
END;
/
PL/SQL procedure successfully completed.
That's all, now the SCOTT user, when accessing the USER_DATA table , will see only the data that belongs to him in the ud_user_id column , according to the setting in the USER_ALLOWED table . It remains to verify this, again log in as SCOTT and:
select ud_id, ud_user_id, ud_data from user_data;
ORA-28113: policy predicate has error
In order to determine what the error is, you will need to view the trace file, which contains a detailed description:
ORA-28108: circular security policies detected
In fact, I would call it an analogue of a mutation in a trigger. To solve the problem, you can use ... as practice has shown, not much. If you want, try it yourself, but given that the real task will be very different from the above example. Let's go back to the connection on behalf of SYS , and do:
create or replace type sys.number_table is table of number;
/
Type created.
CREATE OR REPLACE PACKAGE SYS.SCOTT_ALLOWED AS
allowed_id number_table:=number_table();
function get_allowed_id return number_table;
END SCOTT_ALLOWED;
/
Package created.
CREATE OR REPLACE PACKAGE BODY SYS.SCOTT_ALLOWED AS
function get_allowed_id return number_table is
begin
return allowed_id;
end;
END SCOTT_ALLOWED;
/
Package body created.
CREATE OR REPLACE FUNCTION sys.scott_predicate_allow
(
obj_schema IN VARCHAR2, obj_name IN VARCHAR2
)
RETURN VARCHAR2
IS
BEGIN
scott_allowed.allowed_id:=number_table();
select ud_id bulk collect into scott_allowed.allowed_id
from scott.user_data ud,
scott.user_allowed ua
where ud.ud_user_id = ua.ua_id
and upper(ua.oracle_user) = upper(USER);
return 'EXISTS (SELECT 1 FROM TABLE(scott_allowed.get_allowed_id) a WHERE a.column_value = ud_id)';
END scott_predicate_allow;
/
Function created.
The first thing that was done was to declare a new data type that reflects a single-column table of type NUMBER . This type will be used for a package variable in which a list of identifiers will be selected by which access will be granted. The function in the package is a wrapper that allows you to call back data. There will be no problems with the contents of the package variable between sessions, since a separate memory area is allocated for each session. Assuming that the purpose of the cursor in the predicate function is to determine the identifiers of the primary key of the table, it is more advisable to modify the generated constraint into the EXISTS construct .
Now check the result of the work done by connecting on behalf of SCOTT :
select ud_id, ud_user_id, ud_data from user_data;
1 row selected.