PL/SQL Loop to Revoke Privileges - sql

I need to create a procedure that does the following:
Revoke system privileges granted directly to the user. (Table: dba_sys_privs)
Revoke object privileges granted directly to the user. (Table: dba_tab_privs)
It revokes roles granted directly to the user. (Table: dba_role_privs)
It has to loop through the tables and remove all of them for a user. So far I have #1 and #3 working. But I need to add #2, and I can't seem to figure out how to do it. This is my code for #2, and I keep getting an error:
//REVOKING OBJECT PRIVILEGES
CREATE or REPLACE PROCEDURE deactivate_user
(p_username IN VARCHAR2) AS
l_username VARCHAR2(30) := UPPER(p_username);
BEGIN
FOR rec IN (SELECT * FROM dba_tab_privs WHERE GRANTEE = p_username)
LOOP
EXECUTE IMMEDIATE 'REVOKE ALL PRIVILEGES ON TABLE '||rec.owner||'.'||rec.table_name||' FROM '||rec.grantee;
END LOOP;
END;
/
When I try to execute the code: SQL> exec deactivate_user('BLAKE'), I get this error:
ERROR: at line 1:
ORA-06550: table or view does not exist
ORA-06512: at "SYS.DEACTIVATE_USER", line 7
Your help is greatly appreciated!

The correct command for table privileges is either:
REVOKE ALL PRIVILEGES FROM ...
or:
REVOKE READ FROM ...
And it's even better to add the TABLE keyword as well.
So the EXECUTE IMMEDIATE statement should be either:
EXECUTE IMMEDIATE 'REVOKE ALL PRIVILEGES ON TABLE ' || rec.owner || '.' || rec.table_name ||' FROM '|| rec.grantee;
or:
EXECUTE IMMEDIATE 'REVOKE ' || rec.privilege || ' ON TABLE ' || rec.owner || '.' || rec.table_name ||' FROM '|| rec.grantee;
Update
The most important thing was missing in my first answer: the table owner.

Related

grant a role to a selected subset

I have a role I want to grant to a selected subset of users. Can someone help me with the syntax? Something like this:
grant my_special_role to (select username from dba_users where username like 'john%')
This is supposed to grant my_special_role to all users named John!
You could do it in a loop, declarating a block of code
declare
begin
for rec in (select username from dba_users where username like 'john%')
loop
execute immediate 'grant my special role to ' || rec.username;
end loop;
end;
You can use a simple PL/SQL loop to execute the grants to all the users
BEGIN
FOR i IN (SELECT username
FROM dba_users
WHERE username LIKE 'john%')
LOOP
EXECUTE IMMEDIATE 'grant my_special_role to ' || i.username;
END LOOP;
END;
/

how to resolve error while using grant admin option in execute immediate

I am trying to run the below script. My script works fine without ' WITH ADMIN OPTION' in EXECUTE IMMEDIATE. But when using ' WITH ADMIN OPTION' i get below error.
"Error report - ORA-00900: invalid SQL statement ORA-06512: at line 17
00900. 00000 - "invalid SQL statement"".
SET SERVEROUTPUT ON;
DECLARE
v_Model_UserName VARCHAR2(30) := UPPER('&Model_UserName');
v_Cloned_UserName VARCHAR2(30) := UPPER('&Cloned_UserName');
v_dba_role_privs VARCHAR2(3000); -- for dba_role_privs
--- selecting the roles from model user (from dba_role_privs table)
CURSOR c_role_privs (var01 Varchar2 )is
SELECT granted_role from dba_role_privs where grantee = var01;
BEGIN
--- granting the roles from model user to cloned user (from dba_role_privs table)
OPEN c_role_privs (v_Model_UserName);
LOOP
FETCH c_role_privs INTO v_dba_role_privs;
EXIT WHEN c_role_privs%NOTFOUND;
EXECUTE IMMEDIATE 'grant'||v_dba_role_privs||' to '||v_Cloned_UserName||' WITH ADMIN OPTION';
END LOOP;
CLOSE c_role_privs;
END;
/
Try to add a space after grant keyword:
EXECUTE IMMEDIATE 'grant '||v_dba_role_privs||' to '||v_Cloned_UserName||' WITH ADMIN OPTION';

Oracle Procedure with an IF-THEN-ELSE produces error

I have created this procedure in Oracle, to assign a role to a user based on the grade stored in the grade column of the marketing table. However, when I run it I get errors.
Initial Problem
CREATE OR REPLACE PROCEDURE proc_assign_role IS
vn_grade NUMBER(5);
CURSOR cur_user_grade IS
SELECT grade, username
FROM marketing
WHERE grade BETWEEN 1 AND 3;
BEGIN
FOR rec_cur_user_grade IN cur_user_grade
vn_grade:=
IF grade= 1
THEN
GRANT ROLE admin_staff;
ELSIF grade= 2 THEN
GRANT ROLE marketing_staff;
ELSIF grade= 3 THEN
GRANT ROLE event_staff;
END IF;
DBMS_OUTPUT.PUT_LINE(username||'YOU ARE A GRADE '||vn_grade|| 'USER');
END proc_assign_role;
/
This is the error I get:
ERROR at line 11: PLS-00103: Encountered the symbol "VN_GRADE" when expecting one of the following:
. ( * # % & - + / at loop mod remainder range rem ..
|| multiset
1. CREATE OR REPLACE PROCEDURE proc_assign_role IS
2. vn_grade NUMBER(5);
vn_grade:=
You need to assign a value to that line, or get rid of it. You can't assign an IF statement to a number variable. Probably get rid of it, then change your IF statement to look at the grade from the cursor. You also need to end your loop.
Additionally, you can't do a grant directly within a PL/SQL code block. You have to use the execute immediate statement for that. And you have to tell it who you're granting the role to.
FOR rec_cur_user_grade IN cur_user_grade LOOP
IF rec_cur_user_grade.grade= 1 THEN
execute immediate 'GRANT ROLE admin_staff to ' || rec_cur_user_grade.username;
ELSIF rec_cur_user_grade.grade= 2 THEN
execute immediate 'GRANT ROLE marketing_staff to ' || rec_cur_user_grade.username;
ELSIF rec_cur_user_grade.grade= 3 THEN
execute immediate 'GRANT ROLE event_staff to ' || rec_cur_user_grade.username;
END IF;
DBMS_OUTPUT.PUT_LINE(username||'YOU ARE A GRADE '||rec_cur_user_grade.grade|| 'USER');
END LOOP;
I'm seeing a few things that would keep this from working:
After your FOR statement, there's no LOOP statement (which is what the error is complaining about). There's also no END LOOP after your DBMS_OUTPUT.
vn_grade is followed by the := assignment operator, but nothing is being assigned to it.
The GRANT statements are written as bare DDL, which isn't allowed in PL/SQL. They need to be wrapped in EXECUTE IMMEDIATE.
grade and username need to be qualified by the cursor variable (e.g., rec_cur_user_grade.grade and rec_cur_user_grade.username).
Try something like this (which runs as an anonymous block, rather than a procedure, and uses an implicit cursor):
BEGIN
FOR rec_cur_user_grade IN (
SELECT grade, username
FROM marketing
WHERE grade BETWEEN 1 AND 3
)
LOOP
CASE rec_cur_user_grade.grade
WHEN 1 THEN
EXECUTE IMMEDIATE 'GRANT ROLE admin_staff TO ' || rec_cur_user_grade.username;
WHEN 2 THEN
EXECUTE IMMEDIATE 'GRANT ROLE marketing_staff TO ' || rec_cur_user_grade.username;
WHEN 3 THEN
EXECUTE IMMEDIATE 'GRANT ROLE event_staff TO ' || rec_cur_user_grade.username;
END CASE;
DMBS_OUTPUT.PUT_LINE(rec_cur_user_grade.username || ' YOU ARE A GRADE ' || rec_cur_user_grade.grade || ' USER');
END LOOP;
END;
/
grant is DDL and therefore cannot be used in PL/SQL directly. In order to accomplish this, the DDL needs to be executed dynamically, using execute immediately. Additionally, grant always requires you to specify the recipient of the role. The result would be something like this:
execute immediate 'GRANT ROLE admin_staff to ' || rec_cur_user_grade.username;
An ORA-00990: missing or invalid privilege error is fairly self-descriptive: the owner of the procedure does not have the necessary privileges to take the actions being attempted by the procedure.
The most likely culprit here is roles: permissions granted by a role cannot be used in a procedure. The first step you should take is to make sure that the owner of the procedure has been explicitly granted permission to administer the roles involved.

Oracle 11g: ORA-00604: error occurred at recursive SQL level 1

I executed the script below and it works:
BEGIN
FOR cur_rec IN (SELECT object_name, object_type
FROM user_objects
WHERE object_type IN
('TABLE',
'VIEW',
'PACKAGE',
'PROCEDURE',
'FUNCTION',
'SEQUENCE'
))
LOOP
BEGIN
IF cur_rec.object_type = 'TABLE'
THEN
EXECUTE IMMEDIATE 'DROP '
|| cur_rec.object_type
|| ' "'
|| cur_rec.object_name
|| '" CASCADE CONSTRAINTS';
ELSE
EXECUTE IMMEDIATE 'DROP '
|| cur_rec.object_type
|| ' "'
|| cur_rec.object_name
|| '"';
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ( 'FAILED: DROP '
|| cur_rec.object_type
|| ' "'
|| cur_rec.object_name
|| '"'
);
END;
END LOOP;
END;
/
But the problem is, after this, I cant grant, create or drop etc. in my database even I'm using a sysdba user.
I am getting the error:
ORA-00604: error occurred at recursive SQL level 1
ORA-00942: table or view does not exist
Please help. Thanks.
One possible cause for the recursive SQL error is triggers. You might have run into this scenario:
you have a trigger that fires for every DDL statement
this trigger tries to insert records into some kind of audit/log table
you audit/log table was dropped by your cleanup script
To get a list of all triggers, you can use
select * from dba_triggers
where trigger_type not in ('BEFORE EACH ROW','AFTER EACH ROW')
(you can exclude row-level triggers because they conceptually belong to the table and would have been automatically dropped when you dropped the table). After you've identified the offending trigger, you can either disable or drop it.

Execute Immediate within a stored procedure keeps giving insufficient priviliges error

Here is the definition of the stored procedure:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR) IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Here is the call:
CALL usp_dropTable('SOMESCHEMA', 'SOME_TABLE');
For some reason, I keep getting insufficient privileges error for the EXECUTE IMMEDIATE command. I looked online and found out that the insufficient privileges error usually means the oracle user account does not have privileges for the command used in the query that is passes, which in this case is DROP. However, I have drop privileges. I am really confused and I can't seem to find a solution that works for me.
Thanks to you in advance.
SOLUTION:
As Steve mentioned below, Oracle security model is weird in that it needs to know explicitly somewhere in the procedure what kind of privileges to use. The way to let Oracle know that is to use AUTHID keyword in the CREATE OR REPLACE statement. If you want the same level of privileges as the creator of the procedure, you use AUTHID DEFINER. If you want Oracle to use the privileges of the user currently running the stored procedure, you want to use AUTHID CURRENT_USER. The procedure declaration looks as follows:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR)
AUTHID CURRENT_USER IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Thank you everyone for responding. This was definitely very annoying problem to get to the solution.
Oracle's security model is such that when executing dynamic SQL using Execute Immediate (inside the context of a PL/SQL block or procedure), the user does not have privileges to objects or commands that are granted via role membership. Your user likely has "DBA" role or something similar. You must explicitly grant "drop table" permissions to this user. The same would apply if you were trying to select from tables in another schema (such as sys or system) - you would need to grant explicit SELECT privileges on that table to this user.
You should use this example with AUTHID CURRENT_USER :
CREATE OR REPLACE PROCEDURE Create_sequence_for_tab (VAR_TAB_NAME IN VARCHAR2)
AUTHID CURRENT_USER
IS
SEQ_NAME VARCHAR2 (100);
FINAL_QUERY VARCHAR2 (100);
COUNT_NUMBER NUMBER := 0;
cur_id NUMBER;
BEGIN
SEQ_NAME := 'SEQ_' || VAR_TAB_NAME;
SELECT COUNT (*)
INTO COUNT_NUMBER
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = SEQ_NAME;
DBMS_OUTPUT.PUT_LINE (SEQ_NAME || '>' || COUNT_NUMBER);
IF COUNT_NUMBER = 0
THEN
--DBMS_OUTPUT.PUT_LINE('DROP SEQUENCE ' || SEQ_NAME);
-- EXECUTE IMMEDIATE 'DROP SEQUENCE ' || SEQ_NAME;
-- ELSE
SELECT 'CREATE SEQUENCE COMPTABILITE.' || SEQ_NAME || ' START WITH ' || ROUND (DBMS_RANDOM.VALUE (100000000000, 999999999999), 0) || ' INCREMENT BY 1'
INTO FINAL_QUERY
FROM DUAL;
DBMS_OUTPUT.PUT_LINE (FINAL_QUERY);
cur_id := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.parse (cur_id, FINAL_QUERY, DBMS_SQL.v7);
DBMS_SQL.CLOSE_CURSOR (cur_id);
-- EXECUTE IMMEDIATE FINAL_QUERY;
END IF;
COMMIT;
END;
/
you could use "AUTHID CURRENT_USER" in body of your procedure definition for your requirements.
Alternatively you can grant the user DROP_ANY_TABLE privilege if need be and the procedure will run as is without the need for any alteration. Dangerous maybe but depends what you're doing :)