Revoking Oracle Permissions to multiple users at once - sql

We're rationalising our database user permissions and to that end we'd like to revoke all select permissions across all tables in a schema granted to all users (but not a specific role).
With some regular expressions I've tried creating a universal revoke for each table giving something like:
revoke select on TABLE1 from USER1,USER2,USER3...;
revoke select on TABLE2 from USER1,USER2,USER3...;
However as not all users were granted permissions to all tables, this results in the oracle error:
01927. 00000 - "cannot REVOKE privileges you did not grant"
Which makes sense but isn't helpful.
I also tried using All_Tab_Privs to get a list of all tables with the requisite grants to be revoked, and this works, but I can't see how to easily get from this to actually revoking the permissions.
SELECT * From All_Tab_Privs where grantor = 'GRANTING_USER' and grantee != 'READROLE' and grantee != 'PUBLIC' and grantee != 'SYS';
Any suggestions for how to do this without spending hours in a spreadsheet? I'm guessing some PL/SQL? Thanks.

A general pattern for this kind of job is to use an implicit cursor to return the data you want and then to build the DDL and execute them.
begin
for x in (select attribute_1, attribute_2 ... from ... where ...)
loop
execute immediate 'REVOKE ...'||x.attribute1||' etc';
end loop;
end;
/
A nice alternative is to build the SQL you want to execute in the SELECT and then just execute that. It's easier to test but the SQL then looks a bit clunky.

-- Revoke Privs Granted to Users
BEGIN
FOR r IN (SELECT * FROM all_tab_privs WHERE grantee IN ('USER_1', 'USER_2'))
LOOP
EXECUTE IMMEDIATE 'REVOKE '||r.privilege||' ON '||r.table_name||' FROM '||r.grantee;
END LOOP;
END;
/

Some PL/SQL and dynamic SQL:
begin
for r in (SELECT * From All_Tab_Privs
where grantor = 'GRANTING_USER'
and grantee != 'READROLE'
and grantee != 'PUBLIC'
and grantee != 'SYS')
loop
execute immediate 'revoke '||r.privilege||' from '||r.grantee;
end loop;
end;

I had a quite interesting situation on my database. Oracles own SQL Developer still listed the existing grants for each table, although they were gone from the all_tab_privs table (a SELECT brought no result for the searched grantees).
I've fixed that by using the following construct:
BEGIN
FOR r IN (SELECT owner, table_name FROM dba_tables WHERE owner IN ('owner1', 'owner2'))
LOOP
EXECUTE IMMEDIATE 'REVOKE ALL ON '||r.owner||'.'||r.table_name||' FROM grantee1, grantee2';
END LOOP;
END;
/
This first selects all owners and table names from each table of certain owners. Then it revokes all grants for certain grantees.
The good thing is, that this won't fail if there aren't any grants for one of the listed grantee for one of the selected tables.
Mind that this should be executed from an administrator account, like system, so the current user has the right to revoke grants for tables of another user.

Related

Querying for DB Users who have a certain privilege e.g: SESSION privilege

I'm trying to query for DB Users who have a certain privilege like SESSION or CREATE TABLE privilege, any privilege whatsoever.
I couldn't wrap my head around on how to look for the privilege in all possible tables at once.
For example: The user 'user01' may not have direct table privileges but he may have them through his role.
How would you query for the possession of a direct and indirect specific privilege that you would check for with where privilege='such and such privilege'.
This is the idea I have so far
set serveroutput on;
CREATE OR REPLACE PROCEDURE SP_WHO_HAS_PRIVILEGE(arg_priv varchar2)
IS
CURSOR grantee_cur IS
SELECT GRANTEE FROM DBA_SYS_PRIVS WHERE PRIVILEGE=arg_priv
UNION
SELECT DISTINCT GRANTEE FROM DBA_TAB_PRIVS WHERE PRIVILEGE=arg_priv
UNION
SELECT GRANTEE FROM DBA_ROLE_PRIVS WHERE GRANTED_ROLE IN
( SELECT ROLE FROM ROLE_SYS_PRIVS WHERE PRIVILEGE = arg_priv)
UNION
SELECT GRANTEE FROM DBA_ROLE_PRIVS WHERE GRANTED_ROLE IN
(SELECT ROLE FROM ROLE_TAB_PRIVS WHERE PRIVILEGE = arg_priv);
BEGIN
DBMS_OUTPUT.PUT_LINE('Grantees: ');
FOR rec IN grantee_cur LOOP
DBMS_OUTPUT.PUT_LINE(rec.grantee);
END LOOP;
END;
/
BEGIN
SP_WHO_HAS_PRIVILEGE(&priv);
END;
/
Not sure if this is the right answer or not..
the thing you are trying to achieve can't be done by one parameter ,you need to build the procedure with either two parameters privilege and username or do it in steps.
e.g the following procedure will give you all the users and roles that owns the privilege you are looking for , then from that list you can pick the user you want to audit and look for his specific privileges in the same three views namely DBA_TAB_PRIVS , DBA_SYS_PRIVS and DBA_ROLE_PRIVS.
CREATE OR REPLACE PROCEDURE SP_WHO_HAS_PRIVILEGE(arg_priv varchar2)
IS
CURSOR grantee_cur IS SELECT GRANTEE FROM DBA_SYS_PRIVS WHERE PRIVILEGE=arg_priv
UNION all
SELECT DISTINCT GRANTEE FROM DBA_TAB_PRIVS WHERE PRIVILEGE=arg_priv
UNION all
SELECT GRANTEE FROM DBA_ROLE_PRIVS WHERE GRANTED_ROLE = arg_priv;
BEGIN
DBMS_OUTPUT.PUT_LINE('Grantees: ');
FOR rec IN grantee_cur LOOP
DBMS_OUTPUT.PUT_LINE(rec.grantee);
END LOOP;
END;
/

Why does my query return no records inside a program (PL/SQL), but does when ran manually in SQL?

Hi everyone and thank you for taking the time to help me.
I have the following query:
SELECT owner, object_name
FROM all_objects
WHERE owner IN ('EDI')
ORDER BY object_type, object_name;
As you can see in the screenshot it returns some values.
When I call the query from inside a program, it is not returning any values (see second screenshot).
Test code to illustrate this is:
CREATE OR REPLACE PROCEDURE my_test
AS
BEGIN
DBMS_OUTPUT.put_line('Pre-Loop');
FOR indx IN (SELECT owner, object_name
FROM all_objects
WHERE owner IN ('EDI')
ORDER BY object_type, object_name)
LOOP
DBMS_OUTPUT.put_line('Object: ' || indx.owner || '.' || indx.object_name);
END LOOP;
DBMS_OUTPUT.put_line('Post-Loop');
END;
/
BEGIN
my_test();
END;
/
The EDI schema is brand new, so I suspect this is a grants/privileges issue, but I can't seem to find what I may be missing in order for this to work. I have tried running this as both the EDI user and SYS.
EDIT after getting an answer:
I mentioned in a comment about finding an alternative to the official answer to this question and wanted to make sure it was shared for anyone reading this later so they can weigh the decision the same.
Applying grants like EXECUTE ANY PROCEDURE or SELECT ANY TABLE to the user that is expected to run the code will work, but I am sure there are reasons not to give such wide open grants.
Your stored procedure is a definer's rights stored procedure. That means that it doesn't have access to privileges that are granted via roles only those privileges that are granted directly to the owner of the procedure. Ad hoc SQL, on the other hand, runs with the privileges of whatever roles are enabled for the current session in addition to the user's direct grants. Most likely, the owner of the procedure has access to the tables in question via roles rather than via direct grants.
You can test this by running
set role none;
and then running the ad hoc SQL statement. If my wager is right, the ad hoc SQL will now return 0 rows since you've disabled all the roles for the session.
Depending on what you are going to do with the procedure, you may be able to solve the problem by turning it into an invoker's rights stored procedure.
CREATE OR REPLACE PROCEDURE my_test
AUTHID CURRENT_USER
AS
That will cause the procedure to run with the privileges of the invoker's session (including privileges granted through roles) rather than those of the definer. Assuming that all the users you want to call the procedure will have access to the EDI tables, that should be sufficient.
This is what you have now - no result at all:
SQL> CREATE OR REPLACE PROCEDURE my_test
2 AS
3 BEGIN
4 DBMS_OUTPUT.put_line('Pre-Loop');
5
6 FOR indx IN (SELECT owner, object_name
7 FROM all_objects
8 WHERE owner IN ('SCOTT')
9 AND rownum < 5 -- you don't have that
10 ORDER BY object_type, object_name)
11 LOOP
12 DBMS_OUTPUT.put_line('Object: ' || indx.owner || '.' || indx.object_name);
13 END LOOP;
14
15 DBMS_OUTPUT.put_line('Post-Loop');
16 END;
17 /
Procedure created.
SQL> BEGIN
2 my_test();
3 END;
4 /
PL/SQL procedure successfully completed.
But, if you enable serveroutput, here it is!
SQL> set serveroutput on --> this!
SQL>
SQL> BEGIN
2 my_test();
3 END;
4 /
Pre-Loop
Object: SCOTT.BONUS
Object: SCOTT.DEPT
Object: SCOTT.EMP
Object: SCOTT.SALGRADE
Post-Loop
PL/SQL procedure successfully completed.
SQL>

In Oracle, Can you create a new table with the same groups if you use CTAS Query?

I use the query CTAS to create a new table, however, when CTAS has finished, other users canĀ“t select the new table, but they had access to the old, Is it a way to pass all the users and groups to the new table? because the old table will be deleted.
"A way" is to grant (at least) select privileges to all those users.
If you used a role and granted select privilege to that role, and then granted role to those users, things would be quite simpler - just grant select privilege on the new table to the same role, and everyone will "see" it.
Otherwise, you can write query to create those grant statements for you.
For example, in Scott's schema there's the EMP table. I've previously granted privileges on it to other users in my database, and now I'm going to create a "new" CTAS table and grant privileges to the same set of users.
SQL> create table my_new_table as select * from emp;
Table created.
SQL> select 'grant select on my_new_table to ' || grantee ||';' run_me
2 from all_tab_privs_made
3 where owner = 'SCOTT'
4 and table_name = 'EMP';
RUN_ME
---------------------------------------------------------------
grant select on my_new_table to SYS;
grant select on my_new_table to SUPERUSER;
grant select on my_new_table to MY_ROLE;
grant select on my_new_table to MIKE;
Now simply copy/paste the above bunch of grant statements:
SQL> grant select on my_new_table to SYS;
Grant succeeded.
SQL> grant select on my_new_table to SUPERUSER;
Grant succeeded.
SQL> grant select on my_new_table to MY_ROLE;
Grant succeeded.
SQL> grant select on my_new_table to MIKE;
Grant succeeded.
SQL>
If there's zillion of users, PL/SQL option would be simpler as it would do everything for you (i.e. no copy/pasting):
SQL> begin
2 for cur_r in (select grantee
3 from all_tab_privs_made
4 where owner = 'SCOTT'
5 and table_name = 'EMP'
6 )
7 loop
8 execute immediate 'grant select on my_new_table to ' || cur_r.grantee;
9 end loop;
10 end;
11 /
PL/SQL procedure successfully completed.
SQL>
If you create a table using CTAS from an existing one, the new one is a new segment, therefore it lacks of privileges. You need to recover the permissions granted to the old table and granting to the new one. For that you can use several alternatives ( dbms_metadata, dynamic sql ).
For the purposes , I'd do it like this
SQL> CREATE TABLE T2 AS SELECT * FROM T1 ;
SQL> begin
dbms_metadata.set_transform_param (dbms_metadata.session_transform,
'SQLTERMINATOR', true);
dbms_metadata.set_transform_param (dbms_metadata.session_transform, 'PRETTY',
true);
end;
/
select replace(dbms_metadata.get_dependent_ddl('OBJECT_GRANT', 'T1', 'OWNER_OF_T1' ),'T1','T2') AS ddl
from dual;
The first part is for creating in a nice format the list of necessary grants. The second part retrieves all the privileges granted to T1 and generates the list of grants statements for running to the T2 table. Then you only need to run the list of grants
As I said, there are several alternatives to do this.
Regards

Oracle: How can you clean up every grant a user has?

I'm looking for a quick easy way to revoke every privilege a user has to tables, views, etc. Is there any simple magic that can do this?
The purpose of doing this is to start fresh on what access should have.
When you find out which privileges user has, e.g.
SQL> select * From user_sys_privs;
USERNAME PRIVILEGE ADM
------------------------------ ---------------------------------------- ---
SCOTT CREATE DATABASE LINK NO
SCOTT CREATE ROLE NO
SCOTT CREATE VIEW NO
SCOTT CREATE TYPE NO
SCOTT CREATE PROCEDURE NO
SCOTT UNLIMITED TABLESPACE NO
SCOTT CREATE PUBLIC SYNONYM NO
SCOTT CREATE TABLE NO
SCOTT CREATE TRIGGER NO
SCOTT CREATE SEQUENCE NO
SCOTT CREATE SESSION NO
11 rows selected.
SQL>
then write query which will write some code for you:
SQL> select 'revoke ' || privilege || ' from scott;'
2 from user_sys_privs;
'REVOKE'||PRIVILEGE||'FROMSCOTT;'
--------------------------------------------------------
revoke CREATE DATABASE LINK from scott;
revoke CREATE VIEW from scott;
revoke CREATE ROLE from scott;
revoke UNLIMITED TABLESPACE from scott;
revoke CREATE PROCEDURE from scott;
revoke CREATE TYPE from scott;
revoke CREATE PUBLIC SYNONYM from scott;
revoke CREATE TABLE from scott;
revoke CREATE TRIGGER from scott;
revoke CREATE SESSION from scott;
revoke CREATE SEQUENCE from scott;
11 rows selected.
SQL>
Now copy/paste those revoke statements and run them.
However, that's not all. User can have additional privileges, so - as a privileged user - query DBA_SYS_PRIVS, DBA_ROLE_PRIVS, DBA_TAB_PRIVS.
In order not to think too much :), have a look at how Pete Finnigan did that. Script dates from 2003, but - it'll give you idea how to do it.
Also, probably the simplest way to do it would be to drop that user (but that's, I suppose, not an option).
I kept thinking there was some sort of REVOKE ALL command
Alas, no. Privileges are revoked (and granted) atomically, which is the way it should be. Wanting to revoke all privileges from a user is a product of the same mindset which lead to granting too many and/or too powerful privileges in the first place.
There are three classes of granted privilege:
role
system (CREATE TABLE, CREATE SESSION, etc)
object access (tables, views, procedures etc in other schemas)
Each has a different set of views over the data dictionary.
USER_ROLE_PRIVS ( also ALL_, DBA_ )
USER_SYS_PRIVS ( also ALL_, DBA_ )
USER_TABLE_PRIVS ( also ALL_, DBA_ )
We can use these views to generate REVOKE statements. It seems peculiar to do this as the user in question. So, a a power user (i.e. a DBA) execute something like this:
begin
dbms_output.put_line('Revoking granted roles ...');
for r in ( select * from dba_role_privs
where grantee = 'JOESOAP' )
loop
dbms_output.put_line('revoke ' || r.granted_role ||' from ' || r.grantee ||';');
end loop;
dbms_output.put_line('Revoking granted system privileges ...');
for r in ( select * from dba_sys_privs
where grantee = 'JOESOAP' )
loop
dbms_output.put_line('revoke ' || r.privilege ||' from ' || r.grantee ||';');
end loop;
dbms_output.put_line('granted access privileges ...');
for r in ( select * from dba_tab_privs
where grantee = 'JOESOAP' )
loop
dbms_output.put_line('revoke ' || r.privilege || ' on ' || r.owner||'.'||r.table_name ||' from ' || r.grantee ||';');
end loop;
end;
/
This will output commands to the screen - use an IDE like SQL Developer to make this less tricky - which you can review and save as an executable script. I suggest you do this rather than have the loops EXECUTE IMMEDIATE simply because you need to have a record of what privileges you've zapped, and also to stop you accidentally de-authorising something or somebody which might come back to bite you later.
In fact, rather than revoking all privileges and re-granting some of them it would be better to see all the privileges the user has and just revoke the ones which shouldn't have been granted.

Select access to all tables

In oracle 11g, I have a owner schema which contains the relevant tables required for an app.
For my external application i am trying to create another user which should have select access on certain tables to the above schema.
Lets call the owner schema A and the new schema B
create user B identified by blah;
grant connect to B;
After logging into the original schema A i ran the following (for demo i am giving select on all tables)
begin
FOR x IN (select * from user_tables)
LOOP
EXECUTE IMMEDIATE 'GRANT SELECT ON ' || x.table_name || ' TO some_role';
END LOOP;
end;
grant some_role to B;
If i login as user B and execute a query like
select count(*) from A.some_table ;
it works. But without creating a public synonym is it possible for me to grant
in such a way that the following works when logged in as user B?
select count(*) from some_table;
Thanks
You can create private synonyms for user B to use.
As user B, run the following block:
begin
FOR t IN (SELECT * FROM role_tab_privs WHERE grantee = 'SOME_ROLE' and owner = 'A')
LOOP
EXECUTE IMMEDIATE 'CREAETE SYNONYM ' || table_name || ' FOR A.' || table_name';
END LOOP;
end;
if you are just reading data from tables owned by userB, you can set below command and run SELECT on tables as if they were in userA schema:
alter session set current_schema=userB
(logged as UserA)
Then below two commands will be equivalent (have same result):
select ... from userB.TABLE1
select ... from TABLE1
GRANT SELECT ANY TABLE TO YOUR_USER_OR_ROL;