aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/trigger.sgml4
-rw-r--r--src/backend/commands/trigger.c20
-rw-r--r--src/test/regress/expected/triggers.out67
-rw-r--r--src/test/regress/sql/triggers.sql63
4 files changed, 154 insertions, 0 deletions
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index a9abaab9056..c3c0faf7a1b 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -129,6 +129,10 @@
In all cases, a trigger is executed as part of the same transaction as
the statement that triggered it, so if either the statement or the
trigger causes an error, the effects of both will be rolled back.
+ Also, the trigger will always run in the security context of the role
+ that executed the statement that caused the trigger to fire, unless
+ the trigger function is defined as <literal>SECURITY DEFINER</literal>,
+ in which case it will run as the function owner.
</para>
<para>
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 711c09a1030..7a5ffe32f60 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3635,6 +3635,7 @@ typedef struct AfterTriggerSharedData
TriggerEvent ats_event; /* event type indicator, see trigger.h */
Oid ats_tgoid; /* the trigger's ID */
Oid ats_relid; /* the relation it's on */
+ Oid ats_rolid; /* role to execute the trigger */
CommandId ats_firing_id; /* ID for firing cycle */
struct AfterTriggersTableData *ats_table; /* transition table access */
Bitmapset *ats_modifiedcols; /* modified columns */
@@ -4117,6 +4118,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events,
newshared->ats_firing_id == 0 &&
newshared->ats_table == evtshared->ats_table &&
newshared->ats_relid == evtshared->ats_relid &&
+ newshared->ats_rolid == evtshared->ats_rolid &&
bms_equal(newshared->ats_modifiedcols,
evtshared->ats_modifiedcols))
break;
@@ -4289,6 +4291,8 @@ AfterTriggerExecute(EState *estate,
AfterTriggerShared evtshared = GetTriggerSharedData(event);
Oid tgoid = evtshared->ats_tgoid;
TriggerData LocTriggerData = {0};
+ Oid save_rolid;
+ int save_sec_context;
HeapTuple rettuple;
int tgindx;
bool should_free_trig = false;
@@ -4493,6 +4497,17 @@ AfterTriggerExecute(EState *estate,
MemoryContextReset(per_tuple_context);
/*
+ * If necessary, become the role that was active when the trigger got
+ * queued. Note that the role might have been dropped since the trigger
+ * was queued, but if that is a problem, we will get an error later.
+ * Checking here would still leave a race condition.
+ */
+ GetUserIdAndSecContext(&save_rolid, &save_sec_context);
+ if (save_rolid != evtshared->ats_rolid)
+ SetUserIdAndSecContext(evtshared->ats_rolid,
+ save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+ /*
* Call the trigger and throw away any possibly returned updated tuple.
* (Don't let ExecCallTriggerFunc measure EXPLAIN time.)
*/
@@ -4506,6 +4521,10 @@ AfterTriggerExecute(EState *estate,
rettuple != LocTriggerData.tg_newtuple)
heap_freetuple(rettuple);
+ /* Restore the current role if necessary */
+ if (save_rolid != evtshared->ats_rolid)
+ SetUserIdAndSecContext(save_rolid, save_sec_context);
+
/*
* Release resources
*/
@@ -6431,6 +6450,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
new_shared.ats_tgoid = trigger->tgoid;
new_shared.ats_relid = RelationGetRelid(rel);
+ new_shared.ats_rolid = GetUserId();
new_shared.ats_firing_id = 0;
if ((trigger->tgoldtable || trigger->tgnewtable) &&
transition_capture != NULL)
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 0657da17577..e38304bee50 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3748,3 +3748,70 @@ Inherits: parent
drop table parent, child;
drop function f();
+-- Test who runs deferred trigger functions
+-- setup
+create role regress_groot;
+create role regress_outis;
+create function whoami() returns trigger language plpgsql
+as $$
+begin
+ raise notice 'I am %', current_user;
+ return null;
+end;
+$$;
+alter function whoami() owner to regress_outis;
+create table defer_trig (id integer);
+grant insert on defer_trig to public;
+create constraint trigger whoami after insert on defer_trig
+ deferrable initially deferred
+ for each row
+ execute function whoami();
+-- deferred triggers must run as the user that queued the trigger
+begin;
+set role regress_groot;
+insert into defer_trig values (1);
+reset role;
+set role regress_outis;
+insert into defer_trig values (2);
+reset role;
+commit;
+NOTICE: I am regress_groot
+NOTICE: I am regress_outis
+-- security definer functions override the user who queued the trigger
+alter function whoami() security definer;
+begin;
+set role regress_groot;
+insert into defer_trig values (3);
+reset role;
+commit;
+NOTICE: I am regress_outis
+alter function whoami() security invoker;
+-- make sure the current user is restored after error
+create or replace function whoami() returns trigger language plpgsql
+as $$
+begin
+ raise notice 'I am %', current_user;
+ perform 1 / 0;
+ return null;
+end;
+$$;
+begin;
+set role regress_groot;
+insert into defer_trig values (4);
+reset role;
+commit; -- error expected
+NOTICE: I am regress_groot
+ERROR: division by zero
+CONTEXT: SQL statement "SELECT 1 / 0"
+PL/pgSQL function whoami() line 4 at PERFORM
+select current_user = session_user;
+ ?column?
+----------
+ t
+(1 row)
+
+-- clean up
+drop table defer_trig;
+drop function whoami();
+drop role regress_outis;
+drop role regress_groot;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 7e2f7597c10..b79fecb8fe6 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2839,3 +2839,66 @@ alter trigger parenttrig on parent rename to anothertrig;
drop table parent, child;
drop function f();
+
+-- Test who runs deferred trigger functions
+
+-- setup
+create role regress_groot;
+create role regress_outis;
+create function whoami() returns trigger language plpgsql
+as $$
+begin
+ raise notice 'I am %', current_user;
+ return null;
+end;
+$$;
+alter function whoami() owner to regress_outis;
+
+create table defer_trig (id integer);
+grant insert on defer_trig to public;
+create constraint trigger whoami after insert on defer_trig
+ deferrable initially deferred
+ for each row
+ execute function whoami();
+
+-- deferred triggers must run as the user that queued the trigger
+begin;
+set role regress_groot;
+insert into defer_trig values (1);
+reset role;
+set role regress_outis;
+insert into defer_trig values (2);
+reset role;
+commit;
+
+-- security definer functions override the user who queued the trigger
+alter function whoami() security definer;
+begin;
+set role regress_groot;
+insert into defer_trig values (3);
+reset role;
+commit;
+alter function whoami() security invoker;
+
+-- make sure the current user is restored after error
+create or replace function whoami() returns trigger language plpgsql
+as $$
+begin
+ raise notice 'I am %', current_user;
+ perform 1 / 0;
+ return null;
+end;
+$$;
+
+begin;
+set role regress_groot;
+insert into defer_trig values (4);
+reset role;
+commit; -- error expected
+select current_user = session_user;
+
+-- clean up
+drop table defer_trig;
+drop function whoami();
+drop role regress_outis;
+drop role regress_groot;