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