Running Oracle 11gR1 in an XP SP2 virtual machine. Full disclosure: This is for an assignment.
I'm attempting to audit whenever a user is granted the DBA role and fire off an email when the event occurs.
I believe the command AUDIT DBA; will audit all actions performed upon the DBA role. I have a fully working procedure which will take care of the email portion, but I'm not aware of a way for standard auditing to trigger the procedure in the same way a fine-grained auditing policy can.
I've tried using the policy
begin
dbms_fga.drop_policy
(object_schema => 'SYS',
object_name => 'DBA_ROLE_PRIVS',
policy_name => 'EXAMPLE');
dbms_fga.add_policy
(object_schema => 'SYS',
object_name => 'DBA_ROLE_PRIVS',
policy_name => 'EXAMPLE',
audit_condition => 'GRANTED_ROLE = DBA',
audit_column => 'GRANTED_ROLE',
handler_schema => 'SYS',
handler_module => 'FGA_NOTIFY');
end;
Where FGA_NOTIFY is the email procedure. But I get the notice "adding a policy to an object owned by SYS is not allowed." Searching through Oracle's documentation I have found no way around this.
My question is: can anyone suggest a method for auditing and Oracle database for when a user gains DBA role which can also trigger an email notification?
Thanks in advance for your help!
You could enable AUDIT_TRAIL and create a job that periodically querys the DBA_AUDIT_TRAIL view looking for the types of grants you want to check:
select os_username, username, userhost, terminal, timestamp, grantee
from dba_audit_trail
where action_name = 'GRANT ROLE'
and obj_name = 'DBA'
and timestamp >= (last_time_check_was_done)
You could also create a database trigger for that:
CREATE OR REPLACE TRIGGER TG_GRANTS_DATABASE
BEFORE GRANT ON DATABASE
DECLARE
V_NUM_GRANTEES BINARY_INTEGER;
V_GRANTEE_LIST ORA_NAME_LIST_T;
V_NUM_PRIVS BINARY_INTEGER;
V_PRIV_LIST ORA_NAME_LIST_T;
VB_AUDIT_PRIV BOOLEAN;
VB_AUDIT_GRANTEE BOOLEAN;
BEGIN
V_NUM_GRANTEES := ORA_GRANTEE (V_GRANTEE_LIST);
V_NUM_PRIVS := ORA_PRIVILEGE_LIST (V_PRIV_LIST);
-- Verify the privilege
VB_AUDIT_PRIV := FALSE;
FOR COUNTER IN 1 .. V_NUM_PRIVS
LOOP
IF V_PRIV_LIST (COUNTER) IN ('DBA') THEN
VB_AUDIT_PRIV := TRUE;
EXIT;
END IF;
END LOOP;
-- Verify the user
VB_AUDIT_GRANTEE := FALSE;
FOR COUNTER IN 1 .. V_NUM_GRANTEES
LOOP
IF V_GRANTEE_LIST (COUNTER) IN ('PUBLIC') THEN
VB_AUDIT_GRANTEE := TRUE;
EXIT;
END IF;
END LOOP;
-- Prevent the statement
-- or audit it (BEST DONE on 'AFTER GRANT ON DATABASE trigger')
IF VB_AUDIT_GRANTEE AND VB_AUDIT_PRIV THEN
RAISE_APPLICATION_ERROR(-20001,'Sorry, this can''t be done.');
END IF;
END;
This was an adaptation based on: http://examples.oreilly.com/oraclep3/individual_files/what_privs.sql
Related
I logged in as system and coded this:
CREATE TABLE EMPHOLIDAY(
EmpNo NUMBER(5),
m_Name VARCHAR2(60),
Holiday DATE
);
-- then i created 3 users:
ALTER SESSION SET "_ORACLE_SCRIPT" = TRUE;
CREATE USER Hann IDENTIFIED BY HANN;
CREATE USER Annu IDENTIFIED BY ANNU;
CREATE USER Theota IDENTIFIED BY THEOTA;
GRANT CREATE SESSION TO Hann;
GRANT CREATE SESSION TO Annu;
GRANT CREATE SESSION TO Theota;
Then i want to create policy like this:
1- 'Annu' can only SEE and Edit row of EMPHOLIDAY that has 'EmpNo' = 2.
2- 'Theota' can't see or edit EMPHOLIDAY.
3- 'Hann' can see all rows of EMPHOLDAY, but only can edit rows that have 'Holiday' >= CURRENT DATE.
I create the second policy like this:
CREATE OR REPLACE FUNCTION policy_2(
p_schema IN VARCHAR2,
P_object IN VARCHAR2
)
RETURN VARCHAR2
AS
BEGIN
RETURN 'USER != ''Theota''';
END;
/
BEGIN DBMS_RLS.add_policy(
object_schema => 'system',
object_name => 'EMPHOLIDAY',
policy_name => 'personalInfo',
policy_function => 'policy_2',
statement_types => 'SELECT, UPDATE, DELETE, INSERT',
update_check => TRUE
);
END;
And it worked. But for 2 other policies, i tried to change RETURN statement in POLICY_2 and statement_types some times but this seems not the right ways. Can someone help me solve these problems? Thanks alot.
I have an apex application where i need to send a notification mail to all the employees on the 5th of every month. So just for the sake of testing i am trying to send a mail in every 30 seconds. I created a job scheduler on a procedure to do the same. Here is the PLSQL code for it.
create or replace procedure send_notification_employee as
cursor c_employee is select * from EMPLOYEE;
r_employee c_employee%ROWTYPE;
begin
open c_employee;
loop
fetch c_employee into r_employee;
exit when c_employee%NOTFOUND;
APEX_MAIL.SEND(
p_to => r_employee.EMPLOYEE_EMAIL,
p_from => 'abc#gmail.com',
p_subj => 'Reminder : Meeting',
p_body => '<Some random message>');
end loop;
close c_employee;
end;
/
begin
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'send_notification',
job_type => 'stored_procedure',
job_action => 'send_notification_employee',
start_date => NULL,
repeat_interval => 'FREQ=SECONDLY;INTERVAL=30',
end_date => NULL);
end;
/
begin
DBMS_SCHEDULER.enable(
name => 'send_notification');
end;
/
I guess the code is correct. The only thing i am not sure of is to how to run this scheduler on the apex oracle application. Should i just execute these statements on the SQL Commands or is there any other way to do it?
Also i tried to execute the same statements in the SQL Commands tab but i don't receive any mails as such. Is there any issue with my code? Thanks in advance.
You need to set the security group if sending the mail from the database rather than from APEX directly. You should also use push_queue at the end of the procedure to clear out the table of unsent mail.
create or replace procedure send_notification_employee as
cursor c_employee is select * from EMPLOYEE;
r_employee c_employee%ROWTYPE;
l_workspace number;
begin
-- Get a valid workspace ID
SELECT MAX(workspace_id) INTO l_workspace FROM apex_applications WHERE application_id = <valid application_id>;
-- Set Workspace
wwv_flow_api.set_security_group_id(l_workspace);
open c_employee;
loop
fetch c_employee into r_employee;
exit when c_employee%NOTFOUND;
APEX_MAIL.SEND(
p_to => r_employee.EMPLOYEE_EMAIL,
p_from => 'abc#gmail.com',
p_subj => 'Reminder : Meeting',
p_body => '<Some random message>');
end loop;
close c_employee;
-- Finally force send
APEX_MAIL.PUSH_QUEUE;
end;
Re. how to execute - it depends on what you want to do. If you just want to run it on the 5th of every month just setup a scheduled job in the db to do it, as you have above. If you want to run on an adhoc basis just create a job in an APEX after submit process that calls the procedure and executes through the database.
As an aside, if you plan to create many mail procedures you may wish to create a helper procedure to get the workspace id / send the mail, and just call it from your other mail procedures.
These queries could be handy (To check if you have the correct set up):
Check if util_smtp is installed:
select * from dba_objects where object_name like 'UTL_SMTP%'
Check privileges:
select grantee , table_name , privilege from dba_tab_privs where table_name = 'UTL_SMTP'
Check open network hosts, ports:
select acl , host , lower_port , upper_port from DBA_NETWORK_ACLS;
Check network privileges:
select acl , principal , privilege , is_grant from DBA_NETWORK_ACL_PRIVILEGES
I want to grant the CREATE ANY DIRECTORY permission to a user, with the following restriction: all directories created by this user must be inside of /foo/bar, and any attempt to create a directory outside of this should fail with a permission error. How may I do this on Oracle 11G or 12C?
That depends, if you want to restrict which OS directories Oracle can access from utl_file commands, you can set the utl_file_dir parameter. Unfortunately, this parameter is system wide, so you won't be able to grant/revoke for a specific user using this parameter. Also keep in mind that if you make changes to this parameter, those changes won't go into effect until the Oracle database is restarted:
alter system set utl_file_dir = '/foo/bar' scope=spfile;
shutdown immediate;
startup open;
Consult the 12.1 Oracle Docs for more information regarding utl_file_dir.
That said, if you really want to restrict who can create Oracle Directories to specific OS directories, a procedure would be appropriate for that task since that would allow you to have finer grained control (and limit who has the very powerful create any directory privilege to the owner of the procedure):
sqlplus kjohnston
create or replace procedure mydircreate (p_dir varchar2)
as
ex_custom EXCEPTION;
PRAGMA EXCEPTION_INIT( ex_custom, -20001 );
begin
if lower(p_dir) not like '/foo/bar/%' then
raise_application_error( -20001, 'Not authorized' );
end if;
execute immediate 'create or replace directory mydir as ''' || p_dir || '''';
end mydircreate;
create user testuser identified by <password>;
grant create session to testuser;
grant execute on kjohnston.mydircreate to testuser;
exit;
sqlplus testuser
SQL> exec kjohnston.mydircreate('mydir', '/randomdir');
ORA-20001: Not authorized
SQL> exec kjohnston.mydircreate('mydir', '/foo/bar/baz');
PL/SQL procedure successfully completed.
You can include this restriction in trigger. List of system events and attributes Working with system events
CREATE OR REPLACE TRIGGER trg_before_ddl
BEFORE DDL ON DATABASE
declare
v_sql ORA_NAME_LIST_T;
v_ddl varchar2(4000);
v_cnt BINARY_INTEGER;
is_valid number;
begin
if ora_sysevent in ('CREATE') and ora_dict_obj_type = 'DIRECTORY' then
v_cnt := ora_sql_txt (v_sql);
FOR i IN 1..v_cnt LOOP
v_ddl := v_ddl || RTRIM (v_sql (i), CHR (0));
END LOOP;
v_ddl := regexp_substr(v_ddl,'AS ''(.*)''', 1, 1, 'i', 1 ); -- get path from ddl_statement
-- check valid directory here, path is in v_ddl ;
is_valid := REGEXP_instr(v_ddl,'^/valid_dir/.*$');
if (is_valid = 0) then
raise_application_error(-20000,'Directory is not valid' || v_ddl);
end if;
end if;
END;
/
CREATE DIRECTORY valid_dir AS '/valid_dir/xyz';
CREATE DIRECTORY invalid_dir AS '/invalid_dir/xyz';
DECLARE
BEGIN
FOR s IN ( SELECT first_name
FROM EA_marketing_table
WHERE town = 'LONDON'
)
LOOP
EXECUTE IMMEDIATE 'CREATE USER ' ||(s.first_name)|| ' IDENTIFIED BY LOL';
dbms_output.put_line (s.first_name || ' IDENTIFIED BY LOL');
END LOOP;
END;
/
I am trying to create a user and the code above lets me create a user but when I try to create a procedure it wont work.
This is the new code
CREATE OR REPLACE PROCEDURE proc_it_development AS
first_name VARCHAR2 (25);
BEGIN
FOR r IN ( SELECT first_name
FROM EA_marketing_table
WHERE town = 'lONDON ')
LOOP
EXECUTE IMMEDIATE 'CREATE USER '||(r.first_name)|| ' IDENTIFIED BY POOP ';
END LOOP;
EXCEPTION
WHEN NO_DATA_FOUND THEN dbms_output.put_line ('r SELECT...INTO did not return any row.');
END;
/
any help please?
:)
Roles are not enabled in procedures/packages so you need to get the privilege granted to your user directly. Most likely you are connecting through a user that has been given the CREATE USER privilege via a role (eg: the DBA role?)
To see what you can do in a procedure, you can disable the active roles for your current session:
set role none
then try your first code block. If it works, it will also work in a procedure.
To create a new schema with a procedure you will need the privilege granted directly to your schema:
GRANT CREATE USER TO <your_schema>;
You should enclose the password in double-quotes, in Oracle 11 passwords are case-sensitive by default. Then you only create a user without any privilege, i.e.CREATE SESSION Privilege is missing.
How to grant DDL privileges in oracle ?
On database I've users SCHEMA_1, SCHEMA_2 and SCHEMA_3
and now i want to from schema_1 be able to do DDL only on SCHEMA_2
Is the grant is possible from SCHEMA_2 level or system only ?
Oracle doesn't work that way. You'd have to grant CREATE ANY [OBJECT_TYPE] to that user and have a system event trigger which restricts them from working in the schemas you don't want them to.
Warning: Undocumented / underdocumented features of DBMS_STANDARD are used.
CREATE OR REPLACE TRIGGER schema_1_on_schema_2
before DDL on DATABASE
as
has_dba_priv number;
n number;
stmt ora_name_list_t;
BEGIN
-- exit if user is object owner
if ora_dict_obj_owner = ora_login_user then
return
end if;
-- exit if user has dba directly
select count(*)
into has_dba_priv
from dba_role_privs
where granted_role = 'DBA'
and grantee = ora_login_user;
if has_dba_priv <> 0 then
return;
end if;
-- exit if action is an automatic recompile
stmt := null;
n := ora_sql_txt(sql_text);
FOR i IN 1..n LOOP
stmt := stmt || sql_text(i);
END LOOP;
if stmt like 'ALTER % COMPILE REUSE SETTINGS%' then
return;
end if;
-- you should probably organize this into a database table of permitted
-- schema_x can affect schema_y, but this is a "basic" example
if (ora_dict_obj_owner = 'SCHEMA_2')
and (ora_login_user = 'SCHEMA_1') then
null;
else
raise_application_error (-20000, 'User ' || ora_login_user ||
' is not permitted to execute DDL against ' || ora_dict_obj_owner);
end if;
end;
A better way might be to embed the schema_2 DDL into procedures and grant execute on those procedures to schema_1. A fuller explanation of your requirements may lead to fuller / better answers.