Well, I've a couple of things to say for this one....
One is, if you haven't, you might want to read:
</code>
http://asktom.oracle.com/~tkyte/article2/index.html <code>
That is a paper I've written on fine grained access control. One of the topics I have in there is "Handling Errors and debugging". In the debugging part (which is really hard with FGAC), I suggest using a "debug.f" routine we have. It lets you easily create trace files from PLSQL. I'm going to use it to demonstrate to you what is happening.
In a nutshell -- user_* views, when queried in PLSQL, always return the rows
for the user who created the procedure (unless they are invokers rights but thats another story). So, by querying user_* views, you've effectively fixed the rows that can be returned
at compile time. You need to query the DBA_* views in order to see anyones privs. In fact, since the dba_user_privs view performs pretty poorly, I'm going to suggest going directly after the underlying tables in this case (a rarity).
Lets start by writing a policy that simply dumps the user_roles_privs view, the dba_roles_privs for the current user and the modified query to get the roles. We'll time them as well. That policy is:
ops$tkyte@DEV8I.WORLD> create or replace
2 function my_security_function( p_schema in varchar2,
3 p_object in varchar2 )
4 return varchar2
5 as
6 l_start number;
7 begin
8 debug.f( 'User Role Privs' );
9 l_start := dbms_utility.get_time;
10 for x in ( select * from user_role_privs )
11 loop
12 debug.f( 'Username = "%s" Granted_Role = "%s"',
13 x.username, x.granted_role );
14 end loop;
15 debug.f( 'end, time = %d',
16 round( (dbms_utility.get_time-l_start)/100, 2 ) );
17
18 debug.f( 'Dba Role Privs' );
19 l_start := dbms_utility.get_time;
20 for x in ( select *
21 from dba_role_privs
22 where grantee =
23 sys_context('userenv','session_user') )
24 loop
25 debug.f( 'Username = "%s" Granted_Role = "%s"',
26 x.grantee, x.granted_role );
27 end loop;
28 debug.f( 'end, time = %d',
29 round( (dbms_utility.get_time-l_start)/100, 2 ) );
30
31 debug.f( 'Direct query' );
32 l_start := dbms_utility.get_time;
33 for x in ( select u1.name granted_role,u2.name grantee
34 from sys.user$ u1,
35 sys.user$ u2,
36 sys.sysauth$ sa
37 where u1.user# = sa.privilege#
38 and u2.user# = sa.grantee#
39 and u2.name =
40 sys_context('userenv','session_user') )
41 loop
42 debug.f( 'Username = "%s" Granted_Role = "%s"',
43 x.grantee, x.granted_role );
44 end loop;
45 debug.f( 'end, time = %d',
46 round( (dbms_utility.get_time-l_start)/100, 2 ) );
47
48
49 return '';
50 end;
51 /
Function created.
Now, lets create a table and attach this policy on it:ops$tkyte@DEV8I.WORLD> drop table t;
Table dropped.
ops$tkyte@DEV8I.WORLD> create table t ( x int );
Table created.
ops$tkyte@DEV8I.WORLD> grant all on t to public;
Grant succeeded.
ops$tkyte@DEV8I.WORLD> begin
2 dbms_rls.add_policy
3 ( object_schema => user,
4 object_name => 'T',
5 policy_name => 'MY_POLICY',
6 function_schema => user,
7 policy_function => 'My_Security_Function',
8 statement_types => 'select, insert, update, delete' ,
9 update_check => TRUE );
10 end;
11 /
PL/SQL procedure successfully completed.
Now, lets set up to "trace". I'll enable tracing for all modules to the file /tmp/tkyte.dbg for the current user (me)ops$tkyte@DEV8I.WORLD> host rm /tmp/tkyte.dbg
ops$tkyte@DEV8I.WORLD> exec debug.init( 'all', '/tmp/tkyte.dbg', USER );
PL/SQL procedure successfully completed.
ops$tkyte@DEV8I.WORLD> select * from t;
no rows selected
Now, lets see what we got:ops$tkyte@DEV8I.WORLD> host cat /tmp/tkyte.dbg
User Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
Username = "PUBLIC" Granted_Role = "IAP_USER"
Username = "PUBLIC" Granted_Role = "PLUSTRACE"
Username = "PUBLIC" Granted_Role = "SURVEY_USER"
Username = "PUBLIC" Granted_Role = "WWV_PALM_SYNC_ROLE"
end, time = 2.29
Dba Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
end, time = 1.9
Direct query
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
end, time = .01
That looks OK so far -- user_role_privs and dba_role_privs agree with each other so far. But thats ONLY because the currently logged in user = the owner of the procedure.
I would like to point out that in my database I have over 30k users created. The performance of the *_role_privs view is not very good in my case. It takes far too long to get the answer. The last query, directly against the SYS tables is very very fast however...
Now, lets try this as another user and you'll see where you went wrong...ops$tkyte@DEV8I.WORLD> connect scott/tiger
Connected.
ops$tkyte@DEV8I.WORLD> @login
scott@DEV8I.WORLD> host rm /tmp/scott.dbg
scott@DEV8I.WORLD> exec debug.init( 'all', '/tmp/scott.dbg', USER );
PL/SQL procedure successfully completed.
scott@DEV8I.WORLD> select * from ops$tkyte.t;
no rows selected
scott@DEV8I.WORLD> host cat /tmp/scott.dbg
User Role Privs
Username = "OPS$TKYTE" Granted_Role = "CONNECT"
Username = "OPS$TKYTE" Granted_Role = "DBA"
Username = "PUBLIC" Granted_Role = "IAP_USER"
Username = "PUBLIC" Granted_Role = "PLUSTRACE"
Username = "PUBLIC" Granted_Role = "SURVEY_USER"
Username = "PUBLIC" Granted_Role = "WWV_PALM_SYNC_ROLE"
end, time = 2.33
Notice how the username above is still OPS$TKYTE -- thats cause the USER_* view in a procedure will return the data for the owner of the procedure (except with invokers rights..)Dba Role Privs
Username = "SCOTT" Granted_Role = "CONNECT"
Username = "SCOTT" Granted_Role = "DBA"
Username = "SCOTT" Granted_Role = "RESOURCE"
end, time = 1.89
But dba role privs gives us the correct info (what we wanted) albeit slowly...Direct query
Username = "SCOTT" Granted_Role = "CONNECT"
Username = "SCOTT" Granted_Role = "RESOURCE"
Username = "SCOTT" Granted_Role = "DBA"
end, time = .03
The direct query gives us what we want but fast...scott@DEV8I.WORLD>