The first of the result_cache usage restrictions (--link) is - not defined in a module that has invoker's rights or in an anonymous block.
What does "invoker's rights" mean ? Does it mean the user_procedures.AUTHID shouldnt be "DEFINER" for this function ?
Invoker and Definer Rights When Executing Oracle PL/SQL Code
The rule you are interpreting restricts usage of certain kinds of PL/SQL code that is stored in the database cache. The types of PL/SQL code included are segments that are within:
ANONYMOUS BLOCKS (i.e., these are code snippets run through SQL Plus or some other input without actually being a part of a persistent object in the database, such as a stored procedure, function or package)
DEFINED WITH INVOKER's RIGHTS: (See Below for Explanation)
An INVOKER means that whatever USER account that executes a PL/SQL Object, the procedure runs with the privileges of that USER account instead of the customary privileges which are those of the DEFINER (owner of the object).
Tom Kyte has a good discussion thread on the topic of definer vs. invoker rights on his Ask-Tom site.
The most notable comment from the above reference is this example block of code:
In the above, if user1 created:
create or replace procedure proc1
authid current_user
as
begin
for x in ( select * from my_table ) loop
null;
end loop;
end;
/
In the following line: authid current_user, this is the comment for setting an INVOKER's rights. The table my_table in the reference has multiple possibilities:
When PROC1 is executed where the DEFINER and the INVOKER are the same, (i.e., OWNER or USER1) this table will be: USER1.MY_TABLE
When PROC1 is executed by an INVOKER or user other than the OWNER of the procedure PROC1, (i.e., USER2) this table can be one of these possible targets:
A PUBLIC owned table named MY_TABLE
A Private or Publicly owned SYNONYM for a table typed object called MY_TABLE
A local TABLE owned by USER2 (i.e., USER2.MY_TABLE) of the same name, MY_TABLE.
If the above cases are true, then there is a great deal of ambiguity surrounding the reference involved with the procedure USER1.PROC1. The actual, physical table being used are different based on the context of the invoking user identity and the accessibility/existence of aliased pointers (SYNONYMS).
The result is that the usefulness of a cached value for a procedure with INVOKER's rights enabled is questionable given the variable nature of its internal references.
Related
I have two schemas in oracle DB, say A and B.
Schema A has a table tabA and Schema B as a table tabB.
Now in Schema B, this works:
select * from A.tabA
But, I created a package in schema B that has a procedure FOO. The procedure essentially does the same thing..i.e.
PROCEDURE FOO IS
BEGIN
INSERT INTO B.tabB (select * from A.tabA)
END FOO
This doesn't compile and gives ORA-01031 Insufficient privileges error.
I had to go to schema A and explicitly grant SELECT privilege to schema B to make the procedure compile successfully.
Now A.tabA is not the only table and new tables will be added in the future. In the procedure FOO, I fetch the table name dynamically. So, It could be A.tabC tomorrow, A.tabD the day after. I don't want to manually assign the privileges in every new table created in schema A.
Why privileges are specifically required in procedure while not required in worksheet?
How to assign privileges automatically to new tables?
A definer's rights stored procedure can't access permissions granted to a role only those granted directly to the owner. I would guess that B has been given select access on a.tabA via a role, which would work if you ran an ad-hoc SQL statement (assuming the role was enabled for the session) but not for a definer's rights stored procedure. Most likely, whatever administrative process manages to grant the select privilege to the role would need to be modified to grant the privilege directly to b instead of or in addition to the role. Alternately, you could make the procedure an invoker's rights stored procedure (add authid current_user to the declaration before the as). But that would require that any user that wants to execute the procedure would need to have access to a.tabA not just the procedure owner.
If you do want an invoker's rights stored procedure
PROCEDURE FOO
authid current_user
IS
BEGIN
INSERT INTO B.tabB (select * from A.tabA);
END FOO;
I have a problem with creating a stored procedure.
I have permissions to the table XYZ through Roles, so when I do a SELECT to the table XYZ I don't have problem, but when I want to create a stored procedure (inside my code I call to the table XYZ table) Oracle tell me:
Table or view doesn't exist.
When another user who have directly permissions for table XYZ create the stored procedure with the same code don't have problems.
So I hope you could help me. I think I need to assign another kind of permission to the Role that I use.
From the documentation:
How Roles Work in PL/SQL Blocks
Role behavior in a PL/SQL block is determined by the type of block and
by definer's rights or invoker's rights.
Roles Used in Named Blocks with Definer's Rights
All roles are disabled in any named PL/SQL block that executes with
definer's rights.
Roles Used in Named Blocks with Invoker's Rights and Anonymous PL/SQL
Blocks
Named PL/SQL blocks that execute with invoker's rights and anonymous
PL/SQL blocks are executed based on privileges granted through enabled
roles.
Roles Used in Named Blocks with Definer's Rights
All roles are disabled in any named PL/SQL block that executes with
definer's rights.
Examples of named PL/SQL blocks are stored procedures, functions, and
triggers.
Roles are not used for privilege checking and you cannot set roles
within a definer's rights procedure.
This is from the 19c documentation, but it has been true pretty much forever.
You only have 2 solutions:
either your Oracle user is granted the direct privilege on the table (ie without a role)
or you create a stored procedure with invoker rights.
Here is an example of invoker right procedure.
Use AUTHID CURRENT_USER clause and access table SCHEMA.XYZ with dynamic SQL because invoker rights are not enabled at PL/SQL compilation time but only at PL/SQL run time.
create or replace procedure myproc
authid current_user
as
l number;
begin
execute immediate 'select c from schema.xyz where rownum = 1' into l;
dbms_output.put_line('l= ' || l);
end;
/
Problem anamnesis:
In order to automate index recreation for a PVJOURNAL table in oracle I wrote a procedure in a package.
The table PVJOURNAL locates in PROVIEW schema. My SPACEMAN user has enough grants in that schema. Here below is a DDL of the package:
CREATE OR REPLACE
PACKAGE spaceman.tmp_itcm4052 is
-- This proc rebuilds ALL indexes of a specific.
PROCEDURE idx_rebuild;
END;
/
CREATE OR REPLACE
PACKAGE BODY spaceman.tmp_itcm4052 is
-- ===========================================================================
PROCEDURE idx_rebuild as
-- This proc rebuilds ALL indexes of a specific table. Run by Job
v_sql_str VARCHAR2(200);
begin
FOR rec in (
select owner ||'.'|| index_name as IDX_NAME,
tablespace_name as TblSpace
from sys.all_indexes
where upper(index_type) = 'NORMAL'
and upper(table_owner) = 'PROVIEW'
and upper(table_name) in ('PVJOURNAL')
)
LOOP
v_sql_str := 'ALTER INDEX '||rec.IDX_NAME||' rebuild tablespace '
||rec.TblSpace||' online';
-- ALTER INDEX ... SHRINK SPACE COMPACT
-- ALTER INDEX ... DEALLOCATE UNUSED SHRINK SPACE COMPACT
dbms_output.put_line('v_sql_str = '||v_sql_str);
execute immediate v_sql_str;
commit;
END LOOP;
end idx_rebuild;
END tmp_ITCM4052;
/
Problem itself:
The problem is that a call of the procedure under my SPACEMAN user
begin
spaceman.tmp_itcm4052.idx_rebuild;
end;
ends up with error
ORA-01031: insufficient privileges - full view of the error
But the direct run of the line, produced by the procedure, saved into v_sql_str variable and visualized by this piece of the procedure:
dbms_output.put_line('v_sql_str = '||v_sql_str);, done under the same SPACEMAN user works like a magic. proof that direct alter does work
Questions:
What wrong have I done to the gods of pl/sql? Why the call to the procedure fails whilst the same command, execurted directly - is not?
UPD: Not only GRANT INDEX ON PROVIEW.PVJOURNAL TO SPACEMAN; didn't help. Moreover, for whatewer reason not yet kown to me, GRANT ALTER ANY INDEX to SPACEMAN; didn't help either.
Had doublechecked it with
SELECT PRIVILEGE
FROM sys.dba_sys_privs
WHERE upper(grantee) = 'SPACEMAN'
UNION
SELECT PRIVILEGE
FROM dba_role_privs rp JOIN role_sys_privs rsp ON (rp.granted_role = rsp.role)
WHERE upper(rp.grantee) = 'SPACEMAN'
ORDER BY 1;
the privilege ALTER ANY INDEX (as well as ALTER ANY INDEXTYPE ) is in the list.
UPD 1: as it turned out - the ALTER ANY INDEX privilege is not sufficient in this case. What did helped me is that link, kindly provided by #Wernfried Domscheit. REspect, man!
The procedure created as
CREATE OR REPLACE
PACKAGE spaceman.tmp_itcm4052 authid CURRENT_USER is
works like a spell.
Inside a PL/SQL block you have only privileges which are granted to the user directly. Privileges which are granted by ROLE (e.g. DBA) do not apply inside a PL/SQL block (except role PUBLIC).
Grant CREATE ANY INDEX directly to the user, then it should work.
The behavior described above is the default, it is called "definer's rights unit". You can change it by adding AUTHID CURRENT_USER to your procedure/package. For more information see Invoker's Rights and Definer's Rights (AUTHID Property)
N.b. COMMIT is not required after a DDL statement. Oracle performs an implicit commit.
Different grants are used when you invoke direct SQL and when it is used in the procedure.
Privileges granted through a role (such as DBA) are not available to
definer's rights stored procedures which is the default.
Only privileges that are granted directly to the user are available in a
definer's rights stored procedure.
You must grant the ALTER ANY INDEX privileges directly to your user.
Cheers!!
Begin
Execute immediate 'CREATE SEQUENCE seq1 MINVALUE 1 MAXVALUE 99 START WITH 1INCREMENT BY 1 CACHE 20';
End;
Sequence creating successfully.
Create or replace procedure proc_seq as
Begin
Execute immediate 'CREATE SEQUENCE seq2 MINVALUE 1 MAXVALUE 99 START WITH 1INCREMENT BY 1 CACHE 20';
End;
Exec proc_seq();
Error: Insufficient Privilege...
This is a invokers/definers rights issue. By default a stored procedure has definer's right (DR - emphasis added):
During a server call, when a DR unit is pushed onto the call stack, the database stores the currently enabled roles and the current values of CURRENT_USER and CURRENT_SCHEMA. It then changes both CURRENT_USER and CURRENT_SCHEMA to the owner of the DR unit, and enables only the role PUBLIC.
If your privilege to create a sequence was granted to you via a role, then as that role will be disabled, the privilege isn't active. You can verify that is the case by running set role none, which will cause your anonymous block to fail with the same error.
As #Littlefoot said you can have the privilege granted directly to you instead of (or as well as) via the role.
Alternatively, you can specify that the procedure should have invoker's rights:
Create or replace procedure proc_seq
authid current_user
as
Begin
Execute immediate 'CREATE SEQUENCE seq2 MINVALUE 1 MAXVALUE 99 START WITH 1INCREMENT BY 1 CACHE 20';
End;
/
exec proc_seq;
PL/SQL procedure successfully completed.
select seq2.nextval from dual;
NEXTVAL
----------
1
Hopefully this is a contrived example; you wouldn't normally create database objects at runtime anyway...
The caveat to this approach is that it might not be suitable for how you intend the procedure to be invoked. If you're running it as yourself then that's fine, but if you will be granting execute permission to other users then they would have to have the relevant privileges (granted either directly or via a role); and would be creating objects in their own schema. That may be exactly what you do want. But if it isn't, and you don't want to grant create sequence to the callers or to have them create their own objects, then you can keep the procedure with definer's rights and grant the privilege to your user directly.
If you acquired CREATE SEQUENCE privilege via role (apparently, you did), it will work in SQL layer as well is in anonymous PL/SQL blocks (your first code).
However, it won't work in named PL/SQL procedures (your second code) - to do so, you (which means: user that runs that code) will have to be granted that privilege directly, not via role.
I want to perform some dynamic DDL in a procedure owned by an admin user. I'd like to execute this procedure with a technical operational user with definer rights (operational user doesn't have the create table role).
The problem is the 'create table' permission is granted to the admin user through use of a role, which doesn't allow me to execute the DDL as it seems that roles don't count in named pl/sql blocks.
create or replace
PROCEDURE test_permissions AUTHID DEFINER AS
v_query_string VARCHAR2(400 CHAR) := 'CREATE TABLE TEST(abcd VARCHAR2(200 CHAR))';
BEGIN
EXECUTE IMMEDIATE v_query_string;
END;
What I tried:
Running the procedure from a function set to AUTHID DEFINER with AUTHID CURRENT_USER on the proc (hoping the function would cascade the definer somehow)
Putting an anonymous block inside the function to execute the DDL (as roles seem to count in anonymous block)
If I set the AUTHID to CURRENT_USER, I can execute the procedure correctly with the admin user.
Is there any way I can work around this without granting CREATE TABLE directly to the admin user?
You can only set a role within a PL/SQL stored procedure/function if it has Invoker's Rights (AUTHID CURRENT_USER)(see doc). Which means that you can't use ops_user to call admin_user's procedure and then access admin_user's roles. If your DBAs insist on using a role to control the CREATE TABLE privilege, here's the approach I've seen before:
create or replace package admin_user.role_test authid current_user is
procedure test_permissions;
end role_test;
/
create or replace package body admin_user.role_test is
procedure test_permissions is
v_query_string VARCHAR2(400 CHAR) := 'begin
dbms_output.put_line(''after'');
for r in (select role from session_roles) loop
dbms_output.put_line(r.role);
end loop;
end;';
begin
dbms_output.put_line('before');
for r in (select role from session_roles) loop
dbms_output.put_line(r.role);
end loop;
DBMS_SESSION.SET_ROLE('CREATE_TABLE_ROLE IDENTIFIED BY "SECRET_PASSWORD"');
execute immediate v_query_string;
DBMS_SESSION.SET_ROLE('ALL EXCEPT CREATE_TABLE_ROLE'); -- restore defaults
end;
end role_test;
/
grant execute on admin_user.role_test to ops_user;
This will temporarily grant the role to ops_user just to execute your code. By default the ops_user should not be able to view the admin_user's package body source. You could probably wrap the package body to further protect the password. But password security aside, my biggest concern with this approach is that Oracle doesn't provide a nice way to disable a single role, so if ops_user has other password-protected roles, this code might raise an ORA-01979 when it tries to restore them.
So, there's an answer, but I'd still recommend doing what the other commenters suggested and granting CREATE TABLE to your admin user.