Invoker and Definer Rights

advertisement
Invoker and Definer Rights
To begin with, some definitions are called for to ensure that we all understand exactly the same thing by
the terms invoker and definer:
❑
Definer – The schema (username) of the owner of the compiled stored object. Compiled
stored objects include packages, procedures, functions, triggers, and views.
❑
Invoker – The schema whose privileges are currently in effect in the session. This may or may
not be the same as the currently logged in user.
Prior to Oracle 8i, all compiled stored objects were executed with the privileges and name resolution of
the definer of the object. That is, the set of privileges granted directly to the owner (definer) of the
stored object were used at compile-time to figure out:
❑
What objects (tables, and so on) to actually access.
❑
Whether the definer had the necessary privileges to access them.
This static, compile-time binding went as far as to limit the set of privileges to only those granted to the
definer directly (in other words, no roles were ever enabled during the compilation or execution of a
stored procedure). Additionally, when anyone executes a routine with definer's rights, this routine will
execute with the base set of privileges of the definer of the routine, not the invoker that executes the
procedure.
Beginning in Oracle 8i, we have a feature called invoker rights, which allow us to create procedures,
packages, and functions that execute with the privilege set of the invoker at run-time, rather than the
definer. In this chapter we will look at:
❑
5254ch23cmp2.pdf 1
When you should use invoker rights routines, covering uses such as data dictionary
applications, generic object types, and implementing your own 'access control'.
2/28/2005 6:52:59 PM
Chapter 23
❑
When to use definer rights procedures, covering their scalability compared to invoker rights,
and also ways in which they can implement security on the database.
❑
How each of these features works.
❑
Issues which need to be considered when implementing the feature, such as considering the
shared pool utilization, performance of procedures, the need for greater error handling
abilities in the code, and also using Java to implement invoker rights.
An Example
With the introduction of invoker rights, we can now, for example, develop a stored procedure that
executes with the privilege set of the invoker at run-time. This allows us to create a stored procedure
that might execute properly and correctly for one user (one who had access to all of the relevant
objects), but not for another (who didn't). The reason we can do this is that access to the underlying
objects is not defined at compile-time, (although the definer must have access to these objects, or at least
objects with these names, in order to compile the PL/SQL code), but rather at run-time. This run-time
access is based on the privileges and roles of the current schema/user in effect. It should be noted that
invoker rights are not available in the creation of views or triggers. Views and triggers are created with
definer rights only.
This invoker rights feature is very easy to implement and test, as it only demands you add one line to a
procedure or package to use it. For example, consider the following routine, which prints out:
❑
CURRENT_USER – The name of the user under whose privileges the session is currently
executing.
❑
SESSION_USER – The name of the user who originally created this session, who is logged in.
This is constant for a session.
❑
CURRENT_SCHEMA – The name of the default schema that will be used to resolve references to
unqualified objects.
To create the procedure with definer rights, we would code:
tkyte@TKYTE816> create or replace procedure definer_proc
2 as
3 begin
4
for x in
5
( select sys_context( 'userenv', 'current_user' ) current_user,
6
sys_context( 'userenv', 'session_user' ) session_user,
7
sys_context( 'userenv', 'current_schema' ) current_schema
8
from dual )
9
loop
10
dbms_output.put_line( 'Current User:
' || x.current_user );
11
dbms_output.put_line( 'Session User:
' || x.session_user );
12
dbms_output.put_line( 'Current Schema: ' || x.current_schema );
13
end loop;
14 end;
15 /
Procedure created.
tkyte@TKYTE816> grant execute on definer_proc to scott;
Grant succeeded.
982
5254ch23cmp2.pdf 2
2/28/2005 6:52:59 PM
Invoker and Definer Rights
To create the same procedure with invoker rights, you would code:
tkyte@TKYTE816> create or replace procedure invoker_proc
2 AUTHID CURRENT_USER
3 as
4 begin
5
for x in
6
( select sys_context( 'userenv', 'current_user' ) current_user,
7
sys_context( 'userenv', 'session_user' ) session_user,
8
sys_context( 'userenv', 'current_schema' ) current_schema
9
from dual )
10
loop
11
dbms_output.put_line( 'Current User:
' || x.current_user );
12
dbms_output.put_line( 'Session User:
' || x.session_user );
13
dbms_output.put_line( 'Current Schema: ' || x.current_schema );
14
end loop;
15 end;
16 /
Procedure created.
tkyte@TKYTE816> grant execute on invoker_proc to scott;
Grant succeeded.
That's it; one line and the procedure will now execute with the privileges and name resolution of the
invoker, not the definer. To see exactly what this means, we'll run the above routines and examine the
two outputs. First the definer rights routine:
tkyte@TKYTE816> connect scott/tiger
scott@TKYTE816>
Current User:
Session User:
Current Schema:
exec tkyte.definer_proc
TKYTE
SCOTT
TKYTE
PL/SQL procedure successfully completed.
For the definer rights procedure, the current user, and the schema whose privileges the session is
currently executing under, is TKYTE inside of the procedure. The session user is the logged on user,
SCOTT, which will be constant for this session. In this scenario, all unqualified schema references will be
resolved using TKYTE as the schema (for example, the query SELECT * FROM T will be resolved as
SELECT * FROM TKYTE.T).
The invoker rights routine behaves very differently:
scott@TKYTE816>
Current User:
Session User:
Current Schema:
exec tkyte.invoker_proc
SCOTT
SCOTT
SCOTT
PL/SQL procedure successfully completed.
983
5254ch23cmp2.pdf 3
2/28/2005 6:53:00 PM
Chapter 23
The current user is SCOTT, not TKYTE. The current user in the invoker rights routine will be different
for every user that directly runs this procedure. The session user is SCOTT, as expected. The current
schema however, is also SCOTT, meaning that if this procedure executed the SELECT * FROM T, it would
execute as SELECT * FROM SCOTT.T. This shows the fundamental differences between a definer and an
invoker rights routine – the schema whose privileges the procedure executes under is the invoker of the
routine. Also, the current schema is dependent on the invoker of the routine. Different objects may be
accessed via this routine when executed by different users.
Additionally, it is interesting to see the effect that changing our current schema has on these routines:
scott@TKYTE816> alter session set current_schema = system;
Session altered.
scott@TKYTE816>
Current User:
Session User:
Current Schema:
exec tkyte.definer_proc
TKYTE
SCOTT
TKYTE
PL/SQL procedure successfully completed.
scott@TKYTE816>
Current User:
Session User:
Current Schema:
exec tkyte.invoker_proc
SCOTT
SCOTT
SYSTEM
PL/SQL procedure successfully completed.
As you can see, the definer rights routine does not change its behavior at all. Definer rights procedures
are 'static' with regards to the current user, and the current schema. These are fixed at compile-time and
are not affected by subsequent changes in the current environment. The invoker rights routine, on the
other hand, is much more dynamic. The current user is set according to the invoker at run-time, and the
current schema may change from execution to execution, even within the same session.
This is an extremely powerful construct when used correctly, and in the correct places. It allows
PL/SQL stored procedures and packages to behave more like a compiled Pro*C application might. A
Pro*C application (or ODBC, JDBC, or any 3GL) executes with the privilege set and name resolution of
the currently logged in user (invoker). We can now write code in PL/SQL which, in the past, we had to
write using a 3GL outside of the database.
When to Use Invoker Rights
In this section we will explore the various reasons and cases where you might choose to use this feature.
We will concentrate on invoker rights since it is new and is still the exception. Stored procedures have
previously always been executed in Oracle using definer rights.
The need for invoker rights arises most often when some generic piece of code is to be developed by
one person but reused by many others. The developer will not have access to the objects that the end
users will have. Rather, the end users' privileges will determine which objects this code may access.
Another potential use of this feature is to produce a single set of routines that will centralize data
retrieval from many different schemas. With definer rights procedures, we have seen how the current
984
5254ch23cmp2.pdf 4
2/28/2005 6:53:00 PM
Invoker and Definer Rights
user (the privilege user) and the current schema (the schema used to resolve unqualified references) are
static, fixed at the time of compilation. A definer rights procedure would access the same set of objects
each time it was executed (unless you wrote dynamic SQL of course). An invoker rights routine allows
you to write one routine that can access similar structures in different schemas, based on who executes
the procedure.
So, lets take a look at some typical cases where you will use invoker rights routines.
Developing Generic Utilities
In this case, you might develop a stored procedure that uses dynamic SQL to take any query, execute it,
and produce a comma-separated file. Without invoker rights, one of the following would have to be true
in order to allow this procedure to be generally useful to everyone:
❑
The definer of the procedure would have to have read access to virtually every object in the
database – For example, via the SELECT ANY TABLE privilege. Otherwise, when we run this
procedure to produce a flat file from some table, the procedure would fail because the definer
lacked the necessary SELECT privileges on this particular table. Here, we would like the
procedure to execute with our privileges, not the definer's privileges.
❑
Everyone would have the source code and be able to install their own copy of the code – This is
undesirable for obvious reasons – it produces a maintenance nightmare. If a bug is found in
the original code, or an upgrade changes the way in which the code must be written, we now
have many dozens of copies to go out and 'upgrade'. Additionally, objects we could access via
a role will still not be available to us in this copied procedure.
In general, the second option above was the most frequently applied method of developing generic
code. This is not a very satisfying approach, but is the 'safest' from a security perspective. Using invoker
rights procedures however, I now can write that routine once, grant execute on it to many people, and
they can use it with their own privileges and name resolution. We'll look at a small example here. I
frequently need to view tables in SQL*PLUS that are very 'wide', in other words, they have many
columns. If I just do a SELECT * FROM T on that table, SQL*PLUS will wrap all of the data on my
terminal. For example:
tkyte@DEV816> select * from dba_tablespaces where rownum = 1;
TABLESPACE_NAME
INITIAL_EXTENT
------------------------------ -------------MAX_EXTENTS PCT_INCREASE MIN_EXTLEN STATUS
----------- ------------ ---------- --------EXTENT_MAN ALLOCATIO PLU
---------- --------- --SYSTEM
16384
505
50
0 ONLINE
DICTIONARY USER
NO
NEXT_EXTENT MIN_EXTENTS
----------- ----------CONTENTS LOGGING
--------- ---------
16384
PERMANENT LOGGING
1
All of the data is there, but it is extremely hard to read. What if I could get the output like this instead:
tkyte@DEV816> exec print_table('select * from dba_tablespaces where rownum = 1');
TABLESPACE_NAME
: SYSTEM
985
5254ch23cmp2.pdf 5
2/28/2005 6:53:00 PM
Chapter 23
INITIAL_EXTENT
NEXT_EXTENT
MIN_EXTENTS
MAX_EXTENTS
PCT_INCREASE
MIN_EXTLEN
STATUS
CONTENTS
LOGGING
EXTENT_MANAGEMENT
ALLOCATION_TYPE
PLUGGED_IN
-----------------
:
:
:
:
:
:
:
:
:
:
:
:
16384
16384
1
505
50
0
ONLINE
PERMANENT
LOGGING
DICTIONARY
USER
NO
PL/SQL procedure successfully completed.
Now, that's more like it! I can actually see what column is what. Every time someone sees me using my
PRINT_TABLE procedure, they want a copy. Rather then give them the code, I tell them to just use mine
since it was created using AUTHID CURRENT_USER. I do not need access to their tables. This procedure
will be able to access them (not only that but it can access tables via a role, something a definer rights
procedure cannot do). Let us look at the code, and see how it behaves. We'll start by creating a utility
account to hold this generic code as well as an account that can be used to test the security features:
tkyte@TKYTE816> grant connect to another_user identified by another_user;
Grant succeeded.
tkyte@TKYTE816> create user utils_acct identified by utils_acct;
User created.
tkyte@TKYTE816> grant create session, create procedure to utils_acct;
Grant succeeded.
What I have done here is to create a user with very few privileges. Just enough to log on and create a
procedure. I will now install the utility code into this schema:
tkyte@TKYTE816> utils_acct/utils_acct
utils_acct@TKYTE816> create or replace
2 procedure print_table( p_query in varchar2 )
3 AUTHID CURRENT_USER
4 is
5
l_theCursor
integer default dbms_sql.open_cursor;
6
l_columnValue
varchar2(4000);
7
l_status
integer;
8
l_descTbl
dbms_sql.desc_tab;
9
l_colCnt
number;
10 begin
11
dbms_sql.parse( l_theCursor, p_query, dbms_sql.native );
12
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl);
13
14
for i in 1 .. l_colCnt loop
986
5254ch23cmp2.pdf 6
2/28/2005 6:53:00 PM
Invoker and Definer Rights
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
dbms_output.put_line( rpad( l_descTbl(i).col_name, 30 )
|| ': ' ||
l_columnValue );
end loop;
dbms_output.put_line( '-----------------' );
end loop;
exception
when others then
dbms_sql.close_cursor( l_theCursor );
RAISE;
end;
/
Procedure created.
utils_acct@TKYTE816> grant execute on print_table to public;
Grant succeeded.
I'll now go one step further. I'll actually make it so that we cannot log in to the UTILS_ACCT account at
all. This will prevent a normal user from guessing the UTILS_ACCT password, and placing a Trojan
horse in place of the PRINT_TABLE procedure. Of course, a DBA with the appropriate privileges will be
able to reactivate this account and log in as this user anyway – there is no way to prevent this:
utils_acct@TKYTE816> connect tkyte/tkyte
tkyte@TKYTE816> revoke create session, create procedure
2 from utils_acct;
Revoke succeeded.
So, what we have is an account with some code in it but that is effectively locked, since it no longer has
CREATE SESSION privileges. When we log in as SCOTT, we'll find that not only can we still use this
procedure (even though UTILS_ACCT is a non-functional account with no privileges), but also that it can
access our tables. We will then verify that other users cannot use it to access our tables as well (unless
they could do so with a straight query), thus showing the procedure executes with the privileges of the
invoker:
scott@TKYTE816> exec utils_acct.print_table('select * from scott.dept')
DEPTNO
: 10
DNAME
: ACCOUNTING
LOC
: NEW YORK
----------------...
PL/SQL procedure successfully completed.
987
5254ch23cmp2.pdf 7
2/28/2005 6:53:00 PM
Chapter 23
This shows that SCOTT can use the procedure, and it can access SCOTT's objects. However,
ANOTHER_USER might discover the following:
scott@TKYTE816> connect another_user/another_user
another_user@TKYTE816> desc scott.dept
ERROR:
ORA-04043: object scott.dept does not exist
another_user@TKYTE816> set serverout on
another_user@TKYTE816> exec utils_acct.print_table('select * from scott.dept' );
BEGIN utils_acct.print_table('select * from scott.dept' ); END;
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at “UTILS_ACCT.PRINT_TABLE”, line 31
ORA-06512: at line 1
Any user in the database who does not have access to SCOTT's tables cannot use this routine to get
access to it. For completeness, we'll log back in as SCOTT, and grant ANOTHER_USER the appropriate
privilege to complete the example:
another_user@TKYTE816> connect scott/tiger
scott@TKYTE816> grant select on dept to another_user;
Grant succeeded.
scott@TKYTE816> connect another_user/another_user
another_user@TKYTE816> exec utils_acct.print_table('select * from scott.dept' );
DEPTNO
: 10
DNAME
: ACCOUNTING
LOC
: NEW YORK
----------------...
PL/SQL procedure successfully completed.
This effectively shows the use of invoker rights with regards to generic applications.
Data Dictionary Applications
People have always wanted to create procedures that would display the information in the data
dictionary in a nicer format than a simple SELECT can achieve, or to create a DDL extraction tool
perhaps. With definer rights procedures, this was very difficult. If you used the USER_* views (for
example, USER_TABLES), the tables would be the set that the definer of the procedure owned, and
never the invoker's tables. This is because the USER_* and ALL_* views all include in their predicate:
where o.owner# = userenv('SCHEMAID')
988
5254ch23cmp2.pdf 8
2/28/2005 6:53:00 PM
Invoker and Definer Rights
The USERENV('SCHEMAID') function returns the user ID of the schema under which the procedure
executes. In a stored procedure that is defined with definer rights (the default), and this was, in effect, a
constant value – it would always be the user ID of the person who owned the procedure. This means
any procedure they write, which accesses the data dictionary would see their objects, never the objects
of the person executing the query. Furthermore, roles were never active (we will revisit this fact below)
inside of a stored procedure, so if you had access to a table in someone else's schema via a role, your
stored procedure could not see that object. In the past, the only solution to this conundrum, was to
create the stored procedure on the DBA_* views (after getting direct grants on all of them), and
implementing your own security, to ensure people could only see what they would have seen via the
ALL_* or USER_* views. This is less than desirable, as it leads to writing lots of code, getting a grant on
each of the DBA_* tables, and, unless you are careful, you will risk exposing objects that should not be
visible.
Invoker rights to the rescue here. Now, not only can we create a stored procedure that accesses the
ALL_* and USER_* views, we can do it as the currently logged in user, using their privileges, and even
their roles. We will demonstrate this with the implementation of a 'better' DESCRIBE command. This is
will be the minimal implementation – once you see what it can do, you can make it do anything you
want:
tkyte@TKYTE816> create or replace
2 procedure desc_table( p_tname in varchar2 )
3 AUTHID CURRENT_USER
4 as
5 begin
6
dbms_output.put_line('Datatypes for Table ' || p_tname );
7
dbms_output.new_line;
8
9
dbms_output.put_line( rpad('Column Name',31) ||
10
rpad('Datatype',20)
||
11
rpad('Length',11) ||
12
'Nullable' );
13
dbms_output.put_line( rpad('-',30,'-') || ' ' ||
14
rpad('-',19,'-') || ' ' ||
15
rpad('-',10,'-') || ' ' ||
16
'--------' );
17
for x in
18
( select column_name,
19
data_type,
20
substr(
21
decode( data_type,
22
'NUMBER', decode( data_precision, NULL, NULL,
23
'('||data_precision||','||data_scale||')' ),
24
data_length),1,11) data_length,
25
decode( nullable,'Y','null','not null') nullable
26
from user_tab_columns
27
where table_name = upper(p_tname)
28
order by column_id )
29
loop
30
dbms_output.put_line( rpad(x.column_name,31) ||
31
rpad(x.data_type,20)
||
32
rpad(x.data_length,11) ||
33
x.nullable );
34
end loop;
989
5254ch23cmp2.pdf 9
2/28/2005 6:53:00 PM
Chapter 23
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
dbms_output.put_line( chr(10) || chr(10) ||
'Indexes on ' || p_tname );
for z in
( select a.index_name, a.uniqueness
from user_indexes a
where a.table_name = upper(p_tname)
and index_type = 'NORMAL' )
loop
dbms_output.put( rpad(z.index_name,31) ||
z.uniqueness );
for y in
( select decode(column_position,1,'(',', ')||
column_name column_name
from user_ind_columns b
where b.index_name = z.index_name
order by column_position )
loop
dbms_output.put( y.column_name );
end loop;
dbms_output.put_line( ')' || chr(10) );
end loop;
end;
/
Procedure created.
tkyte@TKYTE816> grant execute on desc_table to public
2 /
Grant succeeded.
This procedure queries the USER_INDEXES and USER_IND_COLUMNS views heavily. Under definer
rights (without the AUTHID CURRENT_USER) this procedure would be able to show the information for
only one user (and always the same user). In the invoker rights model, however, this procedure will
execute with the identity and privileges of the user who is logged in at run-time. So, even though TKYTE
owns this procedure, we can execute it as the user SCOTT, and receive output similar to the following:
tkyte@TKYTE816> connect scott/tiger
scott@TKYTE816> set serveroutput on format wrapped
scott@TKYTE816> exec tkyte.desc_table( 'emp' )
Datatypes for Table emp
Column Name
-----------------------------EMPNO
ENAME
JOB
MGR
HIREDATE
SAL
COMM
Datatype
------------------NUMBER
VARCHAR2
VARCHAR2
NUMBER
DATE
NUMBER
NUMBER
Length
---------(4,0)
10
9
(4,0)
7
(7,2)
(7,2)
Nullable
-------not null
null
null
null
null
null
null
990
5254ch23cmp2.pdf 10
2/28/2005 6:53:01 PM
Invoker and Definer Rights
DEPTNO
NUMBER
Indexes on emp
EMP_PK
UNIQUE(EMPNO)
(2,0)
null
PL/SQL procedure successfully completed.
Generic Object Types
The reasoning here is similar to above, but is more powerful in nature. Using the Oracle 8 feature that
allows you to create your own object types with their own methods for manipulating data, you can now
create member functions and procedures that act under the privilege domain of the currently logged in
user. This allows you to create generic types, install them once in the database, and let everyone use
them. If we did not have invoker rights, the owner of the object type would need to have very powerful
privileges (as described above), or we would have to install the object type into each schema that
wanted it.
Invoker rights is the mode in which the Oracle supplied types (for interMedia support, these are the
ORDSYS.* types) have always operated, making it so that you can install them once per database, and
everyone can use them with their privilege set intact. The relevance of this is that the ORDSYS object
types read and write database tables. The set of database tables that they read and write are totally
dependent on who is running them at the time. This is what allows them to be very generic and general
purpose. The object types are installed in the ORDSYS schema, but ORDSYS does not have access to the
tables on which it actually operates. Now, in Oracle 8i, you can do the same.
Implementing your own Access Control
Oracle 8i introduced a feature called Fine Grained Access Control (FGAC) that allows you to
implement a security policy to prevent unauthorized access to data. Typically, this might be
accomplished by adding a column to every table, say a column named COMPANY. This column would be
populated automatically by a trigger, and every query would be modified to include WHERE COMPANY =
SYS_CONTEXT (...) to restrict access to just the rows the particular user was authorized to access (see
Chapter 21, Fine Grained Access Control, for full details).
Another approach would be to create a schema (set of tables) per company. That is, each company
would get its own copy of the database tables installed, and populated. There would be no chance of
anyone accessing someone else's data, since this data is physically stored in a totally different table. This
approach is very viable and has many advantages (as well as disadvantages) over FGAC. The problem
is, however, that you would like to maintain one set of code for all users. You do not want to have ten
copies of the same large PL/SQL package cached in the shared pool. You do not want to have to
remember to update ten copies of the same code when a bug is found and fixed. You do not want
people to be running potentially different versions of the code at any time. Invoker rights supports this
model (many sets of tables, one copy of code).
With invoker rights, I can write one stored procedure that accesses tables based on the currently logged
in users access privileges and name resolution. As demonstrated in the PRINT_TABLE example, we can
do this in dynamic SQL, but it works with static SQL as well. Consider this example. We will install the
EMP/DEPT tables into both the SCOTT schema as well as my TKYTE schema. A third party will write the
991
5254ch23cmp2.pdf 11
2/28/2005 6:53:01 PM
Chapter 23
application that uses the EMP and DEPT tables to print a report. This third party will not have access to
either SCOTT or TKYTE's EMP or DEPT table, (they will have their own copy for testing). We will see that
when SCOTT executes the procedure, it will display data from SCOTT's schema and when TKYTE
executes the procedure, it utilizes his own tables:
tkyte@TKYTE816> connect scott/tiger
scott@TKYTE816> grant select on emp to public;
Grant succeeded.
scott@TKYTE816> grant select on dept to public;
Grant succeeded.
scott@TKYTE816> connect tkyte/tkyte
tkyte@TKYTE816> create table dept as select * from scott.dept;
Table created.
tkyte@TKYTE816> create table emp as select * from scott.emp;
Table created.
tkyte@TKYTE816> insert into emp select * from emp;
14 rows created.
tkyte@TKYTE816> create user application identified by pw
2
default tablespace users quota unlimited on users;
User created.
tkyte@TKYTE816> grant create session, create table,
2
create procedure to application;
Grant succeeded.
tkyte@TKYTE816> connect application/pw
application@TKYTE816> create table emp as select * from scott.emp where 1=0;
Table created.
application@TKYTE816> create table dept as
2
select * from scott.dept where 1=0;
Table created.
So, at this point we have three users, each with their own EMP/DEPT tables in place. The data in all
three tables is distinctly different. SCOTT has the 'normal' set of EMP data, TKYTE has two times the
normal amount, and APPLICATION has just empty tables. Now, we will create the application:
992
5254ch23cmp2.pdf 12
2/28/2005 6:53:01 PM
Invoker and Definer Rights
application@TKYTE816> create or replace procedure emp_dept_rpt
2 AUTHID CURRENT_USER
3 as
4 begin
5
dbms_output.put_line( 'Salaries and Employee Count by Deptno' );
6
dbms_output.put_line( chr(9)||'Deptno
Salary
Count' );
7
dbms_output.put_line( chr(9)||'----------------' );
8
for x in ( select dept.deptno, sum(sal) sal, count(*) cnt
9
from emp, dept
10
where dept.deptno = emp.deptno
11
group by dept.deptno )
12
loop
13
dbms_output.put_line( chr(9) ||
14
to_char(x.deptno,'99999') || ' ' ||
15
to_char(x.sal,'99,999') || ' ' ||
16
to_char(x.cnt,'99,999') );
17
end loop;
18
dbms_output.put_line( '=====================================' );
19 end;
20 /
Procedure created.
application@TKYTE816> grant execute on emp_dept_rpt to public
2 /
Grant succeeded.
application@TKYTE816> set serveroutput on format wrapped
application@TKYTE816> exec emp_dept_rpt;
Salaries and Employee Count by Deptno
Deptno
Salary
Count
---------------=====================================
PL/SQL procedure successfully completed.
This shows that when APPLICATION executes the procedure it shows the empty tables, as expected.
Now, when SCOTT, and then TKYTE run the same exact application:
tkyte@TKYTE816> connect scott/tiger
scott@TKYTE816> set serveroutput on format wrapped
scott@TKYTE816> exec application.emp_dept_rpt
Salaries and Employee Count by Deptno
Deptno
Salary
Count
---------------10
8,750
3
20 10,875
5
30
9,400
6
=====================================
PL/SQL procedure successfully completed.
scott@TKYTE816> connect tkyte/tkyte
993
5254ch23cmp2.pdf 13
2/28/2005 6:53:01 PM
Chapter 23
tkyte@TKYTE816> set serveroutput on format wrapped
tkyte@TKYTE816> exec application.emp_dept_rpt
Salaries and Employee Count by Deptno
Deptno
Salary
Count
---------------10 17,500
6
20 21,750
10
30 18,800
12
=====================================
PL/SQL procedure successfully completed.
we see that it actually accesses different tables in different schemas. As we will see in the Caveats section,
however, care must be taken to ensure that the schemas are synchronized. Not only must the same table
names exist, but also the data type, order, and number of columns should be the same when using static
SQL.
When to Use Definer Rights
Definer rights routines will continue to be the predominant method used with compiled stored objects.
There are two major reasons for this, both of which address critical issues:
❑
Performance – A database using definer rights routines when possible, will be inherently more
scalable and better performing than a database using invoker rights routines.
❑
Security – Definer rights routines have some very interesting and useful security aspects that
make them the correct choice almost all of the time.
Performance and Scalability
A definer rights procedure is really a great thing in terms of security and performance. In the How they
Work section, we will see that, due to the static binding at compile-time, much in the way of efficiency
can be gained at run-time. All of the security validations, dependency mechanisms, and so on are done
once at compile-time. With an invoker rights routine, much of this work must be done at run-time. Not
only that, but it may have to be performed many times in a single session, after an ALTER SESSION or
SET ROLE command. Anything that can change the run-time execution environment will cause an
invoker rights routine to change its behavior as well. A definer rights routine is static with regards to
this, invoker rights routines are not.
Additionally, as we'll see in the Caveats section later in this chapter, an invoker rights routine will incur
higher shared pool utilization than will a definer rights routine. Since the execution environment of the
definer rights routine is static, all static SQL executed by them is guaranteed to be sharable in the
shared pool. As we have seen in other sections of this book, the shared pool is a data structure we must
take care to not abuse (via the use of bind variables, avoiding excessive parsing, and so on). Using
definer rights routines ensure maximum usage of the shared pool. An invoker rights routine on the
other hand defeats the shared pool in some respects. Instead of the single query SELECT * FROM T
meaning the same thing to all people when it is in a procedure, it may very well mean different things to
different people. We'll have more SQL in our shared pool. Using definer rights procedures ensures the
best overall shared pool utilization.
994
5254ch23cmp2.pdf 14
2/28/2005 6:53:01 PM
Invoker and Definer Rights
Security
In a nutshell, definer rights allow us to create a procedure that operates safely and correctly on some set
of database objects. We can then allow other people to execute this procedure via the GRANT EXECUTE
ON <procedure> TO <user>/public/<role> command. These people can run this procedure to
access our tables in a read/write fashion (via the code in the procedure only), without being able to
actually read or write our tables in any other way. In other words, we have just made a trusted process
that can modify or read our objects in a safe way, and can give other people the permission to execute
this trusted process without having to give them the ability to read or write our objects via any other
method. They will not be using SQL*PLUS to insert into your Human Resources table. The ability to
do this is provided only via your stored procedure, with all of your checks and audits in place. This has
huge implications in the design of your application, and how you allow people to use your data. No
longer would you GRANT INSERT on a table as you do with a client-server application that does straight
SQL inserts. Instead, you would GRANT EXECUTE on a procedure that can validate and verify the data,
implement other auditing and security checks, and not worry about the integrity of your data (your
procedure knows what to do and it's the only game in town).
Compare this to how typical client-server applications, or even many 3-tier applications work. In a
client-sever application, the INSERT, UPDATE and DELETE statements, and so on, are coded directly
into the client application. The end user must have been granted INSERT, UPDATE and DELETE directly
on the base tables in order to run this application. Now the whole world has access to your base tables
via any interface that can log into Oracle. If you use a definer rights procedure, you have no such issue.
Your trusted procedure is the only mechanism for modifying the tables. This is very powerful.
Frequently people ask, 'How can I make it so that only my application myapp.exe is able to perform
operation X in the database?' That is, they want their .exe to be able to INSERT into some table, but
they do not want any other application to be able to do the same thing. The only secure way to do this is
to put the database logic of myapp.exe into the database – do not ever put an INSERT, UPDATE,
DELETE, or SELECT into the client application. Only if you put the application directly in the database,
removing the need for the client application to directly INSERT, or whatever into your table, can you
make it so that only your application can access the data. By placing your application's database logic in
the database, your application now becomes nothing more then a presentation layer. It does not matter
if your application (the database component of it) is invoked via SQL*PLUS, by your GUI application,
or by some yet to be implemented interface, it is your application that is running in the database.
How they Work
This is where things can get confusing; exactly what privileges are active and when. Before we get into
how the invoker rights procedures work, we will take a look at definer rights procedures and how they
work (and have always worked). After we understand definer rights, and why they work the way they
do, we'll look at the different ways invoker rights procedures will behave under various calling
circumstances.
Definer Rights
In the definer rights model, a stored procedure is compiled using the privileges granted directly to the
person who 'owns' the procedure. By 'granted directly', I mean all object and system privileges granted
to that account, or granted to PUBLIC, not inclusive of any roles the user or PUBLIC may have. In short,
995
5254ch23cmp2.pdf 15
2/28/2005 6:53:01 PM
Chapter 23
in a definer rights procedure, roles have no meaning or presence either at compile-time or during runtime execution. The procedures are compiled using only directly granted privileges. This fact is
documented in Oracle Application Developer's Guide as follows:
Privileges Required to Create Procedures and Functions
To create a stand-alone procedure or function, or package specification or body, you must
meet the following prerequisites:
You must have the CREATE PROCEDURE system privilege to create a procedure or package
in your schema, or the CREATE ANY PROCEDURE system privilege to create a procedure or
package in another user's schema.
Attention: To create without errors (to compile the procedure or package successfully)
requires the following additional privileges:
❑
The owner of the procedure or package must have been explicitly granted the necessary
object privileges for all objects referenced within the body of the code.
❑
The owner cannot have obtained required privileges through roles.
If the privileges of a procedure's or package's owner change, the procedure must be
reauthenticated before it is executed. If a necessary privilege to a referenced object is
revoked from the owner of the procedure (or package), the procedure cannot be executed.
Although it doesn't explicitly state this, a grant to PUBLIC is as good as a grant to the owner of the
procedure as well. This requirement, the need for a direct grant in definer rights procedure, leads to the
sometimes confusing situation demonstrated below. Here, we will see that we can query the object in
SQL*PLUS, and we can use an anonymous block to access the object, but we cannot create a stored
procedure on this object. We'll start by setting up the appropriate grants for this scenario:
scott@TKYTE816> revoke select on emp from public;
Revoke succeeded.
scott@TKYTE816> grant select on emp to connect;
Grant succeeded.
scott@TKYTE816> connect tkyte/tkyte
tkyte@TKYTE816> grant create procedure to another_user;
Grant succeeded.
and now we'll see that ANOTHER_USER can query the SCOTT.EMP table:
tkyte@TKYTE816> connect another_user/another_user
another_user@TKYTE816> select count(*) from scott.emp;
COUNT(*)
---------14
996
5254ch23cmp2.pdf 16
2/28/2005 6:53:01 PM
Invoker and Definer Rights
Likewise, ANOTHER_USER can also execute an anonymous PL/SQL block:
another_user@TKYTE816> begin
2
for x in ( select count(*) cnt from scott.emp )
3
loop
4
dbms_output.put_line( x.cnt );
5
end loop;
6 end;
7 /
14
PL/SQL procedure successfully completed.
However, when we try to create a procedure identical to the PL/SQL above, we find this:
another_user@TKYTE816> create or replace procedure P
2 as
3 begin
4
for x in ( select count(*) cnt from scott.emp )
5
loop
6
dbms_output.put_line( x.cnt );
7
end loop;
8 end;
9 /
Warning: Procedure created with compilation errors.
another_user@TKYTE816> show err
Errors for PROCEDURE P:
LINE/COL
-------4/14
4/39
6/9
6/31
ERROR
------------------------------------------------------PL/SQL: SQL Statement ignored
PLS-00201: identifier 'SCOTT.EMP' must be declared
PL/SQL: Statement ignored
PLS-00364: loop index variable 'X' use is invalid
I cannot create a procedure (or in fact any compiled stored object, such as a view or trigger) that
accesses SCOTT.EMP. This is expected, and documented behavior. In the above example,
ANOTHER_USER is a user with the CONNECT role. The CONNECT role was granted SELECT on
SCOTT.EMP. This privilege from the role CONNECT, is not available in the definer rights stored
procedure however, hence the error message. What I tell people to do to avoid this confusion, is to SET
ROLE NONE in SQL*PLUS, and try out the statement they want to encapsulate in a stored procedure. For
example:
another_user@TKYTE816> set role none;
Role set.
another_user@TKYTE816> select count(*) from scott.emp;
select count(*) from scott.emp
*
ERROR at line 1:
ORA-00942: table or view does not exist
997
5254ch23cmp2.pdf 17
2/28/2005 6:53:02 PM
Chapter 23
If it won't work in SQL*PLUS without roles, it will definitely not work in a definer rights stored
procedure either.
Compiling a Definer Rights Procedure
When we compile the procedure into the database, a couple of things happen with regards to privileges.
We will list them here briefly, and then go into more detail:
❑
All of the objects, which the procedure statically accesses (anything not accessed via dynamic
SQL), are verified for existence. Names are resolved via the standard scoping rules as they
apply to the definer of the procedure.
❑
All of the objects it accesses are verified to ensure that the required access mode will be
available. That is, if an attempt to UPDATE T is made, Oracle will verify that the definer, or
PUBLIC, has the ability to UPDATE T without use of any roles.
❑
A dependency between this procedure and the referenced objects is set up and maintained. If
this procedure issues SELECT FROM T, then a dependency between T and this procedure is
recorded
If, for example, I have a procedure P that attempted to SELECT * FROM T, the compiler will first resolve
T into a fully qualified reference. T is an ambiguous name in the database – there may be to choose
from. Oracle will follow its scoping rules to figure out what T really is. Any synonyms will be resolved
to their base objects, and the schema name will be associated with the object. It does this name
resolution using the rules for the currently logged in user (the definer). That is, it will look for an object
called T that is owned by this user, and use that first (this includes private synonyms), then it will look at
public synonyms, and try to find T, and so on.
Once it determines exactly what T refers to, Oracle will determine if the mode in which we are
attempting to access T is permitted. In this case, if the definer owns the object T, or has been granted
SELECT on T directly (or if PUBLIC was granted SELECT privileges), then the procedure will compile. If
the definer does not have access to an object called T by a direct grant, then the procedure P will not
compile. So, when the object (the stored procedure that references T) is compiled into the database,
Oracle will do these checks. If they 'pass', Oracle will compile the procedure, store the binary code for
the procedure, and set up a dependency between this procedure, and this object T. This dependency is
used to invalidate the procedure later, in the event something happens to T that necessitates the stored
procedure's recompilation. For example if at a later date, we REVOKE SELECT ON T from the owner of
this stored procedure, Oracle will mark all stored procedures this user has, which are dependent on T,
and that refer to T, as INVALID. If we ALTER T ADD ... some column, Oracle can invalidate all of the
dependent procedures. This will cause them to be recompiled automatically upon their next execution.
What is interesting to note is not only what is stored, but what is not stored when we compile the object.
Oracle does not store the exact privilege used to get access to T. We only know that the procedure P is
dependent on T. We do not know if the reason we were allowed to see T was due to:
❑
A grant given to the definer of the procedure (GRANT SELECT ON T TO USER)
❑
A grant to PUBLIC on T (GRANT SELECT ON T TO PUBLIC)
❑
The user having the SELECT ANY TABLE privilege
The reason it is interesting to note what is not stored, is that a REVOKE of any of the above will cause the
procedure P to become invalid. If all three privileges were in place when the procedure was compiled, a
REVOKE of any of them will invalidate the procedure, forcing it to be recompiled before it is executed
again.
998
5254ch23cmp2.pdf 18
2/28/2005 6:53:02 PM
Invoker and Definer Rights
Now that the procedure is compiled into the database, and the dependencies are all set up, we can
execute the procedure, and be assured that it knows what T is, and that T is accessible. If something
happens to either the table T, or to the set of base privileges available to the definer of this procedure
that might affect our ability to access T, our procedure will become invalid, and will need to be
recompiled.
Definer Rights and Roles
This leads us on to why roles are not enabled during the compilation and execution of a stored
procedure in definer rights mode. Oracle is not storing exactly why you are allowed to access T, only that
you are. Any change to your privileges that might cause access to T to be removed, will cause the
procedure to become invalid, and necessitate its recompilation. Without roles, this means only REVOKE
SELECT ANY TABLE or REVOKE SELECT ON T from the definer account or from PUBLIC.
With roles enabled, it greatly expands the number of occasions where we could invalidate this
procedure. To illustrate what I mean by this, let's imagine for a moment that roles did give us privileges
on stored objects. Now, almost every time any role we had was modified, any time a privilege was
revoked from a role, or from a role that had been assigned to a role (and so on, roles can and are
granted to roles), we run the risk of invalidating many procedures (even procedures where we were not
relying on a privilege from the modified role).
Consider the impact of revoking a system privilege from a role. It would be comparable to revoking a
powerful system privilege from PUBLIC (don't do it, just think about it – or do it on a test database first).
If PUBLIC had been granted SELECT ANY TABLE, revoking that privilege would cause virtually every
procedure in the database to be made invalid. If procedures relied on roles, virtually every procedure in
the database would constantly become invalid due to small changes in permissions. Since one of the
major benefits of procedures is the 'compile once, run many' model, this would be disastrous for
performance.
Also consider that roles may be:
❑
Non-default – If I have a non-default role, enable it, and compile a procedure that relies on
those privileges, when I log out I no longer have that role. Should my procedure become
invalid? Why? Why not? I could easily argue both sides.
❑
Password protected – If someone changes the password on a ROLE, should everything that
might need this role need to be recompiled? I might be granted this role but, not knowing the
new password, I can no longer enable it. Should the privileges still be available? Why, or why
not? Again, there are cases for and against.
The bottom line with regard to roles in procedures with definer rights is:
❑
❑
❑
You have thousands, or tens of thousands of end users. They don't create stored objects (they
should not). We need roles to manage these people. Roles are designed for these people (end
users).
You have far fewer application schemas (things that hold stored objects). For these we want to
be explicit as to exactly what privileges we need, and why. In security terms, this is called the
concept of 'least privileges'. You want to specifically say what privilege you need, and why
you need it. If you inherit lots of privileges from roles, you cannot do this effectively. You can
manage to be explicit, since the number of development schemas is small (but the number of
end users is large).
Having the direct relationship between the definer and the procedure makes for a much more
efficient database. We recompile objects only when we need to, not when we might need to. It is a
large enhancement in efficiency.
999
5254ch23cmp2.pdf 19
2/28/2005 6:53:02 PM
Chapter 23
Invoker Rights
There is a big difference between invoker rights procedures and definer rights procedures (and
anonymous blocks of PL/SQL) with regard to how they use privileges, and resolve references to objects.
In terms of executing SQL statements, invoker rights procedures are similar to an anonymous block of
PL/SQL, but they execute very much like a definer rights procedure with respect to other PL/SQL
statements. Additionally, roles may be enabled in an invoker rights procedure, depending on how it was
accessed – unlike definer rights, which disallows the use of roles to provide access to objects in stored
procedures.
We will explore two pieces of these invoker rights procedures:
❑
'SQL' pieces – anything we SELECT, INSERT, UPDATE, DELETE, and anything we dynamically
execute using DBMS_SQL or EXECUTE IMMEDIATE (including PL/SQL code dynamically
executed).
❑
'PL/SQL' pieces – static references to object types in variable declarations, calls to other
stored procedures, packages, functions, and so on.
These two 'pieces' are treated very differently in invoker rights procedures. The 'SQL pieces' are in fact
resolved at compile-time (to determine their structure and such), but are resolved once again at runtime. This is what allows a stored procedure with a SELECT * FROM EMP access to totally different EMP
tables at run-time, when executed by different users. The 'PL/SQL' pieces however, are statically bound
at compile-time, much as they are in a definer rights procedure. So, if your invoker rights procedure has
code such as:
...
AUTHID CURRENT_USER
as
begin
for x in ( select * from T ) loop
proc( x.c1 );
end loop;
...
then the reference to T will be resolved at run-time (as well as compile-time, to understand what
SELECT * means) dynamically, allowing for a different T to be used by each person. The reference to
PROC however, will be resolved only at compile-time, and our procedure will be statically bound to a
single PROC. The invoker of this routine does not need EXECUTE ON PROC, but they do need SELECT on
an object called T. Not to confuse the issue, but if we desire the call to PROC to be resolved at run-time,
we have the mechanism for doing so. We can code:
...
AUTHID CURRENT_USER
as
begin
for x in ( select * from T ) loop
execute immediate 'begin proc( :x ); end;' USING x.c1;
end loop;
...
1000
5254ch23cmp2.pdf 20
2/28/2005 6:53:02 PM
Invoker and Definer Rights
In the above case, the reference to PROC will be resolved using the invoker set of privileges, and they
must have EXECUTE granted to them (or to some role, if roles are active).
Resolving References and Conveying Privileges
Let's look at how privileges are conveyed within an invoker rights procedure. When we do this, we'll
have to consider the various environments, or call stacks, that might invoke our procedure:
❑
A direct invocation by an end user.
❑
An invocation by a definer rights procedure.
❑
An invocation by another invoker rights procedure.
❑
An invocation by a SQL statement.
❑
An invocation by a view that references an invoker rights procedure.
❑
An invocation by a trigger.
With the exact same procedure, the result in each of the above environments could, potentially, be
different. In each case, an invoker rights procedure may very well access totally different database tables
and objects at run-time.
So, we'll begin by looking at how objects are bound, and what privileges are available in an invoker
rights procedure at run-time when executing in each of the above environments. The case of the view
and trigger will be considered the same, since both execute with definer rights only. Also, since PL/SQL
static objects are always resolved at compile-time in all environments, we will not consider them. They are
always resolved with respect to the definer's schema and access rights. The currently logged in user does
not need access to referenced PL/SQL object. The following table describes the behavior you should
expect for each environment:
Environment
SQL objects and dynamically
invoked PL/SQL
Are roles enabled?
A direct invocation
by an end user. For
example:
References to these objects are
resolved using the current user's
default schema and privileges.
Unqualified references to objects will
be resolved in their schema. All
objects must be accessible to the
currently logged in user. If the
procedure SELECTs from T, the
currently logged in user must have
SELECT on T as well (either directly
or via some role).
Yes. All of the roles enabled
prior to the execution of the
procedure are available inside
of the procedure. They will
be used to allow or deny
access to all SQL objects and
dynamically invoked
PL/SQL.
SQL> exec p;
1001
5254ch23cmp2.pdf 21
2/28/2005 6:53:02 PM
Chapter 23
Environment
SQL objects and dynamically
invoked PL/SQL
Are roles enabled?
An invocation by a
definer rights
procedure (P1),
where P2 is an
invoker rights
procedure. For
example:
These are resolved using the definer
schema, the schema of the calling
procedure. Unqualified objects will
be resolved in this other schema, not
the schema of the currently logged
in user, and not the schema that
created the invoker rights procedure,
but the schema of the calling
procedure. In our example, the
owner of P1 would always be the
'invoker' inside of P2.
No. There are no roles
enabled since the definer
rights procedure was invoked.
At the point of entry into the
definer rights procedure, all
roles were disabled and will
remain so until the definer
rights procedure returns.
An invocation by
another invoker
rights procedure.
Same as direct invocation by an end
user.
Yes. Same as direct
invocation by an end user.
An invocation by a
SQL statement.
Same as direct invocation by an end
user.
Yes. Same as direct
invocation by an end user.
An invocation by a
VIEW or TRIGGER
that references an
invoker rights
procedure.
Same as an invocation by definer
rights procedure.
No. Same as an invocation by
definer rights procedure.
procedure p1
is
begin
p2;
end;
So, as you can see, the execution environment can have a dramatic effect on the run-time behavior of an
invoker rights routine. The exact same PL/SQL stored procedure, when run directly, may access a
wholly different set of objects than it will when executed by another stored procedure, even when
logged in as the same exact user.
To demonstrate this, we will create a procedure that shows what roles are active at run-time and access
a table that has data in it, which tells us who 'owns' that table. We will do this for each of the above
cases, except for the invoker rights routine called from an invoker rights routine, since this is exactly the
same as just calling the invoker rights routine directly. We'll start by setting up the two accounts we'll
use for this demonstration:
tkyte@TKYTE816> drop user a cascade;
User dropped.
tkyte@TKYTE816> drop user b cascade;
User dropped.
tkyte@TKYTE816> create user a identified by a default tablespace data temporary
1002
5254ch23cmp2.pdf 22
2/28/2005 6:53:02 PM
Invoker and Definer Rights
tablespace temp;
User created.
tkyte@TKYTE816> grant connect, resource to a;
Grant succeeded.
tkyte@TKYTE816> create user b identified by b default tablespace data temporary
tablespace temp;
User created.
tkyte@TKYTE816> grant connect, resource to b;
Grant succeeded.
This sets up are two users, A and B, each with two roles, CONNECT and RESOURCE. Next, we will have
the user A create an invoker rights routine, and then a definer rights routine and a view, each of which
calls the invoker rights routine. Each execution of the procedure will tell us how many roles are in
place, who the current user is (the schema whose privilege set we are executing under), what the current
schema is, and finally what table is being used by the query. We start by creating a table identifiable to
user A:
tkyte@TKYTE816> connect a/a
a@TKYTE816> create table t ( x varchar2(255) );
Table created.
a@TKYTE816> insert into t values ( 'A's table' );
1 row created.
Next, user A creates the invoker rights function, definer rights procedure, and the view:
a@TKYTE816> create function Invoker_rights_function return varchar2
2 AUTHID CURRENT_USER
3 as
4
l_data varchar2(4000);
5 begin
6
dbms_output.put_line( 'I am an IR PROC owned by A' );
7
select 'current_user=' ||
8
sys_context( 'userenv', 'current_user' ) ||
9
' current_schema=' ||
10
sys_context( 'userenv', 'current_schema' ) ||
11
' active roles=' || cnt ||
12
' data from T=' || t.x
13
into l_data
14
from (select count(*) cnt from session_roles), t;
15
16
return l_data;
17 end;
1003
5254ch23cmp2.pdf 23
2/28/2005 6:53:02 PM
Chapter 23
18
/
Function created.
a@TKYTE816> grant execute on Invoker_rights_function to public;
Grant succeeded.
a@TKYTE816> create procedure Definer_rights_procedure
2 as
3
l_data varchar2(4000);
4 begin
5
dbms_output.put_line( 'I am a DR PROC owned by A' );
6
select 'current_user=' ||
7
sys_context( 'userenv', 'current_user' ) ||
8
' current_schema=' ||
9
sys_context( 'userenv', 'current_schema' ) ||
10
' active roles=' || cnt ||
11
' data from T=' || t.x
12
into l_data
13
from (select count(*) cnt from session_roles), t;
14
15
dbms_output.put_line( l_data );
16
dbms_output.put_line
( 'Going to call the INVOKER rights procedure now...' );
17
dbms_output.put_line( Invoker_rights_function );
18 end;
19 /
Procedure created.
a@TKYTE816> grant execute on Definer_rights_procedure to public;
Grant succeeded.
a@TKYTE816> create view V
2 as
3 select invoker_rights_function from dual
4 /
View created.
a@TKYTE816> grant select on v to public
2 /
Grant succeeded.
Now we will log in as user B, create a table T with an identifying row, and execute the above
procedures:
a@TKYTE816> connect b/b
b@TKYTE816> create table t ( x varchar2(255) );
Table created.
1004
5254ch23cmp2.pdf 24
2/28/2005 6:53:03 PM
Invoker and Definer Rights
b@TKYTE816> insert into t values ( 'B''s table' );
1 row created.
b@TKYTE816> exec dbms_output.put_line( a.Invoker_rights_function )
I am an IR PROC owned by A
current_user=B current_schema=B active roles=3 data from T=B's table
PL/SQL procedure successfully completed.
This shows that when user B directly invokes the invoker rights routine owned by A, the privileges are
taken from user B at run-time (current_user=B). Further, since the current_schema is user B, the
query selected from B.T, not A.T. This is evidenced by the data from T=B's table in the above
output. Lastly, we see that there are three roles active in the session at the point in time we executed the
query (I have PLUSTRACE, used by AUTOTRACE, granted to PUBLIC in my database – this is the third
role). Now, let's compare that to what happens when we invoke through the definer rights procedure:
b@TKYTE816> exec a.Definer_rights_procedure
I am a DR PROC owned by A
current_user=A current_schema=A active roles=0 data from T=A's table
Going to call the INVOKER rights procedure now...
I am an IR PROC owned by A
current_user=A current_schema=A active roles=0 data from T=A's table
PL/SQL procedure successfully completed.
This shows that the definer rights routine executed with user A's privileges, minus roles (active
roles=0). Further, the definer rights routine is statically bound to the table A.T, and will not see the
table B.T.
The most important thing to note is the effect seen when we call the invoker rights routine from the
definer rights routine. Notice that the invoker this time is A, not B. The invoker is the schema that is
currently in place at the time of the call to the invoker rights routine. It will not execute as user B as it
did before, but this time it will execute as user A. Thus, the current_user and current_schema are
set to user A and so the table the invoker rights routine accesses will be A's table. Another important fact
is that the roles are not active in the invoker rights routine this time around. When we entered the
definer rights routine, the roles were disabled, and they remain disabled until we exit the definer rights
routine again.
Now, let's see what the effects of calling the invoker rights function from SQL:
b@TKYTE816> select a.invoker_rights_function from dual;
INVOKER_RIGHTS_FUNCTION
----------------------------------------------------------------------current_user=B current_schema=B active roles=3 data from T=B's table
b@TKYTE816> select * from a.v;
INVOKER_RIGHTS_FUNCTION
-----------------------------------------------------------------------current_user=A current_schema=A active roles=0 data from T=B's table
1005
5254ch23cmp2.pdf 25
2/28/2005 6:53:03 PM
Chapter 23
We can see that calling the invoker rights routine from SQL directly, as we did by selecting it from
DUAL, is the same as calling the routine directly. Further, calling the routine from a view, as we did with
the second query, shows that it will behave as if it were called from a definer rights routine, since views
are always stored using definer rights.
Compiling an Invoker Rights Procedure
We will now explore what happens when we compile an invoker rights procedure into the database.
This might be surprising, but the answer to this is the same exact thing as what happens when we compile a
Definer rights procedure. The steps are:
❑
All of the objects it statically accesses (anything not accessed via dynamic SQL) are verified
for existence. Names are resolved via the standard scoping rules as they apply to the definer
of the procedure. Roles are not enabled.
❑
All of the objects it accesses are verified to ensure that the required access mode will be
available. That is, if an attempt to UPDATE T is made, Oracle will verify the definer, or
PUBLIC has the ability to UPDATE T without use of any roles.
❑
A dependency between this procedure and the referenced objects is set up and maintained. If
this procedure SELECTS FROM T, then a dependency between T and this procedure is
recorded
What this means is that an invoker rights routine, at compile-time, is treated exactly the same as a definer
rights routine. This is an area of confusion for many. They have heard that roles are enabled in invoker
rights routines, and this is, in fact, accurate. However (and this is a big however), they are not in effect
during the compilation process. This means that the person who compiles the stored procedure, the
owner of the stored procedure, still needs to have direct access to all statically referenced tables. Recall
the example we used in the Definer Rights section, where we showed that SELECT COUNT(*) FROM EMP
succeeded in SQL and in PL/SQL with an anonymous block, but failed in the stored procedure
compilation. The exact same thing would still happen with an invoker rights routine. The rules spelled
out in the Oracle 8i Application Developer's Guide on Privileges Required to Create Procedures and Functions
remain in place. You still need direct access to the underlying objects.
The reason for this is due to the dependency mechanism employed by Oracle. If an operation
performed in the database would cause a definer rights procedure to become invalid (for example, the
REVOKE statement), the corresponding invoker rights procedure would also become invalid. The only
true difference between invoker rights procedures and definer rights is their run-time execution
behavior. In terms of dependencies, invalidation, and the privileges required by the owner of the
procedure, they are exactly the same.
There are ways to work around this issue, and for many uses of invoker rights procedures it won't be an
issue at all. However, it does indicate the need for template objects, in some cases. In the next section
we'll see what template objects are, and how we can use them to get around the need for a direct grant.
Using Template Objects
Now that we know that at compile-time, an invoker rights procedure is really no different to a definer
rights procedure we can understand the need to have access to all of the objects directly. If we are
designing invoker rights procedures in which we intend to make use of roles, we, as the definer, will
need direct grants, not the role. This may not be possible for whatever reason (it just takes someone
saying 'no, I won't grant you select on that table') and we need to work around that.
1006
5254ch23cmp2.pdf 26
2/28/2005 6:53:03 PM
Invoker and Definer Rights
Enter template objects. A template object is basically an object to which the defining schema has direct
access, and which looks just like the object you actually want to access at run-time. Think of it like a C
struct, a Java Class, a PL/SQL record, or a data structure. It is there to let PL/SQL know the number of
columns, the types of columns, and so on. An example will help here. Let's say you wanted to create a
procedure that queries the DBA_USERS table, and displays, in a nice format, a CREATE USER statement
for any existing user. You might attempt to write a procedure as a DBA, such as:
tkyte@TKYTE816> create or replace
2 procedure show_user_info( p_username in varchar2 )
3 AUTHID CURRENT_USER
4 as
5
l_rec
dba_users%rowtype;
6 begin
7
select *
8
into l_rec
9
from dba_users
10
where username = upper(p_username);
11
12
dbms_output.put_line( 'create user ' || p_username );
13
if ( l_rec.password = 'EXTERNAL' ) then
14
dbms_output.put_line( ' identified externally' );
15
else
16
dbms_output.put_line
17
( ' identified by values ''' || l_rec.password || '''' );
18
end if;
19
dbms_output.put_line
20
( ' temporary tablespace ' || l_rec.temporary_tablespace ||
21
' default tablespace ' || l_rec.default_tablespace ||
22
' profile ' || l_rec.profile );
23 exception
24
when no_data_found then
25
dbms_output.put_line( '*** No such user ' || p_username );
26 end;
27 /
Warning: Procedure created with compilation errors.
tkyte@TKYTE816> show err
Errors for PROCEDURE SHOW_USER_INFO:
LINE/COL
-------4/13
4/13
6/5
8/12
12/5
12/10
ERROR
----------------------------------------------------------------PLS-00201: identifier 'SYS.DBA_USERS' must be declared
PL/SQL: Item ignored
PL/SQL: SQL Statement ignored
PLS-00201: identifier 'SYS.DBA_USERS' must be declared
PL/SQL: Statement ignored
PLS-00320: the declaration of the type of this expression is
incomplete or malformed
18/5
19/35
PL/SQL: Statement ignored
PLS-00320: the declaration of the type of this expression is
incomplete or malformed
1007
5254ch23cmp2.pdf 27
2/28/2005 6:53:03 PM
Chapter 23
This procedure fails to compile, not because SYS.DBA_USERS does not really exist, but rather because
we have the ability to access DBA_USERS via a role, and roles are not enabled during the compilation of
stored procedures, ever. So, what can we do to get this procedure to compile? For one, we could create
our own DBA_USERS table. This will allow our procedure to successfully compile. However, since this
table will not be the 'real' DBA_USERS table, it will not give us the result we desire unless we execute it as
another user who can access the real DBA_USERS view:
tkyte@TKYTE816> create table dba_users
2 as
3 select * from SYS.dba_users where 1=0;
Table created.
tkyte@TKYTE816> alter procedure show_user_info compile;
Procedure altered.
tkyte@TKYTE816> exec show_user_info( USER );
*** No such user TKYTE
PL/SQL procedure successfully completed.
tkyte@TKYTE816> connect system/manager
system@TKYTE816> exec tkyte.show_user_info( 'TKYTE' )
create user TKYTE
identified by values '698F1E51F530CA57'
temporary tablespace TEMP default tablespace DATA profile DEFAULT
PL/SQL procedure successfully completed.
We now have a procedure that, when executed by someone other than the definer, sees the correct
DBA_USERS (if the invoker is not allowed to see DBA_USERS, they will receive table or view does
not exist). When the definer runs the procedure, they get no such user ... since their template
object DBA_USERS is empty. Everyone else though, gets the expected results. In many cases this is
perfectly acceptable. An example of this is when you expect to run the same set of code against many
different tables. In this case however, we wish for this procedure to execute against exactly one table,
DBA_USERS. So, back to the drawing board, how can we get this procedure to work for all users,
including the definer? The answer is to use a template object of a different kind. We will create a table
that is structurally the same as DBA_USERS, but give it a different name, say DBA_USERS_TEMPLATE.
We'll use this table simply to define a record to fetch into. We will then dynamically access DBA_USERS
in all cases:
system@TKYTE816> connect tkyte/tkyte
tkyte@TKYTE816> drop table dba_users;
Table dropped.
tkyte@TKYTE816> create table dba_users_TEMPLATE
2 as
3 select * from SYS.dba_users where 1=0;
Table created.
1008
5254ch23cmp2.pdf 28
2/28/2005 6:53:03 PM
Invoker and Definer Rights
tkyte@TKYTE816> create or replace
2 procedure show_user_info( p_username in varchar2 )
3 AUTHID CURRENT_USER
4 as
5
type rc is ref cursor;
6
7
l_rec
dba_users_TEMPLATE%rowtype;
8
l_cursor rc;
9 begin
10
open l_cursor for
11
'select *
12
from dba_users
13
where username = :x'
14
USING upper(p_username);
15
16
fetch l_cursor into l_rec;
17
if ( l_cursor%found ) then
18
19
dbms_output.put_line( 'create user ' || p_username );
20
if ( l_rec.password = 'EXTERNAL' ) then
21
dbms_output.put_line( ' identified externally' );
22
else
23
dbms_output.put_line
24
( ' identified by values ''' || l_rec.password || '''' );
25
end if;
26
dbms_output.put_line
27
( ' temporary tablespace ' || l_rec.temporary_tablespace ||
28
' default tablespace ' || l_rec.default_tablespace ||
29
' profile ' || l_rec.profile );
30
else
31
dbms_output.put_line( '*** No such user ' || p_username );
32
end if;
33
close l_cursor;
34 end;
35 /
Procedure created.
tkyte@TKYTE816> exec show_user_info( USER );
create user TKYTE
identified by values '698F1E51F530CA57'
temporary tablespace TEMP default tablespace DATA profile DEFAULT
PL/SQL procedure successfully completed.
So, in this case, we used the table DBA_USERS_TEMPLATE as a simple way to create a record type to
fetch into. We could have described DBA_USERS and set up our own record type and all the rest of it,
but I just find it easier to let the database do the work for me. In the event we upgrade to the next
release of Oracle, we can simply recreate the template table, our procedure will recompile itself, and
any new/additional columns or data type changes will be accounted for automatically.
1009
5254ch23cmp2.pdf 29
2/28/2005 6:53:03 PM
Chapter 23
Caveats
As with any feature, there are some nuances that need to be noted in the way this feature functions. This
section attempts to address some of them.
Invoker Rights and Shared Pool Utilization
When using invoker rights to have a single procedure access data in different schemas, depending on
who is running the query at run-time, you must be aware of the penalty you will pay in the shared pool.
When using definer rights procedures, there is at most one copy of a SQL statement in the shared pool
for each query in the procedure. Definer rights stored procedures make excellent use of the shared SQL
facility (see Chapter 10 on Tuning Strategies and Tools for why this is an extremely important
consideration). Invoker rights procedures, by design, on the other hand might not.
This is neither a terrible nor a good thing. Rather, it is something you must be aware of and size your
shared pool accordingly. When using invoker rights procedures, we will use the shared pool in much
the same way as you would if you wrote a client-server application using ODBC or JDBC that directly
invoked DML. Each user may be executing the same exact query, but each query may actually be
different. So, while we might all be issuing SELECT * FROM T, since we all may have different T tables,
we will each get our own copy of the query plan and related information in the shared pool. This is
necessary, since we each have a different T with different access rights and totally different access plans.
We can see the effect on the shared pool easily via an example. I have created the following objects in
one schema:
tkyte@TKYTE816> create table t ( x int );
Table created.
tkyte@TKYTE816> create table t2 ( x int );
Table created.
tkyte@TKYTE816> create public synonym T for T;
Synonym created.
tkyte@TKYTE816> create or replace procedure dr_proc
2 as
3
l_cnt number;
4 begin
5
select count(*) into l_cnt from t DEMO_DR;
6 end;
7 /
Procedure created.
tkyte@TKYTE816> create or replace procedure ir_proc1
2 authid current_user
3 as
4
l_cnt number;
5 begin
1010
5254ch23cmp2.pdf 30
2/28/2005 6:53:03 PM
Invoker and Definer Rights
6
7
8
select count(*) into l_cnt from t DEMO_IR_1;
end;
/
Procedure created.
tkyte@TKYTE816> create or replace procedure ir_proc2
2 authid current_user
3 as
4
l_cnt number;
5 begin
6
select count(*) into l_cnt from tkyte.t DEMO_IR_2;
7 end;
8 /
Procedure created.
tkyte@TKYTE816> create or replace procedure ir_proc3
2 authid current_user
3 as
4
l_cnt number;
5 begin
6
select count(*) into l_cnt from t2 DEMO_IR_3;
7 end;
8 /
Procedure created.
tkyte@TKYTE816> grant select on t to public;
Grant succeeded.
tkyte@TKYTE816> grant execute on dr_proc to public;
Grant succeeded.
tkyte@TKYTE816> grant execute on ir_proc1 to public;
Grant succeeded.
tkyte@TKYTE816> grant execute on ir_proc2 to public;
Grant succeeded.
tkyte@TKYTE816> grant execute on ir_proc3 to public;
Grant succeeded.
We have created two tables T and T2. A public synonym T for TKYTE.T exists. Our four procedures all
access either T or T2. The definer rights procedure, being statically bound at compile-time, does not
need a schema qualifier. The invoker rights procedure, IR_PROC1 will access T via the public synonym.
The second procedure IR_PROC2 will use a fully qualified reference, and the third procedure IR_PROC3
will access T2 in an unqualified way. Note that there is no public synonym for T2 – it is my intention to
have IR_PROC3 access many different T2s at run-time.
1011
5254ch23cmp2.pdf 31
2/28/2005 6:53:04 PM
Chapter 23
Next, I created ten users via this script:
tkyte@TKYTE816> begin
2
for i in 1 .. 10 loop
3
begin
4
execute immediate 'drop user u' || i || ' cascade';
5
exception
6
when others then null;
7
end;
8
execute immediate 'create user u'||i || ' identified by pw';
9
execute immediate 'grant create session, create table to u'||i;
10
execute immediate 'alter user u' || i || ' default tablespace
11
data quota unlimited on data';
12
end loop;
13 end;
14 /
PL/SQL procedure successfully completed.
and for each user, we executed:
create table t2 ( x int );
exec tkyte.dr_proc
exec tkyte.ir_proc1
exec tkyte.ir_proc2
exec tkyte.ir_proc3
This would log in as that user, create T2, and then run the four procedures in question. Now, after doing
this for each of the ten users, we can inspect our shared pool, specifically the V$SQLAREA view, to see
what happened, using the PRINT_TABLE procedure shown earlier in the chapter:
tkyte@TKYTE816> set serveroutput on size 1000000
tkyte@TKYTE816> begin
2
print_table ('select sql_text, sharable_mem, version_count,
3
loaded_versions, parse_calls, optimizer_mode
4
from v$sqlarea
5
where sql_text like ''% DEMO\__R%'' escape ''\''
6
and lower(sql_text) not like ''%v$sqlarea%'' ');
7 end;
8 /
SQL_TEXT
SHARABLE_MEM
VERSION_COUNT
LOADED_VERSIONS
PARSE_CALLS
OPTIMIZER_MODE
----------------SQL_TEXT
SHARABLE_MEM
VERSION_COUNT
LOADED_VERSIONS
PARSE_CALLS
OPTIMIZER_MODE
:
:
:
:
:
:
SELECT COUNT(*)
4450
1
1
10
CHOOSE
FROM OPS$TKYTE.T DEMO_IR_2
:
:
:
:
:
:
SELECT COUNT(*)
4246
1
1
10
CHOOSE
FROM T DEMO_DR
1012
5254ch23cmp2.pdf 32
2/28/2005 6:53:04 PM
Invoker and Definer Rights
----------------SQL_TEXT
SHARABLE_MEM
VERSION_COUNT
LOADED_VERSIONS
PARSE_CALLS
OPTIMIZER_MODE
----------------SQL_TEXT
SHARABLE_MEM
VERSION_COUNT
LOADED_VERSIONS
PARSE_CALLS
OPTIMIZER_MODE
-----------------
:
:
:
:
:
:
SELECT COUNT(*)
4212
1
1
10
CHOOSE
FROM T DEMO_IR_1
:
:
:
:
:
:
SELECT COUNT(*)
FROM T2 DEMO_IR_3
31941
10
10
10
MULTIPLE CHILDREN PRESENT
PL/SQL procedure successfully completed.
Even though the SQL text is exactly the same for SELECT COUNT(*) FROM T2 DEMO_IR_3, we can
clearly see that there are ten different copies of this code in the shared pool. Each user in fact, needs
their own optimized plan, as the objects referenced by this same query are totally different. In the cases
where the underlying objects were identical, and the privileges were in place, we shared the SQL plans
as expected.
So, the bottom line is that if you are using invoker rights to host one copy of code to access many
different schemas, you must be prepared to have a larger shared pool to cache these query plans and
such. This leads us into the next caveat.
Performance
When using invoker rights procedures, as you are now aware, each user might need to have their own
special query plan generated for them. The cost of this additional parsing can be huge. Parsing a query
is one of the most CPU-intensive things we do. We can see the 'cost' of parsing unique queries, as an
invoker rights routine might do, by using TKPROF to time the parse of statements. In order to execute
the following example, you will need the ALTER SYSTEM privilege:
tkyte@TKYTE816> alter system flush shared_pool;
System altered.
tkyte@TKYTE816> alter system set timed_statistics=true;
System altered.
tkyte@TKYTE816> alter session set sql_trace=true;
Session altered.
tkyte@TKYTE816> declare
2
type rc is ref cursor;
3
l_cursor rc;
4 begin
5
for i in 1 .. 500 loop
1013
5254ch23cmp2.pdf 33
2/28/2005 6:53:04 PM
Chapter 23
6
7
8
9
10
open l_cursor for 'select * from all_objects t' || i;
close l_cursor;
end loop;
end;
/
PL/SQL procedure successfully completed.
This will cause 500 unique statements (each has a different table alias) to be parsed (similar to an
invoker rights routine run by 500 different users with 500 different schemas). Looking at the TKPROF
report summary for this session we see:
...
OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS
call
count
------- -----Parse
1148
Execute
1229
Fetch
1013
------- -----total
3390
cpu
elapsed
disk query
current
----- ---------- ------ ------ ---------17.95
18.03
0
55
15
0.29
0.25
0
0
0
0.14
0.17
0
2176
0
----- ---------- ------ ------ ---------18.38
18.45
0
2231
15
rows
-----0
0
888
-----888
Misses in library cache during parse: 536
504
648
1152
0
user SQL statements in session.
internal SQL statements in session.
SQL statements in session.
statements EXPLAINed in this session.
Now we run a block that does not parse a unique statement 500 times, such as:
tkyte@TKYTE816> alter system flush shared_pool;
System altered.
tkyte@TKYTE816> alter system set timed_statistics=true;
System altered.
tkyte@TKYTE816> alter session set sql_trace=true;
Session altered.
tkyte@TKYTE816> declare
2
type rc is ref cursor;
3
l_cursor rc;
4 begin
5
for i in 1 .. 500 loop
6
open l_cursor for 'select * from all_objects t';
7
close l_cursor;
8
end loop;
9
end;
1014
5254ch23cmp2.pdf 34
2/28/2005 6:53:04 PM
Invoker and Definer Rights
10
/
PL/SQL procedure successfully completed.
we find from the TKPROF report that:
...
OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS
call
count
------- -----Parse
614
Execute
671
Fetch
358
------- -----total
1643
cpu
elapsed
disk query current
rows
----- ---------- ------ ------ ------ -----0.74
0.53
1
55
9
0
0.09
0.31
0
0
0
0
0.08
0.04
8
830
0
272
----- ---------- ------ ------ ------ -----0.91
0.88
9
885
9
272
Misses in library cache during parse: 22
504
114
618
0
user SQL statements in session.
internal SQL statements in session.
SQL statements in session.
statements EXPLAINed in this session.
This is, quite simply, a huge difference. 500 unique statements (emulating the behavior of an invoker
rights routine that accesses different tables each time), 17.95 CPU seconds of parse time. 500 of the
same statement (emulating a standard definer rights routine), 0.74 CPU seconds of parse time. That is 24
times the effort!
This is definitely something to watch out for. In many cases, where SQL is not reused, the system will
spend more time parsing queries than actually executing them! To find out why this is so, see the
Chapter 10 on Tuning Strategies and Tools, where I talk of the vital importance of bind variables to make
queries reusable.
This is not a reason to avoid using invoker rights routines. By all means use them, but be aware of the
implications of doing so.
Code must be more Robust in Handling Errors
Normally, if I were to code a stored procedure such as:
...
begin
for x in ( select pk from t ) loop
update y set c = c+0.5 where d = x.pk;
end loop;
end;
...
I could feel very confident that if that procedure were valid, it would run. In the definer rights model,
this is the case. I know for a fact that T and Y exist. I know for a fact that T is readable and that Y is
updateable.
1015
5254ch23cmp2.pdf 35
2/28/2005 6:53:04 PM
Chapter 23
Under invoker rights, I lose all of these facts. I no longer know if T exists and if it does, does it have a
column called PK? If it does exist, do I have SELECT on it? If I have SELECT on it, is the SELECT via a
role, meaning if I invoke this procedure from a definer rights routine, it won't work, but a direct
invocation would? Does Y exist? And so on. In short, all of the facts we have ever taken for granted, are
removed from us in invoker rights routines. So, while invoker rights routines open up a new way for us
to program, in some ways they make it harder.
In the above, our code should be prepared to handle many of the possible (and probable) cases such as:
❑
T does not exist.
❑
T exists, but we do not have the required privileges on it.
❑
T exists, but it does not have a PK column.
❑
T exists, and has a column called PK, but the column's data type is different from the type at
compilation.
❑
All of the above with regards to Y.
Since the update on Y only happens when there is some data in T, we may be able to run this procedure
successfully many times, but one day when data is put into T the procedure fails. In fact, we never were
able to see Y, but because this is the first time we had ever 'tried' to see Y, the procedure had failed.
Only when a code path is executed will it fail!
To have a 'bullet-proof' routine that catches the possible errors, we would need to code:
create or replace procedure P
authid current_user
as
no_such_table exception;
pragma exception_init(no_such_table,-942);
insufficient_privs exception;
pragma exception_init(insufficient_privs,-1031);
invalid_column_name exception;
pragma exception_init(invalid_column_name,-904);
inconsistent_datatypes exception;
pragma exception_init(inconsistent_datatypes,-932);
begin
for x in ( select pk from t ) loop
update y set c = c+0.5 where d = x.pk;
end loop;
exception
when NO_SUCH_TABLE then
dbms_output.put_line( 'Error Caught: ' || sqlerrm
when INSUFFICIENT_PRIVS then
dbms_output.put_line( 'Error Caught: ' || sqlerrm
when INVALID_COLUMN_NAME then
dbms_output.put_line( 'Error Caught: ' || sqlerrm
when INCONSISTENT_DATATYPES then
dbms_output.put_line( 'Error Caught: ' || sqlerrm
... (a variety of other errors go here)...
end;
/
);
);
);
);
1016
5254ch23cmp2.pdf 36
2/28/2005 6:53:04 PM
Invoker and Definer Rights
Side Effects of Using SELECT *
Using a SELECT * can be very dangerous in a PL/SQL routine that accesses different tables, when run
by different users such as in invoker rights code. The data may appear to come out 'scrambled', or in a
different order. This is because the record that is set up, and fetched into, is defined at compile-time, not
at run-time. Hence, the * is expanded at compile-time for the PL/SQL objects (the record types) but
expanded at run-time for the query. If you have an object with the same name, but a different column
ordering, in different schemas and access this via an invoker rights routine with a SELECT *, be
prepared for this side effect:
tkyte@TKYTE816> create table t ( msg varchar2(25), c1 int, c2 int );
Table created.
tkyte@TKYTE816> insert into t values ( 'c1=1, c2=2', 1, 2 );
1 row created.
tkyte@TKYTE816> create or replace procedure P
2 authid current_user
3 as
4 begin
5
for x in ( select * from t ) loop
6
dbms_output.put_line( 'msg= ' || x.msg );
7
dbms_output.put_line( 'C1 = ' || x.c1 );
8
dbms_output.put_line( 'C2 = ' || x.c2 );
9
end loop;
10 end;
11 /
Procedure created.
tkyte@TKYTE816> exec p
msg= c1=1, c2=2
C1 = 1
C2 = 2
PL/SQL procedure successfully completed.
tkyte@TKYTE816> grant execute on P to u1;
Grant succeeded.
So what we have above is a procedure that simply shows us what is in the table T. It prints out a MSG
column, which I am using in this example to show what I expect the answer to be. It prints out C1 and
C2's values. Very simple, very straightforward. Now, let's see what happens when I execute it as another
user with their own T table:
tkyte@TKYTE816> @connect u1/pw
u1@TKYTE816> drop table t;
Table dropped.
1017
5254ch23cmp2.pdf 37
2/28/2005 6:53:04 PM
Chapter 23
u1@TKYTE816> create table t ( msg varchar2(25), c2 int, c1 int );
Table created.
u1@TKYTE816> insert into t values ( 'c1=2, c2=1', 1, 2 );
1 row created.
Notice here that I created the table with C1 and C2 reversed! Here, I am expecting that C1 = 2 and C2 =
1. When we run the procedure however, we get this:
u1@TKYTE816> exec tkyte.p
msg= c1=2, c2=1
C1 = 1
C2 = 2
PL/SQL procedure successfully completed.
It is not exactly what we expected – until we think about it. PL/SQL, at compile-time, set up the implicit
record X for us. The record X is simply a data structure with three elements, MSG VARCHAR2, C1
NUMBER, and C2 NUMBER. When the SELECT * columns were expanded during the parse phase of the
query as user TKYTE, they got expanded to be MSG, C1, and C2 in that order. As U1 however, they got
expanded to MSG, C2, and C1. Since the data types all matched up with the implicit record X, we did not
receive an INCONSISTENT DATATYPE error (this could also happen if the data types were not
compatible). The fetch succeeded, but put column C2 into record attribute C1. This is the expected
behavior, and yet another good reason to not use SELECT * in production code.
Beware of the 'Hidden' Columns
This is very similar to the SELECT * caveat above. Again this ties into how the PL/SQL routine with
invoker rights is compiled, and how names and references to objects are resolved. In this case, we will
consider an UPDATE statement that, if executed directly in SQL*PLUS would give a totally different
answer than when executed in an invoker rights routine. It does the 'correct' thing in both environments
– it just does them very differently.
When PL/SQL code is compiled into the database, each and every static SQL query is parsed, and all
identifiers are discovered in them. These identifiers might be database column names or they might
reference PL/SQL variables (bind variables). If they are database column names, they are left in the
query 'as is'. If they are PL/SQL variable names, they are replaced in the query with a
:BIND_VARIABLE reference. This replacement is done at compile-time, never at run-time. So, if we take
an example:
tkyte@TKYTE816> create table t ( c1 int );
Table created.
tkyte@TKYTE816> insert into t values ( 1 );
1 row created.
tkyte@TKYTE816> create or replace procedure P
2 authid current_user
1018
5254ch23cmp2.pdf 38
2/28/2005 6:53:04 PM
Invoker and Definer Rights
3
4
5
6
7
8
as
c2
number default 5;
begin
update t set c1 = c2;
end;
/
Procedure created.
tkyte@TKYTE816> exec p
PL/SQL procedure successfully completed.
tkyte@TKYTE816> select * from t;
C1
---------5
tkyte@TKYTE816> grant execute on P to u1;
Grant succeeded.
All looks normal so far. C1 is a database column in the table T, and C2 is a PL/SQL variable name. The
statement UPDATE T SET C1 = C2 is processed by PL/SQL at compile-time to be UPDATE T SET C1 =
:BIND_VARIABLE, and the value of :BIND_VARIABLE is passed in at run-time. Now, if we log in as U1,
and create our own T table:
tkyte@TKYTE816> connect u1/pw
u1@TKYTE816> drop table t;
Table dropped.
u1@TKYTE816> create table t ( c1 int, c2 int );
Table created.
u1@TKYTE816> insert into t values ( 1, 2 );
1 row created.
u1@TKYTE816> exec tkyte.p
PL/SQL procedure successfully completed.
u1@TKYTE816> select * from t;
C1
C2
---------- ---------5
2
This might seem right or wrong, depending on how you look at it. We just executed UPDATE T SET C1 =
C2, which if we were to execute at the SQL*PLUS prompt, would result in C1 being set to 2, not 5.
However, since PL/SQL rewrote this query at compile-time to not have any references to C2, it does the
1019
5254ch23cmp2.pdf 39
2/28/2005 6:53:05 PM
Chapter 23
same exact thing to our copy of T, as it did to the other copy of T – it set the column C1 to 5. This
PL/SQL routine cannot 'see' the column C2, since C2 does not exist in the object it was compiled
against.
At first, this seems confusing, since we do not get to see the rewritten update normally, but once you are
aware of it, it makes perfect sense.
Java and Invoker Rights
PL/SQL, by default, compiles with definer rights. You must go out of your way to make it run as the
invoker. Java on the other hand, goes the other way. Java by default, uses invoker rights. If you want
definer rights you must specify this when you load it.
As an example, I've created a table T such as:
ops$tkyte@DEV816> create table t ( msg varchar2(50) );
Table created.
ops$tkyte@DEV816> insert into t values ( 'This is T owned by ' || user
);
1 row created.
I have also created, and loaded two Java stored procedures (you will need the CREATE PUBLIC
SYNONYM privilege to complete this example). These Java stored procedures are very much like the
PL/SQL examples above. They will access a table T that contains a row describing who 'owns' this
table, and they will print out the session user, current user (privilege schema), and current schema:
tkyte@TKYTE816> host type ir_java.java
import java.sql.*;
import oracle.jdbc.driver.*;
public class ir_java
{
public static void test() throws SQLException
{
Connection cnx = new OracleDriver().defaultConnection();
String sql =
"SELECT MSG, sys_context('userenv','session_user'), "+
"sys_context('userenv','current_user'), "+
"sys_context('userenv','current_schema') "+
"FROM T";
Statement stmt = cnx.createStatement();
ResultSet rset = stmt.executeQuery(sql);
if (rset.next())
System.out.println( rset.getString(1)
" session_user=" +
" current_user=" +
" current_schema="
rset.close();
+
rset.getString(2)+
rset.getString(3)+
+ rset.getString(4) );
1020
5254ch23cmp2.pdf 40
2/28/2005 6:53:05 PM
Invoker and Definer Rights
stmt.close();
}
}
tkyte@TKYTE816> host dropjava -user tkyte/tkyte ir_java.java
tkyte@TKYTE816> host loadjava -user tkyte/tkyte -synonym -grant u1 -verbose resolve ir_java.java
initialization complete
loading : ir_java
creating : ir_java
resolver :
resolving: ir_java
synonym : ir_java
By default, the above routine is loaded with invoker rights. Now we'll load the same routine but with a
different name. When we loadjava this routine, we'll specify it as a definer rights routine:
tkyte@TKYTE816> host type dr_java.java
import java.sql.*;
import oracle.jdbc.driver.*;
public class dr_java
{
... same code as above ...
}
tkyte@TKYTE816> host dropjava -user tkyte/tkyte dr_java.java
tkyte@TKYTE816> host loadjava -user tkyte/tkyte -synonym -definer -grant u1 verbose -resolve dr_jav
initialization complete
loading : dr_java
creating : dr_java
resolver :
resolving: dr_java
synonym : dr_java
Now, the only difference between IR_JAVA and DR_JAVA is their class name, and the fact that DR_JAVA
was loaded with -definer.
Next, I created the PL/SQL call specs so we can run these procedures from SQL*PLUS. Notice that
there are four versions here. All calls to Java stored procedures are ultimately via the SQL layer. Since
this SQL layer is really just a PL/SQL binding, we can specify the AUTHID clause here as well. We need
to see what happens when an invoker/definer rights PL/SQL layer calls the invoker/definer rights Java
procedure:
tkyte@TKYTE816> create OR replace procedure ir_ir_java
2 authid current_user
3 as language java name 'ir_java.test()';
4 /
Procedure created.
tkyte@TKYTE816> grant execute on ir_ir_java to u1;
1021
5254ch23cmp2.pdf 41
2/28/2005 6:53:05 PM
Chapter 23
Grant succeeded.
tkyte@TKYTE816> create OR replace procedure dr_ir_java
2 as language java name 'ir_java.test()';
3 /
Procedure created.
tkyte@TKYTE816> grant execute on dr_ir_java to u1;
Grant succeeded.
tkyte@TKYTE816> create OR replace procedure ir_dr_java
2 authid current_user
3 as language java name 'dr_java.test()';
4 /
Procedure created.
tkyte@TKYTE816> grant execute on ir_dr_java to u1;
Grant succeeded.
tkyte@TKYTE816> create OR replace procedure dr_dr_java
2 authid current_user
3 as language java name 'dr_java.test()';
4 /
Procedure created.
tkyte@TKYTE816> grant execute on dr_dr_java to u1;
Grant succeeded.
Now we need to create and populate the table T in the TKYTE schema:
tkyte@TKYTE816> drop table t;
Table dropped.
tkyte@TKYTE816> create table t ( msg varchar2(50) );
Table created.
tkyte@TKYTE816> insert into t values ( 'This is T owned by ' || user
);
1 row created.
So now we are ready to test this using U1, who will just happen to have a table T with a row identifying
the owner as well:
tkyte@TKYTE816> @connect u1/pw
u1@TKYTE816> drop table t;
1022
5254ch23cmp2.pdf 42
2/28/2005 6:53:05 PM
Invoker and Definer Rights
Table dropped.
u1@TKYTE816> create table t ( msg varchar2(50) );
Table created.
u1@TKYTE816> insert into t values ( 'This is T owned by ' || user
);
1 row created.
u1@TKYTE816> set serveroutput on size 1000000
u1@TKYTE816> exec dbms_java.set_output(1000000);
PL/SQL procedure successfully completed.
u1@TKYTE816> exec tkyte.ir_ir_java
This is T owned by U1 session_user=U1 current_user=U1 current_schema=U1
PL/SQL procedure successfully completed.
This shows that when the invoker rights Java stored procedure is called via an invoker rights PL/SQL
layer, it behaves as an invoker rights routine. U1 is the current user and current schema, the SQL in the
Java stored procedure accessed U1.T, not TKYTE.T. Now, let's call that same bit of Java via a definer
rights layer:
u1@TKYTE816> exec tkyte.dr_ir_java
This is T owned by TKYTE session_user=U1 current_user=TKYTE current_schema=TKYTE
PL/SQL procedure successfully completed.
Now, even though the Java stored procedure is an invoker rights routine, it is behaving as if it were a
definer rights routine. This is expected, as we saw above. When an invoker rights routine is called by a
definer rights routine, it will behave much like the definer rights routine. There are no roles; the current
schema is statically fixed, as is the current user. This routine queries TKYTE.T not U1.T as before, and
the current user/schema is fixed at TKYTE.
Continuing on, we'll see what happens when an invoker rights PL/SQL layer calls the definer rights
loaded Java stored procedure:
u1@TKYTE816> exec tkyte.ir_dr_java
This is T owned by TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE
PL/SQL procedure successfully completed.
This shows that by loading the Java with –definer, it runs using definer rights, even when called by an
invoker rights layer. The last example should be obvious by now. We have a definer rights PL/SQL
layer invoking a Java definer rights routine:
u1@TKYTE816> exec tkyte.dr_dr_java
This is T owned by TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE
PL/SQL procedure successfully completed.
And of course, it executes as a definers rights routine, as expected.
1023
5254ch23cmp2.pdf 43
2/28/2005 6:53:05 PM
Chapter 23
Given the above, you might not even notice the Java stored procedure is loaded with invoker rights by
default, since the PL/SQL call spec is typically the invoker of the Java stored procedure, and this by
default compiles with definer rights. Typically, the schema that loads the Java is the schema that creates
the call spec, and if they create it with definer rights, the Java appears to have definer rights as well (and
for all intents and purposes it does in that case). I would hazard a guess that most people are not aware
of the fact that Java is loaded this way, as it almost never appears to be an invoker rights routine. Only if
the call spec is created in the schema with AUTHID CURRENT_USER does it make itself apparent.
The other case where it 'matters' that Java is loaded with invoker rights by default, is when the call spec
is defined in a wholly different schema from that of the Java bytecode. Using the same loaded Java code
above, I had U1 create some call specs to invoke the Java in TKYTE's schema. In order to do this, U1 was
granted CREATE PROCEDURE. Also, this relies on the fact that when the Java code was loaded, we used
-synonym, which created a public synonym for the loaded Java and -grant U1, which gave U1 direct
access to this Java code. This is the result:
u1@TKYTE816> create OR replace procedure ir_java
2 authid current_user
3 as language java name 'ir_java.test()';
4 /
Procedure created.
u1@TKYTE816> exec ir_java
This is T owned by U1 session_user=U1 current_user=U1 current_schema=U1
PL/SQL procedure successfully completed.
So this shows that this invoker rights procedure (in fact a definer rights procedure would have the same
effect) owned by U1 runs the SQL in the Java code as if U1 had loaded it. It shows that the Java code is
loaded with invoker rights. If it were not, the SQL in the Java code would execute with the name
resolution and privileges of TKYTE, not U1. This next example shows the definer rights loaded by the U1
schema. Java does execute in the domain of TKYTE:
u1@TKYTE816> create OR replace procedure dr_java
2 as language java name 'dr_java.test()';
3 /
Procedure created.
u1@TKYTE816> exec dr_java
This is T owned by TKYTE session_user=U1 current_user=TKYTE current_schema =TKYTE
PL/SQL procedure successfully completed.
This shows that the Java code loaded with definer rights runs as TKYTE, not as U1. We had to force this
Java code to load with definer rights using -definer, showing the Java stored procedure is 'backwards'
with regards to this when compared to PL/SQL.
1024
5254ch23cmp2.pdf 44
2/28/2005 6:53:05 PM
Invoker and Definer Rights
Errors You Might Encounter
Beyond what we just discussed in the Caveats section, there are no special errors you can expect when
using definer or invoker rights. When using invoker rights, it is important to understand more fully how
PL/SQL processes embedded SQL so as to avoid issues with SELECT * changing the order of columns,
'hidden' columns at run-time, and so on. Additionally, with invoker rights, your PL/SQL code that
would normally run without any issues whatsoever may fail to run at various places for different users.
The reason being that objects are being resolved differently. In different schemas, the required
privileges may not be in place, data types might be different, and so on.
In general, with invoker rights procedures, your code must be a little more robust, and you must expect
errors where errors would not normally occur. Static references no longer guarantee clean running
code. It will be more like maintaining an ODBC or JDBC program with straight SQL calls. You control
the 'linkage' of your program (you know what subroutines in your client application will be called), but
you have no control over when the SQL will execute until you actually execute it. The SQL invoked in
an invoker rights PL/SQL routine will behave just like it would in a JDBC client application. Until you
test every execution path with every user, you will never be 100 percent sure that it will execute
flawlessly in production. Hence, you must code much more error handling than otherwise necessary in
a traditional stored procedure.
Summary
In this chapter we thoroughly explored the concepts of definer rights and invoker rights procedures. We
learned how easy it is to enable invoker rights, but we also learned of the price that is to be paid with
regards to:
❑
Error detection and handling.
❑
Subtle errors that could be introduced by different table structures at run-time.
❑
Additional shared SQL area overhead potential.
❑
Additional parse times incurred.
At first glance, these seem too high a price to pay – and in many cases it is. In other cases, such as the
generic routine to print out comma-separated data from any query, or to print out the results of a query
down the screen instead of across the screen, it is an invaluable feature. Without it, we just could not
accomplish what we set out to do.
Invoker rights routines make the most sense in the following cases:
❑
When the SQL to be processed is dynamic in nature (as these examples are).
❑
When the SQL to be processed is set up to enforce security by the SCHEMAID, as in the case of
the data dictionary (or your own application).
❑
When you need roles to be in place, invoker rights routines are the only way to do it.
Invoker rights can be used to provide access to different schemas based on the current schema (as
returned by SYS_CONTEXT('USERENV','CURRENT_SCHEMA')), but care must be taken here to ensure
the schemas are consistent with each other, and that the necessary privileges are in place (or that your
code is set up to handle the lack of access gracefully). You must also be prepared to pay the price in
shared pool utilization, and additional overhead with regards to parsing.
Definer rights procedures are still the correct implementation for almost all stored procedures. Invoker
rights routines is a powerful tool, but should only be used where appropriate.
1025
5254ch23cmp2.pdf 45
2/28/2005 6:53:05 PM
Download