This is an automated email from the ASF dual-hosted git repository.

reshke pushed a commit to branch mdb_admin_2
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit a9dd2c5c8072ba48abdd8d1a29e5a5041beb6cd5
Author: reshke <reshke@double.cloud>
AuthorDate: Fri Sep 19 13:22:50 2025 +0000

    MDB admin patch & tests
    
    This patch introcudes new pseudo-pre-defined role "mdb_admin".
    
    Introduces 2 new function:
    extern bool mdb_admin_allow_bypass_owner_checks(Oid userId,  Oid ownerId);
    extern void check_mdb_admin_is_member_of_role(Oid member, Oid role);
    
    To check mdb admin belongship and role-to-role ownership transfer
    correctness.
    
    Our mdb_admin ACL model is the following:
    
    * Any roles user or/and roles can be granted with mdb_admin
    * mdb_admin memeber can tranfser ownershup of relations,
    namespaces and functions to other roles, if target role in neither:
    superuser, pg_read_server_files, pg_write_server_files nor
    pg_execute_server_program.
    
    This patch allows mdb admin to tranfers ownership on non-superuser objects
---
 src/backend/catalog/namespace.c                 |  20 ++++-
 src/backend/commands/alter.c                    |   8 +-
 src/backend/commands/functioncmds.c             |  20 +++--
 src/backend/commands/schemacmds.c               |  13 ++-
 src/backend/commands/tablecmds.c                |  12 +--
 src/backend/storage/ipc/signalfuncs.c           |  28 +++++-
 src/backend/utils/activity/backend_status.c     |  16 ++++
 src/backend/utils/adt/acl.c                     | 112 ++++++++++++++++++++++++
 src/backend/utils/misc/guc.c                    |   2 +-
 src/include/utils/acl.h                         |   7 ++
 src/include/utils/backend_status.h              |   3 +
 src/include/utils/guc_tables.h                  |   2 +
 src/test/Makefile                               |   3 +
 src/test/mdb_admin/.gitignore                   |   2 +
 src/test/mdb_admin/Makefile                     |  23 +++++
 src/test/mdb_admin/t/signals.pl                 |  74 ++++++++++++++++
 src/test/regress/expected/create_function_3.out |   4 +-
 src/test/regress/expected/mdb_admin.out         |  81 +++++++++++++++++
 src/test/regress/parallel_schedule              |   4 +
 src/test/regress/sql/mdb_admin.sql              |  77 ++++++++++++++++
 20 files changed, 486 insertions(+), 25 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index f367b00a675..be09847022b 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -2971,7 +2971,6 @@ LookupExplicitNamespace(const char *nspname, bool 
missing_ok)
 {
        Oid                     namespaceId;
        AclResult       aclresult;
-
        /* check for pg_temp alias */
        if (strcmp(nspname, "pg_temp") == 0)
        {
@@ -2989,7 +2988,24 @@ LookupExplicitNamespace(const char *nspname, bool 
missing_ok)
        if (missing_ok && !OidIsValid(namespaceId))
                return InvalidOid;
 
-       aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_USAGE);
+       HeapTuple tuple;
+       Oid ownerId;
+
+       tuple = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(namespaceId));
+       if (!HeapTupleIsValid(tuple))
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_SCHEMA),
+                                errmsg("schema with OID %u does not exist", 
namespaceId)));
+
+       ownerId = ((Form_pg_namespace) GETSTRUCT(tuple))->nspowner;
+
+       ReleaseSysCache(tuple);
+
+       if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), ownerId)) {
+               aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), 
ACL_USAGE);
+       } else {
+               aclresult = ACLCHECK_OK;
+       }
        if (aclresult != ACLCHECK_OK)
                aclcheck_error(aclresult, OBJECT_SCHEMA,
                                           nspname);
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index f5dfd6ff126..6f370a2c9aa 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -1085,7 +1085,8 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid 
new_ownerId)
                if (!superuser())
                {
                        /* must be owner */
-                       if (!has_privs_of_role(GetUserId(), old_ownerId))
+                       if (!has_privs_of_role(GetUserId(), old_ownerId) 
+                       && !mdb_admin_allow_bypass_owner_checks(GetUserId(), 
old_ownerId))
                        {
                                char       *objname;
                                char            namebuf[NAMEDATALEN];
@@ -1105,14 +1106,13 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, 
Oid new_ownerId)
                                aclcheck_error(ACLCHECK_NOT_OWNER, 
get_object_type(classId, objectId),
                                                           objname);
                        }
-                       /* Must be able to become new owner */
-                       check_is_member_of_role(GetUserId(), new_ownerId);
+
+                       check_mdb_admin_is_member_of_role(GetUserId(), 
new_ownerId);
 
                        /* New owner must have CREATE privilege on namespace */
                        if (OidIsValid(namespaceId))
                        {
                                AclResult       aclresult;
-
                                aclresult = pg_namespace_aclcheck(namespaceId, 
new_ownerId,
                                                                                
                  ACL_CREATE);
                                if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/commands/functioncmds.c 
b/src/backend/commands/functioncmds.c
index b99b2419fcc..8a570fa6965 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1525,9 +1525,13 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt 
*stmt)
         * by security barrier views or row-level security policies.
         */
        if (isLeakProof && !superuser())
-               ereport(ERROR,
-                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                errmsg("only superuser can define a leakproof 
function")));
+       {
+               Oid role = get_role_oid("mdb_admin", true);
+               if (!is_member_of_role(GetUserId(), role))
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("only superuser or mdb_admin can 
define a leakproof function")));
+       }
 
        if (transformDefElem)
        {
@@ -1852,9 +1856,13 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt 
*stmt)
        {
                procForm->proleakproof = intVal(leakproof_item->arg);
                if (procForm->proleakproof && !superuser())
-                       ereport(ERROR,
-                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                                        errmsg("only superuser can define a 
leakproof function")));
+               {
+                       Oid role = get_role_oid("mdb_admin", true);
+                       if (!is_member_of_role(GetUserId(), role))
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                               errmsg("only superuser or 
mdb_admin can define a leakproof function")));
+               }
        }
        if (cost_item)
        {
diff --git a/src/backend/commands/schemacmds.c 
b/src/backend/commands/schemacmds.c
index 96757eaa814..03f96bb6499 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -598,12 +598,12 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, 
Oid newOwnerId)
                AclResult       aclresult;
 
                /* Otherwise, must be owner of the existing object */
-               if (!pg_namespace_ownercheck(nspForm->oid, GetUserId()))
+               if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), 
nspForm->nspowner)
+                && !pg_namespace_ownercheck(nspForm->oid, GetUserId()))
                        aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
                                                   NameStr(nspForm->nspname));
 
-               /* Must be able to become new owner */
-               check_is_member_of_role(GetUserId(), newOwnerId);
+               check_mdb_admin_is_member_of_role(GetUserId(), newOwnerId);
 
                /*
                 * must have create-schema rights
@@ -614,8 +614,13 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid 
newOwnerId)
                 * schemas.  Because superusers will always have this right, we 
need
                 * no special case for them.
                 */
-               aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(),
+               if (mdb_admin_allow_bypass_owner_checks(GetUserId(), 
nspForm->nspowner)) {
+                       aclresult = ACLCHECK_OK;
+               } else {
+                       aclresult = pg_database_aclcheck(MyDatabaseId, 
GetUserId(),
                                                                                
 ACL_CREATE);
+               }
+
                if (aclresult != ACLCHECK_OK)
                        aclcheck_error(aclresult, OBJECT_DATABASE,
                                                   
get_database_name(MyDatabaseId));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e731e37142c..301c3548e5e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15708,13 +15708,14 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, 
bool recursing, LOCKMODE lock
                                AclResult       aclresult;
 
                                /* Otherwise, must be owner of the existing 
object */
-                               if (!pg_class_ownercheck(relationOid, 
GetUserId()))
+                               if 
(!mdb_admin_allow_bypass_owner_checks(GetUserId(), tuple_class->relowner) 
+                                               && 
!pg_class_ownercheck(relationOid, GetUserId()))
                                        aclcheck_error(ACLCHECK_NOT_OWNER, 
get_relkind_objtype(get_rel_relkind(relationOid)),
                                                                   
RelationGetRelationName(target_rel));
 
-                               /* Must be able to become new owner */
-                               check_is_member_of_role(GetUserId(), 
newOwnerId);
 
+                               check_mdb_admin_is_member_of_role(GetUserId(), 
newOwnerId);
+                               
                                /* New owner must have CREATE privilege on 
namespace */
                                aclresult = pg_namespace_aclcheck(namespaceOid, 
newOwnerId,
                                                                                
                  ACL_CREATE);
@@ -20795,7 +20796,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, 
Oid relid, Oid oldrelid,
        Form_pg_class classform;
        AclResult       aclresult;
        char            relkind;
-
+       
        tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(tuple))
                return;                                 /* concurrently dropped 
*/
@@ -20803,7 +20804,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, 
Oid relid, Oid oldrelid,
        relkind = classform->relkind;
 
        /* Must own relation. */
-       if (!pg_class_ownercheck(relid, GetUserId()))
+       if (!mdb_admin_allow_bypass_owner_checks(GetUserId(), 
classform->relowner) 
+                       && !pg_class_ownercheck(relid, GetUserId()))
                aclcheck_error(ACLCHECK_NOT_OWNER, 
get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 
        /* No system table modifications unless explicitly allowed. */
diff --git a/src/backend/storage/ipc/signalfuncs.c 
b/src/backend/storage/ipc/signalfuncs.c
index 0d5ccaa201d..753b94752d3 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -52,6 +52,7 @@ static int
 pg_signal_backend(int pid, int sig, char *msg)
 {
        PGPROC     *proc = BackendPidGetProc(pid);
+       LocalPgBackendStatus *local_beentry;
 
        /*
         * BackendPidGetProc returns NULL if the pid isn't valid; but by the 
time
@@ -72,9 +73,34 @@ pg_signal_backend(int pid, int sig, char *msg)
                return SIGNAL_BACKEND_ERROR;
        }
 
+       local_beentry = pgstat_fetch_stat_local_beentry_by_pid(pid);
+
        /* Only allow superusers to signal superuser-owned backends. */
        if (superuser_arg(proc->roleId) && !superuser())
-               return SIGNAL_BACKEND_NOSUPERUSER;
+       {
+               Oid role;
+               char * appname;
+
+               if (local_beentry == NULL) {
+                       return SIGNAL_BACKEND_NOSUPERUSER;
+               }
+
+               role = get_role_oid("mdb_admin", true /*if nodoby created 
mdb_admin role in this database*/);
+               appname = local_beentry->backendStatus.st_appname;
+
+               // only allow mdb_admin to kill su queries
+               if (!is_member_of_role(GetUserId(), role)) {
+                       return SIGNAL_BACKEND_NOSUPERUSER;
+               }
+
+               if (local_beentry->backendStatus.st_backendType == 
B_AUTOVAC_WORKER) {
+                       // ok
+               } else if (appname != NULL && strcmp(appname, "MDB") == 0) {
+                       // ok
+               } else {
+                       return SIGNAL_BACKEND_NOSUPERUSER;
+               }
+       }
 
        /* Users can signal backends they have role membership in. */
        if (!has_privs_of_role(GetUserId(), proc->roleId) &&
diff --git a/src/backend/utils/activity/backend_status.c 
b/src/backend/utils/activity/backend_status.c
index 9a0918bceff..217483c1c61 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -1102,6 +1102,22 @@ pgstat_fetch_stat_local_beentry(int beid)
        return &localBackendStatusTable[beid - 1];
 }
 
+/* -- mdb admin patch -- */
+LocalPgBackendStatus *
+pgstat_fetch_stat_local_beentry_by_pid(int pid)
+{
+       pgstat_read_current_status();
+
+       for (int i = 1; i <= localNumBackends; ++i) {
+               if (localBackendStatusTable[i - 1].backendStatus.st_procpid == 
pid) {
+                       return &localBackendStatusTable[i - 1];
+               }
+       }
+
+       return NULL;
+}
+
+/*  -- mdb admin patch end -- */
 
 /* ----------
  * pgstat_fetch_stat_numbackends() -
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 714a536e93d..fc566a575f4 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -5012,6 +5012,60 @@ has_privs_of_role(Oid member, Oid role)
 }
 
 
+// -- non-upstream patch begin
+/*
+ * Is userId allowed to bypass ownership check
+ * and tranfer onwership to ownerId role?
+ */
+bool
+mdb_admin_allow_bypass_owner_checks(Oid userId,  Oid ownerId)
+{
+       Oid mdb_admin_roleoid;
+       /* 
+       * Never allow nobody to grant objects to 
+       * superusers.
+       * This can result in various CVE.
+       * For paranoic reasons, check this even before
+       * membership of mdb_admin role.
+       */
+       if (superuser_arg(ownerId)) {
+               return false;
+       }
+
+       mdb_admin_roleoid = get_role_oid("mdb_admin", true /* superuser 
suggested to be mdb_admin*/);
+       /* Is userId actually member of mdb admin? */
+       if (!is_member_of_role(userId, mdb_admin_roleoid)) {
+               /* if no, disallow. */
+               return false;
+       }
+       
+       /* 
+       * Now, we need to check if ownerId 
+       * is some dangerous role to trasfer membership to.
+       *
+       * For now, we check that ownerId does not have
+       * priviledge to execute server program or/and
+       * read/write server files.
+       */
+
+       if (has_privs_of_role(ownerId, ROLE_PG_READ_SERVER_FILES)) {
+               return false;
+       }
+
+       if (has_privs_of_role(ownerId, ROLE_PG_WRITE_SERVER_FILES)) {
+               return false;
+       }
+
+       if (has_privs_of_role(ownerId, ROLE_PG_EXECUTE_SERVER_PROGRAM)) {
+               return false;
+       }
+
+       /* All checks passed, hope will not be hacked here (again) */
+       return true;
+}
+
+// -- non-upstream patch end
+
 /*
  * Is member a member of role (directly or indirectly)?
  *
@@ -5051,6 +5105,64 @@ check_is_member_of_role(Oid member, Oid role)
                                                GetUserNameFromId(role, 
false))));
 }
 
+// -- mdb admin patch 
+/*
+ * check_mdb_admin_is_member_of_role
+ *             is_member_of_role with a standard permission-violation error if 
not in usual case
+ * Is case `member` in mdb_admin we check that role is neither of superuser, 
pg_read/write 
+ * server files nor pg_execute_server_program
+ */
+void
+check_mdb_admin_is_member_of_role(Oid member, Oid role)
+{
+       Oid mdb_admin_roleoid;
+       /* fast path - if we are superuser, its ok */
+       if (superuser_arg(member)) {
+               return;
+       }
+
+       mdb_admin_roleoid = get_role_oid("mdb_admin", true /* superuser 
suggested to be mdb_admin*/);
+       /* Is userId actually member of mdb admin? */
+       if (is_member_of_role(member, mdb_admin_roleoid)) {
+               /* role is mdb admin */
+               if (superuser_arg(role)) {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("cannot transfer ownership to 
superuser \"%s\"",
+                                                       GetUserNameFromId(role, 
false))));
+               }
+
+               if (has_privs_of_role(role, ROLE_PG_READ_SERVER_FILES)) {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("cannot transfer ownership to 
pg_read_server_files role in Cloud")));
+               }
+
+               if (has_privs_of_role(role, ROLE_PG_WRITE_SERVER_FILES)) {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("cannot transfer ownership to 
pg_write_server_files role in Cloud")));
+               }
+
+               if (has_privs_of_role(role, ROLE_PG_EXECUTE_SERVER_PROGRAM)) {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("cannot transfer ownership to 
pg_execute_server_program role in Cloud")));
+               }
+       } else {
+               /* if no, check membership transfer in usual way. */
+               
+               if (!is_member_of_role(member, role)) {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                       errmsg("must be member of role \"%s\"",
+                                                       GetUserNameFromId(role, 
false))));
+               }
+       }
+}
+
+// -- mdb admin patch 
+
 /*
  * Is member a member of role, not considering superuserness?
  *
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d8cdf027df5..f73f99851b7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4918,7 +4918,7 @@ static struct config_enum ConfigureNamesEnum[] =
        {
                {"session_replication_role", PGC_SUSET, CLIENT_CONN_STATEMENT,
                        gettext_noop("Sets the session's behavior for triggers 
and rewrite rules."),
-                       NULL
+                       NULL, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, 0, true,
                },
                &SessionReplicationRole,
                SESSION_REPLICATION_ROLE_ORIGIN, 
session_replication_role_options,
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 223175099bd..271ac942f6f 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -210,6 +210,13 @@ extern bool has_privs_of_role(Oid member, Oid role);
 extern bool is_member_of_role(Oid member, Oid role);
 extern bool is_member_of_role_nosuper(Oid member, Oid role);
 extern bool is_admin_of_role(Oid member, Oid role);
+
+// -- non-upstream patch begin
+extern bool mdb_admin_allow_bypass_owner_checks(Oid userId,  Oid ownerId);
+
+extern void check_mdb_admin_is_member_of_role(Oid member, Oid role);
+// -- non-upstream patch end
+
 extern void check_is_member_of_role(Oid member, Oid role);
 extern Oid     get_role_oid(const char *rolename, bool missing_ok);
 extern Oid     get_role_oid_or_public(const char *rolename);
diff --git a/src/include/utils/backend_status.h 
b/src/include/utils/backend_status.h
index 139b7355d13..139646d4a40 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -319,6 +319,9 @@ extern uint64 pgstat_get_my_query_id(void);
 extern int     pgstat_fetch_stat_numbackends(void);
 extern PgBackendStatus *pgstat_fetch_stat_beentry(int beid);
 extern LocalPgBackendStatus *pgstat_fetch_stat_local_beentry(int beid);
+/* -- mdb admin patch -- */
+extern LocalPgBackendStatus *pgstat_fetch_stat_local_beentry_by_pid(int pid);
+/* -- mdb admin patch end -- */
 extern char *pgstat_clip_activity(const char *raw_activity);
 
 
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 17d2a166b09..08584e4db54 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -204,6 +204,8 @@ struct config_generic
        char       *sourcefile;         /* file current setting is from (NULL 
if not
                                                                 * set in 
config file) */
        int                     sourceline;             /* line in source file 
*/
+
+       bool            mdb_admin_allowed; /* is mdb admin allowed to change 
this, makes sence only for superuser/not superuser ctx */
 };
 
 /* bit values in status field */
diff --git a/src/test/Makefile b/src/test/Makefile
index d84edb282df..150c4e97b73 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -18,6 +18,9 @@ SUBDIRS = perl regress isolation modules authentication 
recovery
 
 SUBDIRS += fsync walrep heap_checksum isolation2 fdw singlenode_regress 
singlenode_isolation2
 
+# MDB addon
+SUBDIRS += mdb_admin
+
 # Test suites that are not safe by default but can be run if selected
 # by the user via the whitespace-separated list in variable
 # PG_TEST_EXTRA:
diff --git a/src/test/mdb_admin/.gitignore b/src/test/mdb_admin/.gitignore
new file mode 100644
index 00000000000..871e943d50e
--- /dev/null
+++ b/src/test/mdb_admin/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/mdb_admin/Makefile b/src/test/mdb_admin/Makefile
new file mode 100644
index 00000000000..e4e82367da9
--- /dev/null
+++ b/src/test/mdb_admin/Makefile
@@ -0,0 +1,23 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/mdb_admin
+#
+# Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/mdb_admin/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/mdb_admin
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+       $(prove_check)
+
+installcheck:
+       $(prove_installcheck)
+
+clean distclean maintainer-clean:
+       rm -rf tmp_check
diff --git a/src/test/mdb_admin/t/signals.pl b/src/test/mdb_admin/t/signals.pl
new file mode 100644
index 00000000000..a11db27a527
--- /dev/null
+++ b/src/test/mdb_admin/t/signals.pl
@@ -0,0 +1,74 @@
+
+# Copyright (c) 2024-2024, MDB, Mother Russia
+
+# Minimal test testing streaming replication
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init();
+$node_primary->start;
+
+# Create some content on primary and check its presence in standby nodes
+$node_primary->safe_psql('postgres',
+       "
+    CREATE DATABASE regress;
+    CREATE ROLE mdb_admin;
+    CREATE ROLE mdb_reg_lh_1;
+    CREATE ROLE mdb_reg_lh_2;
+    GRANT pg_signal_backend TO mdb_admin;
+    GRANT pg_signal_backend TO mdb_reg_lh_1;
+    GRANT mdb_admin TO mdb_reg_lh_2;
+");
+
+# Create some content on primary and check its presence in standby nodes
+$node_primary->safe_psql('regress',
+       "
+    CREATE TABLE tab_int(i int);
+    INSERT INTO tab_int SELECT * FROm generate_series(1, 1000000);
+    ALTER SYSTEM SET autovacuum_vacuum_cost_limit TO 1;
+    ALTER SYSTEM SET autovacuum_vacuum_cost_delay TO 100;
+    ALTER SYSTEM SET autovacuum_naptime TO 1;    
+");
+
+$node_primary->restart;
+
+sleep 1;
+
+my $res_pid = $node_primary->safe_psql('regress',
+       "
+    SELECT pid FROM pg_stat_activity WHERE backend_type = 'autovacuum worker' 
and datname = 'regress';;
+");
+
+
+print "pid is $res_pid\n";
+
+ok(1);
+
+
+my ($res_reg_lh_1, $stdout_reg_lh_1, $stderr_reg_lh_1)  = 
$node_primary->psql('regress',
+       "
+    SET ROLE mdb_reg_lh_1;
+    SELECT pg_terminate_backend($res_pid);
+");
+
+# print ($res_reg_lh_1, $stdout_reg_lh_1, $stderr_reg_lh_1, "\n");
+
+ok($res_reg_lh_1 != 0, "should fail for non-mdb_admin");
+like($stderr_reg_lh_1, qr/ERROR:  must be a superuser to terminate superuser 
process/, "matches");
+
+my ($res_reg_lh_2, $stdout_reg_lh_2, $stderr_reg_lh_2) = 
$node_primary->psql('regress',
+       "
+    SET ROLE mdb_reg_lh_2;
+    SELECT pg_terminate_backend($res_pid);
+");
+
+ok($res_reg_lh_2 == 0, "should success for mdb_admin");
+
+# print ($res_reg_lh_2, $stdout_reg_lh_2, $stderr_reg_lh_2, "\n");
+
+done_testing();
\ No newline at end of file
diff --git a/src/test/regress/expected/create_function_3.out 
b/src/test/regress/expected/create_function_3.out
index 8380df1591f..7842a3c1c82 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -166,10 +166,10 @@ SET SESSION AUTHORIZATION regress_unpriv_user;
 SET search_path TO temp_func_test, public;
 ALTER FUNCTION functest_E_1(int) NOT LEAKPROOF;
 ALTER FUNCTION functest_E_2(int) LEAKPROOF;
-ERROR:  only superuser can define a leakproof function
+ERROR:  only superuser or mdb_admin can define a leakproof function
 CREATE FUNCTION functest_E_3(int) RETURNS bool LANGUAGE 'sql'
        LEAKPROOF AS 'SELECT $1 < 200'; -- fail
-ERROR:  only superuser can define a leakproof function
+ERROR:  only superuser or mdb_admin can define a leakproof function
 RESET SESSION AUTHORIZATION;
 --
 -- CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
diff --git a/src/test/regress/expected/mdb_admin.out 
b/src/test/regress/expected/mdb_admin.out
new file mode 100644
index 00000000000..5fc2dab10cb
--- /dev/null
+++ b/src/test/regress/expected/mdb_admin.out
@@ -0,0 +1,81 @@
+CREATE ROLE regress_mdb_admin_user1;
+CREATE ROLE regress_mdb_admin_user2;
+CREATE ROLE regress_mdb_admin_user3;
+CREATE ROLE mdb_admin;
+CREATE ROLE regress_superuser WITH SUPERUSER;
+GRANT mdb_admin TO regress_mdb_admin_user1;
+GRANT CREATE ON DATABASE regression TO regress_mdb_admin_user2;
+GRANT CREATE ON DATABASE regression TO regress_mdb_admin_user3;
+-- mdb admin trasfers ownership to another role
+SET ROLE regress_mdb_admin_user2;
+CREATE FUNCTION regress_mdb_admin_add(integer, integer) RETURNS integer
+    AS 'SELECT $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+CREATE SCHEMA regress_mdb_admin_schema;
+GRANT CREATE ON SCHEMA regress_mdb_admin_schema TO regress_mdb_admin_user3;
+CREATE TABLE regress_mdb_admin_schema.regress_mdb_admin_table();
+CREATE TABLE regress_mdb_admin_table();
+CREATE VIEW regress_mdb_admin_view as SELECT 1;
+SET ROLE regress_mdb_admin_user1;
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
regress_mdb_admin_user3;
+ALTER VIEW regress_mdb_admin_view OWNER TO regress_mdb_admin_user3;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
regress_mdb_admin_user3;
+ALTER TABLE regress_mdb_admin_table OWNER TO regress_mdb_admin_user3;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO regress_mdb_admin_user3;
+-- mdb admin fails to transfer ownership to superusers and system roles
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
regress_superuser;
+ERROR:  cannot transfer ownership to superuser "regress_superuser"
+ALTER VIEW regress_mdb_admin_view OWNER TO regress_superuser;
+ERROR:  cannot transfer ownership to superuser "regress_superuser"
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
regress_superuser;
+ERROR:  cannot transfer ownership to superuser "regress_superuser"
+ALTER TABLE regress_mdb_admin_table OWNER TO regress_superuser;
+ERROR:  cannot transfer ownership to superuser "regress_superuser"
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO regress_superuser;
+ERROR:  cannot transfer ownership to superuser "regress_superuser"
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_execute_server_program;
+ERROR:  cannot transfer ownership to pg_execute_server_program role in Cloud
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_execute_server_program;
+ERROR:  cannot transfer ownership to pg_execute_server_program role in Cloud
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_execute_server_program;
+ERROR:  cannot transfer ownership to pg_execute_server_program role in Cloud
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_execute_server_program;
+ERROR:  cannot transfer ownership to pg_execute_server_program role in Cloud
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_execute_server_program;
+ERROR:  cannot transfer ownership to pg_execute_server_program role in Cloud
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_write_server_files;
+ERROR:  cannot transfer ownership to pg_write_server_files role in Cloud
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_write_server_files;
+ERROR:  cannot transfer ownership to pg_write_server_files role in Cloud
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_write_server_files;
+ERROR:  cannot transfer ownership to pg_write_server_files role in Cloud
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_write_server_files;
+ERROR:  cannot transfer ownership to pg_write_server_files role in Cloud
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_write_server_files;
+ERROR:  cannot transfer ownership to pg_write_server_files role in Cloud
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_read_server_files;
+ERROR:  cannot transfer ownership to pg_read_server_files role in Cloud
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_read_server_files;
+ERROR:  cannot transfer ownership to pg_read_server_files role in Cloud
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_read_server_files;
+ERROR:  cannot transfer ownership to pg_read_server_files role in Cloud
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_read_server_files;
+ERROR:  cannot transfer ownership to pg_read_server_files role in Cloud
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_read_server_files;
+ERROR:  cannot transfer ownership to pg_read_server_files role in Cloud
+-- end tests
+RESET SESSION AUTHORIZATION;
+--
+REVOKE CREATE ON DATABASE regression FROM regress_mdb_admin_user2;
+REVOKE CREATE ON DATABASE regression FROM regress_mdb_admin_user3;
+DROP VIEW regress_mdb_admin_view;
+DROP FUNCTION regress_mdb_admin_add;
+DROP TABLE regress_mdb_admin_schema.regress_mdb_admin_table;
+DROP TABLE regress_mdb_admin_table;
+DROP SCHEMA regress_mdb_admin_schema;
+DROP ROLE regress_mdb_admin_user1;
+DROP ROLE regress_mdb_admin_user2;
+DROP ROLE regress_mdb_admin_user3;
+DROP ROLE mdb_admin;
diff --git a/src/test/regress/parallel_schedule 
b/src/test/regress/parallel_schedule
index e2df0208627..6ebdd67731e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -5,6 +5,10 @@
 # this limits the number of connections needed to run the tests.
 # ----------
 
+# mdb admin simple checks
+
+test: mdb_admin
+
 # run tablespace by itself, and first, because it forces a checkpoint;
 # we'd prefer not to have checkpoints later in the tests because that
 # interferes with crash-recovery testing.
diff --git a/src/test/regress/sql/mdb_admin.sql 
b/src/test/regress/sql/mdb_admin.sql
new file mode 100644
index 00000000000..8552bbdd48a
--- /dev/null
+++ b/src/test/regress/sql/mdb_admin.sql
@@ -0,0 +1,77 @@
+CREATE ROLE regress_mdb_admin_user1;
+CREATE ROLE regress_mdb_admin_user2;
+CREATE ROLE regress_mdb_admin_user3;
+CREATE ROLE mdb_admin;
+
+CREATE ROLE regress_superuser WITH SUPERUSER;
+
+GRANT mdb_admin TO regress_mdb_admin_user1;
+GRANT CREATE ON DATABASE regression TO regress_mdb_admin_user2;
+GRANT CREATE ON DATABASE regression TO regress_mdb_admin_user3;
+
+-- mdb admin trasfers ownership to another role
+
+SET ROLE regress_mdb_admin_user2;
+CREATE FUNCTION regress_mdb_admin_add(integer, integer) RETURNS integer
+    AS 'SELECT $1 + $2;'
+    LANGUAGE SQL
+    IMMUTABLE
+    RETURNS NULL ON NULL INPUT;
+
+CREATE SCHEMA regress_mdb_admin_schema;
+GRANT CREATE ON SCHEMA regress_mdb_admin_schema TO regress_mdb_admin_user3;
+CREATE TABLE regress_mdb_admin_schema.regress_mdb_admin_table();
+CREATE TABLE regress_mdb_admin_table();
+CREATE VIEW regress_mdb_admin_view as SELECT 1;
+SET ROLE regress_mdb_admin_user1;
+
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
regress_mdb_admin_user3;
+ALTER VIEW regress_mdb_admin_view OWNER TO regress_mdb_admin_user3;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
regress_mdb_admin_user3;
+ALTER TABLE regress_mdb_admin_table OWNER TO regress_mdb_admin_user3;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO regress_mdb_admin_user3;
+
+
+-- mdb admin fails to transfer ownership to superusers and system roles
+
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
regress_superuser;
+ALTER VIEW regress_mdb_admin_view OWNER TO regress_superuser;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
regress_superuser;
+ALTER TABLE regress_mdb_admin_table OWNER TO regress_superuser;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO regress_superuser;
+
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_execute_server_program;
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_execute_server_program;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_execute_server_program;
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_execute_server_program;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_execute_server_program;
+
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_write_server_files;
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_write_server_files;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_write_server_files;
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_write_server_files;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_write_server_files;
+
+ALTER FUNCTION regress_mdb_admin_add (integer, integer) OWNER TO 
pg_read_server_files;
+ALTER VIEW regress_mdb_admin_view OWNER TO pg_read_server_files;
+ALTER TABLE regress_mdb_admin_schema.regress_mdb_admin_table OWNER TO 
pg_read_server_files;
+ALTER TABLE regress_mdb_admin_table OWNER TO pg_read_server_files;
+ALTER SCHEMA regress_mdb_admin_schema OWNER TO pg_read_server_files;
+
+
+-- end tests
+
+RESET SESSION AUTHORIZATION;
+--
+REVOKE CREATE ON DATABASE regression FROM regress_mdb_admin_user2;
+REVOKE CREATE ON DATABASE regression FROM regress_mdb_admin_user3;
+
+DROP VIEW regress_mdb_admin_view;
+DROP FUNCTION regress_mdb_admin_add;
+DROP TABLE regress_mdb_admin_schema.regress_mdb_admin_table;
+DROP TABLE regress_mdb_admin_table;
+DROP SCHEMA regress_mdb_admin_schema;
+DROP ROLE regress_mdb_admin_user1;
+DROP ROLE regress_mdb_admin_user2;
+DROP ROLE regress_mdb_admin_user3;
+DROP ROLE mdb_admin;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cloudberry.apache.org
For additional commands, e-mail: commits-h...@cloudberry.apache.org

Reply via email to