From 92ac148b82487d4661ed7d4f5d70236e21f25e17 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Fri, 30 Jul 2021 08:52:01 -0700
Subject: [PATCH v5 07/12] Condition event triggers on owning role

Make event triggers fire only when the trigger owner is a direct or
indirect member of the role which performed the event.  For
superuser owners, nothing changes, but by assigning event trigger
ownership to a non-superuser, this provides a way of filtering when
a trigger fires.
---
 doc/src/sgml/event-trigger.sgml             |  7 +++++++
 doc/src/sgml/ref/create_event_trigger.sgml  | 11 ++++++-----
 src/backend/commands/event_trigger.c        |  9 +++++++--
 src/backend/utils/cache/evtcache.c          |  1 +
 src/include/utils/evtcache.h                |  1 +
 src/test/regress/expected/event_trigger.out | 17 +++++++++++++++++
 src/test/regress/sql/event_trigger.sql      | 21 +++++++++++++++++++++
 7 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 60366a950e..f003bedc29 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -119,6 +119,13 @@
      to intercept. A common use of such triggers is to restrict the range of
      DDL operations which users may perform.
    </para>
+
+   <para>
+    A trigger will only fire if the trigger owner is a direct or indirect
+    member of the role which performed the event.  (Note that
+    <productname>PostgreSQL</productname> prior to version 15.0 did not
+    condition trigger firing on the permissions of the trigger owner.)
+   </para>
   </sect1>
 
   <sect1 id="event-trigger-matrix">
diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml
index becd31bcad..ece05a5992 100644
--- a/doc/src/sgml/ref/create_event_trigger.sgml
+++ b/doc/src/sgml/ref/create_event_trigger.sgml
@@ -33,11 +33,12 @@ CREATE EVENT TRIGGER <replaceable class="parameter">name</replaceable>
 
   <para>
    <command>CREATE EVENT TRIGGER</command> creates a new event trigger.
-   Whenever the designated event occurs and the <literal>WHEN</literal> condition
-   associated with the trigger, if any, is satisfied, the trigger function
-   will be executed.  For a general introduction to event triggers, see
-   <xref linkend="event-triggers"/>.  The user who creates an event trigger
-   becomes its owner.
+   Whenever the designated event occurs, the trigger function will be executed
+   if the <literal>WHEN</literal> condition associated with the trigger, if
+   any, is satisfied, and if the owner of the trigger is a direct or indirect
+   member of the role which performed the event.  For a general introduction to
+   event triggers, see <xref linkend="event-triggers"/>.  The user who creates
+   an event trigger becomes its owner.
   </para>
  </refsect1>
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 93e211cecf..13db569775 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -612,8 +612,13 @@ EventTriggerCommonSetup(Node *parsetree,
 
 		if (filter_event_trigger(tag, item))
 		{
-			/* We must plan to fire this trigger. */
-			runlist = lappend_oid(runlist, item->fnoid);
+			/*
+			 * We must plan to fire this trigger only if the event trigger
+			 * owner is a member of the current role, else the trigger owner
+			 * can execute with privilege it could not do directly.
+			 */
+			if (is_member_of_role(item->fnowner, GetUserId()))
+				runlist = lappend_oid(runlist, item->fnoid);
 		}
 	}
 
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 460b720a65..ab86fcfa31 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -173,6 +173,7 @@ BuildEventTriggerCache(void)
 		/* Allocate new cache item. */
 		item = palloc0(sizeof(EventTriggerCacheItem));
 		item->fnoid = form->evtfoid;
+		item->fnowner = form->evtowner;
 		item->enabled = form->evtenabled;
 
 		/* Decode and sort tags array. */
diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h
index 58ddb71cb1..1e968129c6 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -28,6 +28,7 @@ typedef enum
 typedef struct
 {
 	Oid			fnoid;			/* function to be called */
+	Oid			fnowner;		/* event trigger owner */
 	char		enabled;		/* as SESSION_REPLICATION_ROLE_* */
 	Bitmapset  *tagset;			/* command tags, or NULL if empty */
 } EventTriggerCacheItem;
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index d56c75de84..45215a622a 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -29,6 +29,23 @@ ERROR:  unrecognized event name "elephant_bootstrap"
 -- OK
 create event trigger regress_event_trigger on ddl_command_start
    execute procedure test_event_trigger();
+create role test_evt_role1 nosuperuser;
+create role test_evt_role2 nosuperuser;
+alter event trigger regress_event_trigger owner to test_evt_role1;
+set session authorization test_evt_role1;
+-- OK, should fire test_event_trigger
+create table test_evt_table1 (col text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+reset session authorization;
+set session authorization test_evt_role2;
+-- OK, should bypass test_event_trigger
+create table test_evt_table2 (col text);
+grant test_evt_role2 to test_evt_role1;
+-- Ok, should fire test_event_trigger now
+create table test_evt_table3 (col text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+reset session authorization;
+alter event trigger regress_event_trigger owner to current_user;
 -- OK
 create event trigger regress_event_trigger_end on ddl_command_end
    execute function test_event_trigger();
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index ef79b1059d..839dfd6db9 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -29,6 +29,27 @@ create event trigger regress_event_trigger on elephant_bootstrap
 create event trigger regress_event_trigger on ddl_command_start
    execute procedure test_event_trigger();
 
+create role test_evt_role1 nosuperuser;
+create role test_evt_role2 nosuperuser;
+
+alter event trigger regress_event_trigger owner to test_evt_role1;
+
+set session authorization test_evt_role1;
+-- OK, should fire test_event_trigger
+create table test_evt_table1 (col text);
+reset session authorization;
+
+set session authorization test_evt_role2;
+-- OK, should bypass test_event_trigger
+create table test_evt_table2 (col text);
+
+grant test_evt_role2 to test_evt_role1;
+-- Ok, should fire test_event_trigger now
+create table test_evt_table3 (col text);
+
+reset session authorization;
+alter event trigger regress_event_trigger owner to current_user;
+
 -- OK
 create event trigger regress_event_trigger_end on ddl_command_end
    execute function test_event_trigger();
-- 
2.21.1 (Apple Git-122.3)

