|
|
|
Managing Row Level Security in
Oracle8i
By Michael R. Ault, TUSC
Introduction
New in Oracle8i is the concept of row level access restriction. For years DBAs have requested some form of conditional grant where access to specific rows can be easily restricted or granted based on user or group membership. Oracle has finally given DBAs the functionality of conditional grants in the form of row level security. The DBMS_RLS package is only available with the Enterprise edition of Oracle8i.
Fine Grained Access Control (Row Level Security)
The concept of row level security uses dynamic predicates. Dynamic predicates are created by a function that is executed whenever a specific table of view is accessed. The predicate returned by the function creates what is known as a transitive view. The transient view can preserve the updatability of the parent join object because it is derived from a single table or view with predicate only; i.e., no JOIN, ORDER BY, GROUP BY, etc.
The DBMS_RLS procedures cause current DML transactions, if any, to commit before the operation. However, the procedures do not cause a commit first if they are inside a DDL event trigger. With DDL transactions, the DBMS_RLS procedures are part of the DDL transaction.
For example, you may create a trigger for CREATE TABLE. Inside the trigger, you may add a column through ALTER TABLE, and you can add a policy through DBMS_RLS. All of these operations are in the same transaction as CREATE TABLE, even though each one is a DDL statement. The CREATE TABLE succeeds only if the trigger is completed successfully.
Row level security is managed using a combination of Oracle8i contexts, stored procedures, database level triggers and the DBMS_RLS package. The entire row level security concept is tightly bound to the concept of a database policy. Generally speaking a policy will require:
Row level security control depends on certain environment variables, known as contexts, to be set. The DBMS_CONTEXT package is used to set the various context variables used by the RLS policy. Figure 1 shows a flowchart of how to implement a simple security policy.

Figure 1
As you can see the process is not very complex. Let's examine each step and see what is really involved.
Step 1
In the first step a context package or procedure is developed which is used by a login trigger to set each user's context variables. This step is vital in that if the context variables aren't set it is many times more difficult to implement row level security using the DBMS_RLS package. The package or procedure used to set the context variables should resemble the one shown below:
CREATE OR REPLACE PACKAGE graphics_app AUTHID DEFINER AS
PROCEDURE get_graphics_function(usern IN VARCHAR2,
graphics_function OUT VARCHAR2);
PROCEDURE set_graphics_context(usern IN VARCHAR2);
END;
/
SET ARRAYSIZE 1
SHO ERR
CREATE OR REPLACE PACKAGE BODY graphics_app AS
graphics_user VARCHAR2(32);
graphics_function VARCHAR2(32);
PROCEDURE get_graphics_function(usern IN VARCHAR2, graphics_function OUT VARCHAR2) IS
BEGIN
SELECT user_function INTO graphics_function FROM graphics_dba.graphics_users
WHERE username=usern;
END get_graphics_function;
PROCEDURE set_graphics_context(usern IN VARCHAR2) IS
BEGIN
graphics_app.get_graphics_function(usern,graphics_function);
DBMS_SESSION.SET_CONTEXT('GRAPHICS_SEC','GRAPHICS_FUNCTION',graphics_function);
DBMS_SESSION.SET_CONTEXT('GRAPHICS_SEC','GRAPHICS_USER',usern);
END set_graphics_context;
END graphics_app;
/
SHOW ERRIn the above package are tow procedures, one that retrieves a user's graphics function from a pre-built and populated table (GET_GRAPHICS_FUNCTION) and the other which is used to set the users context variables based on using the DBMS_SESSION.SET_CONTEXT procedure provided by Oracle (SET_GRAPHICS_CONTEXT).
Step 2
Of course the procedures above wouldn't be much use without a trigger that could run the procedure whenever a user logged on the system. Until Oracle8i this would have involved setting auditing on for login, moving the aud$ table from SYS ownership and setting the ownership to another user, resetting all of the synonyms point to aud$ and then building an on-insert trigger to perform the actual work. In Oracle8i all we have to do is build a database level trigger similar to the one shown below. An important point to notice is that the trigger includes a WHEN OTHERS construct to handle exceptions. Without the WHEN OTHERS exception a database login trigger that generates an error would result in the user not being able to log on to the database. Only the INTERNAL or SYS users can log in if there is an error in the logon trigger.
CREATE OR REPLACE TRIGGER set_graphics_context AFTER LOGON ON DATABASE
DECLARE
username VARCHAR2(30);
BEGIN
username:=SYS_CONTEXT('USERENV','SESSION_USER');
graphics_app.set_graphics_context(username);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/In the trigger shown above, the logon event causes the user's session user name to be placed into an internal variable called username. The username variable is then passed into the graphics_app.set_graphics_context procedure from step 1 which then sets the users context variables.
Step 3
Once we have an operating context setting package and a database login trigger we can proceed to create the required context checking package and the context it checks. Below is an example context checking package.
CREATE OR REPLACE PACKAGE graphics_sec AUTHID DEFINER AS
FUNCTION graphics_check(obj_schema VARCHAR2, obj_name VARCHAR2)
RETURN VARCHAR2;
PRAGMA RESTRICT_REFERENCES(GRAPHICS_CHECK,WNDS);
END;
/
SET ARRAYSIZE 1
SHOW ERR
CREATE OR REPLACE PACKAGE BODY graphics_sec AS
FUNCTION graphics_check(obj_schema VARCHAR2, obj_name VARCHAR2)
RETURN VARCHAR2 AS
d_predicate VARCHAR2(2000);
user_context VARCHAR2(32);
BEGIN
user_context:=SYS_CONTEXT('graphics_sec','graphics_function');
IF user_context = 'ADMIN' THEN
d_predicate IS NULL;
dbms_output.put_line(d_predicate);
ELSIF user_context = 'GENERAL USER' THEN
d_predicate:=' graphics_usage='||chr(39)||'UNRESTRICTED'||chr(39);
dbms_output.put_line(d_predicate);
ELSIF user_context='DEVELOPER' THEN
d_predicate IS NULL;
dbms_output.put_line(d_predicate);
ELSIF user_context IS NULL THEN
d_predicate:='1=2';
END IF;
RETURN d_predicate;
END graphics_check;
END;
/
SHOW ERRThe entire purpose of the package above is to return a d_predicate value based on a users graphics_function context value. The d_predicate value is appended to whatever WHERE clause is included with their command, or is appended as a WHERE clause whenever there is no pre-existing clause. In the above package the function graphics_check looks at the context values set by the logon trigger and based the value of the user_context (ADMIN, GENERAL USER or DEVELOPER) sets the value of d_predicate. By setting the predicate to NULL it effectively mitigates the restriction. The user of the '1=2' predicate prohibits selection from the table. The pragma restriction clause is required for the function to be available.
Step 4
The creation of our graphics security context is rather simple once we have finished the preliminary work, it boils down to one command:
CREATE OR REPLACE CONTEXT graphics_sec USING sys.graphics_app;
Step 5
The final step is to set the policy into the database. This is done with the DBMS_RLS package using the procedure ADD_POLICY:
BEGIN
dbms_rls.add_policy('GRAPHICS_DBA','INTERNAL_GRAPHICS','GRAPHICS_POLICY', 'GRAPHICS_DBA','GRAPHICS_SEC.GRAPHICS_CHECK','SELECT,INSERT,UPDATE,DELETE');
END;The above policy simply ties the components we previously defined into a coherent entity called GRAPHICS_POLICY and implements this policy against the table INTERNAL_GRAPHICS which is in the schema GRAPHICS_DBA. The policy GRAPHICS_POLICY is owned by GRAPHICS_DBA and uses the procedure GRAPHICS_SEC.GRAPHICS_CHECK to verify users can perform SELECT, INSERT, UPDATE and DELETE operations.
The table graphics_users is required in the above example. The table contains the username and their graphics function.
Policy Usage
Policy usage is controlled internally by the Oracle system and adheres to the following usage guidelines:
FUNCTION policy_function (object_schema IN VARCHAR2, object_name VARCHAR2)
RETURN VARCHAR2
Where:
DBMS_RLS Package
The entire concept of row level security is based on the use of policies stored in the database. The only way to store policies in the database is to use the DBMS_RLS package.
The DBMS_RLS procedures cause current DML transactions, if any, to commit before the operation. However, the procedures do not cause a commit first if they are inside a DDL event trigger. With DDL transactions, the DBMS_RLS procedures are part of the DDL transaction.
For example, you may create a trigger for CREATE TABLE. Inside the trigger, you may add a column through ALTER TABLE, and you can add a policy through DBMS_RLS. All these operations are in the same transaction as CREATE TABLE, even though each one is a DDL statement. The CREATE TABLE succeeds only if the trigger is completed successfully.
The DBMS_RLS package has the procedures shown in the following table.
| Procedure | Purpose |
| ADD_POLICY | Creates a fine-grained access control policy to a table or view. |
| DROP_POLICY | Drops a fine-grained access control policy from a table or view. |
| REFRESH_POLICY | Causes all the cached statements associated with the policy to be re-parsed. |
| ENABLE_POLICY | Enables or disables a fine-grained access control policy. |
Syntax for the ADD_POLICY Procedure:
DBMS_RLS.ADD_POLICY (
object_schema IN VARCHAR2 := NULL,
object_name IN VARCHAR2,
policy_name IN VARCHAR2,
function_schema IN VARCHAR2 := NULL,
policy_function IN VARCHAR2,
statement_types IN VARCHAR2 := NULL,
update_check IN BOOLEAN := FALSE,
enable IN BOOLEAN := TRUE);Where:
Syntax for the DROP_POLICY Procedure:
DBMS_RLS.DROP_POLICY (
object_schema IN VARCHAR2 := NULL,
object_name IN VARCHAR2,
policy_name IN VARCHAR2);Where:
Syntax for the REFRESH_POLICY Procedure:
DBMS_RLS.REFRESH_POLICY (
object_schema IN VARCHAR2 := NULL,
object_name IN VARCHAR2 := NULL,
policy_name IN VARCHAR2 := NULL);Where:
Syntax for the ENABLE_POLICY Procedure:
DBMS_RLS.ENABLE_POLICY (
object_schema IN VARCHAR2 := NULL,
object_name IN VARCHAR2,
policy_name IN VARCHAR2,
enable IN BOOLEAN);Where:
Through the use of the above procedures, DBAs and developers can easily manage policies.
Summary
Oracle has given DBAs and developers a powerful new tool to manage row level security. This new tool is a combination of contexts, triggers and packages and a new package named DBMS_RLS through which security policies are implemented.
Through the proper use of policies, contexts, packages, and database level triggers, row level security can be easily integrated in Oracle8i applications.
(The above article was excerpted from the book: "Oracle8i Administration and Management", Michael R. Ault, John Wiley and Sons Publishers).
Michael Ault can be contacted through the web page http://www.tusc.com or via email at aultm@tusc.com.