Trigger Oracle - Cant capture the userinformation who change the table - sql

I was trying to capture the user who fires [dml-operation] in the table_name from any schema. While trying to do so when I wrote the following code its captures the wrong osuser.
Code
create ore replace trigger trigger_name
after insert on table_name
for each row
declare
v_username varchar2(20);
v_osuser varchar2(20);
begin
select distinct osuser, username into v_osuser, v_username from v$session where osuser in ( select sys_context('USERENV', 'os_user') from dual;
insert into audit_table values (v_osuser, v_username);
end;
/
How can I modify this code so that I can address/solve this issue?
Note:
I am using the trigger in one server, and calling from the other server. Is there any way we can store the user information of the calling server. currently, it is returning the user information from the trigger defined server.
Thank You.

Try using this code:
select osuser, username
from v$session
where sid=(select sid from v$mystat where rownum=1);

use sys_context('userenv','CURRENT_SCHEMA') and sys_context('userenv','OS_USER')
to get the schema name/os user. No need to do any "select into" or declare any local variables
CREATE OR REPLACE TRIGGER trigger_name AFTER
INSERT ON table_name
FOR EACH ROW
BEGIN
INSERT INTO audit_table VALUES (
sys_context(
'userenv','OS_USER'
),
sys_context(
'userenv','CURRENT_SCHEMA'
)
);
END trigger_name;
/
Might be worth reading the docs on sys_context

When an operation is executed over a db link, the "user" is the user defined in the db link, not the user that is invoking the db link. For instance, database MYDB
CREATE PUBLIC DATABASE LINK "MYLINK"
CONNECT TO "SCOTT" IDENTIFIED BY "TIGER"
USING 'YOURDB';
Now, when user FRED executes the following:
select empno from emp#mylink;
Then, in database 'YOURDB', the operation is being executed by YOURDB's user SCOTT, not by MYDB's user FRED.

Related

Trigger for the nexval in a sequence returing ORA-01403

I have a page in Apex where the user can connect a row from a table to another (a mail to the address) because we changed how we store addresses so we are connecting the two tables while working on them.
The link between the tables is a row in a third table that has 5 columns (ID||ID_RACC||ID_INDIRIZZO||DATA_INS||USER_INS), where ID is the main ID of the link-table, ID_RACC is the ID of the mail's table and ID_INDIRIZZO is the ID of the addresses' table.
If I try from the shell to run this query:
INSERT INTO INSERT INTO INDIRIZZI_RACCOMANDATE (ID_RACC, ID_INDIRIZZO, USER_INS, DATA_INS)
VALUES (p_id_racc, p_id_indirizzo, v('USER'), SYSDATE);
with p_id_racc, p_id_indirizzo non-empty variables, I don't have any problem.
But if the user select from a Select List the address and click the Save button from a specific page he/she receives
ORA-01403: no data found
the only code that is run from him/her is the above one.
I searched and I found out that the problem could be the trigger that fills the ID column in the table INDIRIZZI_RACCOMANDATE from a sequence.
The trigger code is:
create or replace trigger "BI_INDIRIZZI_RACCOMANDATE"
BEFORE
insert on "INDIRIZZI_RACCOMANDATE"
for each row
begin
if :NEW."ID" is null then
select "INDIRIZZI_RACCOMANDATE_SEQ".nextval into :NEW."ID" from sys.dual;
end if;
end;​
I can't understand how is it possible to have a no_data_found with only a select nextval from a sequence.
Then I can't understand how is it possible that I have this problem only if I run it from that page and don't have it if I run the exact same code from shell.
But if the user select from a Select List the address and click the Save button from a specific page he/she receives ORA-01403: no data found
While in Apex, use :APP_USER:
INSERT INTO INSERT INTO INDIRIZZI_RACCOMANDATE
(ID_RACC, ID_INDIRIZZO, USER_INS, DATA_INS)
VALUES
(p_id_racc, p_id_indirizzo, :APP_USER, SYSDATE);
^^^^^^^^^
this
By the way, v('USER') is suspicious; should have been v('APP_USER'), I presume.
V (an absolutely abhorrent object name Oracle should know better) is defined as a collection indexed by varchar2. Your problem is that 'USER' is not a valid index value for V. When a collection does not contain the referenced index value on a collection Oracle throws NO DATA FOUND. A bad choice but the one they made. That is how you get that error. use v('APP_USER'); See fiddle and below.
declare
type example_att
is table of varchar2(30)
index by varchar2(8);
example_data example_att;
begin
dbms_output.enable;
example_data('A') := 'Abcd';
example_data('E') := 'Efgh';
example_data('I') := 'Ijlk';
dbms_output.put_line( 'value for ''A'' is ' );
dbms_output.put_line( example_data('A') );
-- OOPS
dbms_output.put_line( 'value for ''L'' is ' );
dbms_output.put_line( example_data('L'));
end;
/
I had the same error message for a procedure I would call in a dynamic action. The Procedure takes two arguments where one is the APP_USER.
BOOKMARK_FAVORITE(v('CARD_ID'), v('APP_USER'));
In my Procedure I would then select the User_ID for this specific APP_USER. And here is where the error was. The username I was selecting was not in Uppercase but the APP_USER was (IN_USERNAME). So I had to use the UPPER function:
select au.user_id into IN_USER_ID
from app_user au
where UPPER(au.username) = IN_USERNAME; --IN_USERNAME = APP_USER
^^^^^
Apparantly once the user log's in, the username will be stored in APP_USER in UPPERCASE, irrespective of how user has entered in log-in screen.
Don't know if this will help in your situation though :/

How do I find the audsid, sql_id of the sql statement which has invoked a trigger, within that trigger

Some records are getting removed from a table and we want to identify sql statements which are removing records from a table, so that we can check find the program which is causing the problem.
I have written the following but the sql
create or replace
trigger find_del_abc
before delete on abc
for each row
declare
temp_audsid integer;
temp_sql_id VARCHAR2(13);
temp_prev_sql_id VARCHAR2(13);
begin
If deleting then
select sql_id, prev_sql_id, audsid into temp_sql_id, temp_prev_sql_id, temp_audsid from v$session where audsid = SYS_CONTEXT('USERENV','sessionid');
insert into delete_abc_session
select * from v$session where audsid = temp_audsid;
insert into my_sql
select sql_id, sql_fulltext from v$sqlarea where sql_id in (temp_sql_id, temp_prev_sql_id);
End If;
end;
But I don't see the 'delete from abc' sql in my_sql.
Am I doing something wrong?
Is there any other way to capture the sql_id, prev_sql_id, audsid of the sql statement which invoked the trigger (inside the trigger block).
Thanks in advance for any help in this regard.
Is there any other way to capture the sql_id, prev_sql_id, audsid of the sql statement which invoked the trigger (inside the trigger block).
Not easily.
Well, AUDSID is easy: use the expression SYS_CONTEXT('USERENV','SESSIONID')
Getting the SQL_ID, as far as I know, is impossible, but it is possible, though difficult, to get the SQL text.
To do so, you would need to create a Fine-Grained Auditing (FGA) policy on your table. Inside an FGA policy handler, you have access to SYS_CONTEXT('USERENV','CURRENT_SQL'). If your policy handler saved that off somewhere, your trigger could have access to it.
Unfortunately, your trigger needs to be an AFTER trigger, because a BEFORE trigger will execute before the FGA policy.
Here is a quick example of the idea:
Create a test table
--DROP TABLE matt1;
CREATE TABLE matt1 ( a number );
Create a fine-grained auditing policy handler to save the last SQL
CREATE OR REPLACE PACKAGE xxcust_record_last_sql_pkg AS
-- TODO: you probably would want to store a collection of last SQL by table name
l_last_sql VARCHAR2(32000);
PROCEDURE record_last_sql (object_schema VARCHAR2, object_name VARCHAR2, policy_name VARCHAR2);
FUNCTION get_last_sql RETURN VARCHAR2;
END xxcust_record_last_sql_pkg;
/
CREATE OR REPLACE PACKAGE BODY xxcust_record_last_sql_pkg AS
PROCEDURE record_last_sql (object_schema VARCHAR2, object_name VARCHAR2, policy_name VARCHAR2) IS
BEGIN
xxcust_record_last_sql_pkg.l_last_sql := SUBSTR(SYS_CONTEXT ('userenv', 'CURRENT_SQL'),1,32000);
-- raise_application_error(-20001,'SQL = ' || xxcust_record_last_sql_pkg.l_last_sql);
END record_last_sql;
FUNCTION get_last_sql RETURN VARCHAR2 IS
BEGIN
RETURN xxcust_record_last_sql_pkg.l_last_sql;
END get_last_sql;
END xxcust_record_last_sql_pkg;
/
Register the FGA policy so that the handler is invoked on any DML of our table
--EXEC DBMS_FGA.drop_policy (user, 'MATT1', 'MATT_TEST_POLICY');
BEGIN
DBMS_FGA.add_policy (
object_schema => user,
object_name => 'MATT1',
policy_name => 'MATT_TEST_POLICY',
audit_condition => '1=1',
audit_column => null,
handler_schema => user,
handler_module => 'XXCUST_RECORD_LAST_SQL_PKG.RECORD_LAST_SQL',
statement_Types => 'INSERT,UPDATE,DELETE',
enable => TRUE);
END;
/
Create an AFTER INSERT trigger on our table to test the concept
--drop trigger matt1_ari1;
create or replace trigger matt1_ari1 after insert on matt1 for each row
begin
raise_application_error(-20001, 'Invoking SQL was: ' || substr(xxcust_record_last_sql_pkg.get_last_sql,1,4000));
end;
/
Test it all out
insert into matt1 (a) select 7*rownum from dual connect by rownum <= 5;
Error starting at line : 54 in command - insert into matt1 (a) select
7*rownum from dual connect by rownum <= 5 Error report - ORA-20001:
Invoking SQL was: insert into matt1 (a) select 7*rownum from dual
connect by rownum <= 5 ORA-06512: at "APPS.MATT1_ARI1", line 4
ORA-04088: error during execution of trigger 'APPS.MATT1_ARI1'
Caveat
I am assuming that you have a legitimate reason to want to do this. One reason this is hard is that there aren't common use cases for this. There is auditing and security to control access to tables. In fact, I would bet that proper use of the fine-grained auditing feature by itself (i.e., no custom trigger on the table) would be a better way to do whatever you are trying to do.

ORACLE PLSQL Create user with trigger identified by :new username and password

I'm writing an online videogame Database in SQL using ORACLE for an accademic project, and i'm trying to create a trigger that for every user that submit their information in my ACCCOUNT TABLE
CREATE TABLE ACCOUNT (
USERNAME VARCHAR(20),
PASSWORD VARCHAR(20) NOT NULL,
NATIONALITY VARCHAR(15),
CREATION DATE DATE,
EMAIL_ACCOUNT VARCHAR(35) NOT NULL,
CONSTRAINT KEYACCOUNT PRIMARY KEY(USERNAME),
CONSTRAINT NO_USER_CSPEC CHECK(REGEXP_LIKE(USERNAME, '^[a-zA-Z0-9._]+$') AND USERNAME NOT LIKE '% %'),
CONSTRAINT NO_EASY_PASS CHECK(REGEXP_LIKE(PASSWORD, '^[a-zA-Z0-9._!#£$%&/()=?]') AND PASSWORD NOT LIKE '% %'),
CONSTRAINT LENGHTUSER CHECK(LENGTH(USERNAME)>3),
CONSTRAINT LENGHTPASS CHECK(LENGTH(PASSWORD)>5),
CONSTRAINT FK_EMAIL FOREIGN KEY(EMAIL_ACCOUNT) REFERENCES PERSONA(EMAIL) ON DELETE CASCADE
);
Will fire a trigger that will create a new user with the new username and password just inserted.
this is the code i tried to wrote
CREATE OR REPLACE TRIGGER NEW_USER
AFTER INSERT ON ACCOUNT
FOR EACH ROW
BEGIN
CREATE USER :NEW.USERNAME IDENTIFIED BY :NEW.PASSWORD;
GRANT ROLE_NAME TO :NEW.USERNAME
END;
Why i'm tyring to do this ?
Basically because i'd like to give specific view on specific row that regards only the specific user. ( imagine if, while managing your account you can access to every other row stored in the table ACCOUNT )
After creating that specific user i can create some procedure that have in input the username ( of a successfully created user ) and give back the view on that specific row.
is there a way to do this ?
At first, you can't use DDL statement in trigger body as a open source, you should put it in execute immediate command. And also you should pay attention to user privileges which will execute then trigger, and role which will be granted to user, are there all priveleges granted, for create session, execute statements and so on. But if I were you I'll put user opening process in separate procedure, I think it won't be so simple code, so it will be easy to edit package procedure.
You can create context for you user sessions, wrap all your table where you want to control access into views and then filter view by user context.
For example you table TAB_A with many rows, in table you store column ACS_USER and wrap table to V_TAB_A , when you can control access to table via view, all user access object will use views like
select * from V_TAB_A where ACSUSER = SYS_CONTEXT('USERENV','SESSION_USER')
The main problem I see here is grant to create user. You probably don't want your schema to be able to create users. So trigger (of course as other answers states this need to be execute immediate) shouldn't directly call create user. I would create procedure that create user in other schema than your working schema. That external schema would have grants to create user and your schema would have only grant to execute that one procedure from strong priviledged schema. In that case trigger will only call single procedure from external schema.
So to recap:
CREATE OR REPLACE TRIGGER your_schema.NEW_USER
AFTER INSERT ON ACCOUNT
FOR EACH ROW
BEGIN
STRONG.CREATE_USER(:NEW.PASSWORD,:NEW.USERNAME);
END;
CREATE OR REPLACE PROCEDURE STRONG.CREATE_USER(PASS VARCHAR2, USERNAME VARCHAR2) AS
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
execute immediate 'CREATE USER ' || USERNAME || ' IDENTIFIED BY ' || PASS;
execute immediate 'GRANT ROLE_NAME, CONNECT, RESOURCE TO ' || USERNAME; --and whatever user needs more
END;
Where STRONG user have rights to create user and your_schema has grant to execute STRONG.CREATE_USER
Additional thing. Never store passwords in plain text. Use some hash.
Alternatively, you can use a database stored procedure instead of a trigger to do the DDL operations.
This is a pseudo code, make necessary changes as per your requirement. You can build your logic on top of this and if you are stuck, always post a question here in SO.
Table
CREATE TABLE account_info
(
user_name VARCHAR (20),
user_password VARCHAR (20) NOT NULL
);
Procedure
CREATE OR REPLACE PROCEDURE test_procedure (
user_name IN account_info.user_name%TYPE,
user_password IN account_info.user_password%TYPE)
AS
BEGIN
INSERT INTO account_info (user_name, user_password)
VALUES ('ABC', 'password123');
-- check the user exists or not, if yes proceed
EXECUTE IMMEDIATE
'CREATE USER ' || user_name || ' IDENTIFIED BY ' || user_password;
-- do the rest of the activities such as grant roles, privilges etc.
END;
/

pass session user value to procedure

I have created a procedure in user A to get the list of tables belonging to session user (who ever runs the procedure) and insert into a table. but when I execute the procedure from user B. It is not getting the values, I mean the session_user value is not being passed to procedure ?? Ienter code heres this an expected behavior ? Is there a way I can pass the session user without actually passing it while execution of procedure(it needs to it by itself) ?
Table :
CREATE TABLE "ABCD"
( "USERNAME" VARCHAR2(1`enter code here`00 BYTE),
"TABLE_NAME" VARCHAR2(100 BYTE)) ;
Procedure I am trying to run :
CREATE OR REPLACE PROCEDURE "SESS_USR_OBJECTS" AS
V_USER varchar2(50):= SYS_CONTEXT('USERENV','SESSION_USER');
cursor SEL_CUR is select OWNER,TABLE_NAME from all_tables where owner in (V_USER);
SEL_REC SEL_CUR%ROWTYPE;
VSQL1 VARCHAR2(100);
BEGIN
OPEN SEL_CUR;
LOOP
FETCH SEL_CUR INTO SEL_REC;
EXIT WHEN SEL_CUR%NOTFOUND;
VSQL1:= 'insert into ABCD VALUES ('''||SEL_REC.OWNER||''','''||SEL_REC.TABLE_NAME||''')';
Dbms_Output.Put_Line(VSQL1);
EXECUTE IMMEDIATE VSQL1;
COMMIT;
END LOOP;
CLOSE SEL_CUR;
END;
When I runt the procedure nothing is being inserted into table.
Yes, user B has access to run the procedure in A.
You are selecting from ALL_TABLES, which is a view that is filtered by SCHEMAID and other (internal) representations of the current user, not SESSION_USER. So, when you execute the procedure, you are selecting from a list of tables that user "A" can see but that are owned by user "B". Apparently, that list is empty in your database.
To make this work the way you want, you need to use invoker's rights on that procedure, like this:
CREATE OR REPLACE PROCEDURE "SESS_USR_OBJECTS" AUTHID CURRENT_USER AS...
That way, when it runs, it will select from user "B"'s version of ALL_TABLES.

Oracle Trigger on Schema not being triggered by other users besides the Schema owner

I have the following table and trigger.
CREATE TABLE LogSchema (
user varchar2(100),
date_ date,
operation varchar2(100),
obj_type varchar2(100),
obj_name varchar2(100)
);
CREATE OR REPLACE TRIGGER LogSchema
AFTER DDL ON USER1.SCHEMA
/* can't use ON DATABASE or else it will log
everything that happens on all schemas*/
DECLARE
BEGIN
INSERT INTO LogSchema VALUES
(USER
, SYSDATE
, sys.sysevent
, sys.dictionary_obj_type
, sys.dictionary_obj_name);
END LogSchema;
I want to log every DDL action (create/drop table/procedure etc) that happens on USER1 schema. This trigger is only being executed if USER1 makes a DDL action, if USER2 makes a DDL action on USER1 schema like:
CREATE TABLE USER1.TEST (
test varchar2(100)
);
it doesn't get logged. Anyone knows what is wrong with my trigger?
The "ON user.schema" clause doesn't work quite the way you think. From the 10gR2 SQL Reference:
"The trigger fires whenever any user connected as schema initiates the triggering event."
(emphasis mine)
So, the trigger only logs DDL issue when connected AS USER1, not when DDL is performed on the schema USER1.
Why do you 'build your own' and not use standard database functionality like 'Auditing'?
You will need to create a database trigger, and check inside if the changed object lies on USER1 schema:
CREATE OR REPLACE TRIGGER LogSchemaUSER1
AFTER DDL ON DATABASE
DECLARE
BEGIN
IF ora_dict_obj_owner = 'USER1' THEN
INSERT INTO USER1.LogSchema VALUES
( USER
, SYSDATE
, ora_sysevent
, ora_dict_obj_type
, ora_dict_obj_name);
END IF;
END LogSchemaUSER1;
This way only SYS operations on USER1 schema will not be logged.