aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/catalogs.sgml3
-rw-r--r--doc/src/sgml/user-manag.sgml15
-rw-r--r--src/backend/catalog/information_schema.sql9
-rw-r--r--src/backend/commands/user.c36
-rw-r--r--src/backend/utils/adt/acl.c44
-rw-r--r--src/backend/utils/cache/catcache.c6
-rw-r--r--src/bin/psql/describe.c1
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/pg_authid.dat5
-rw-r--r--src/test/regress/expected/privileges.out61
-rw-r--r--src/test/regress/sql/privileges.sql31
11 files changed, 207 insertions, 6 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index cd00d9e3bb0..0f8703af5a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -10138,6 +10138,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
<primary>pg_group</primary>
</indexterm>
+ <!-- Unlike information_schema.applicable_roles, this shows no members for
+ pg_database_owner. The v8.1 catalog would have shown no members if
+ that role had existed at the time. -->
<para>
The view <structname>pg_group</structname> exists for backwards
compatibility: it emulates a catalog that existed in
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index a7c187896bd..6920f2db2b2 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -541,6 +541,10 @@ DROP ROLE doomed_role;
<literal>pg_stat_scan_tables</literal>.</entry>
</row>
<row>
+ <entry>pg_database_owner</entry>
+ <entry>None. Membership consists, implicitly, of the current database owner.</entry>
+ </row>
+ <row>
<entry>pg_signal_backend</entry>
<entry>Signal another backend to cancel a query or terminate its session.</entry>
</row>
@@ -573,6 +577,17 @@ DROP ROLE doomed_role;
</para>
<para>
+ The <literal>pg_database_owner</literal> role has one implicit,
+ situation-dependent member, namely the owner of the current database. The
+ role conveys no rights at first. Like any role, it can own objects or
+ receive grants of access privileges. Consequently, once
+ <literal>pg_database_owner</literal> has rights within a template database,
+ each owner of a database instantiated from that template will exercise those
+ rights. <literal>pg_database_owner</literal> cannot be a member of any
+ role, and it cannot have non-implicit members.
+ </para>
+
+ <para>
The <literal>pg_signal_backend</literal> role is intended to allow
administrators to enable trusted, but non-superuser, roles to send signals
to other backends. Currently this role enables sending of signals for
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 513cb9a69cf..941a9f664c9 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -255,7 +255,14 @@ CREATE VIEW applicable_roles AS
SELECT CAST(a.rolname AS sql_identifier) AS grantee,
CAST(b.rolname AS sql_identifier) AS role_name,
CAST(CASE WHEN m.admin_option THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable
- FROM pg_auth_members m
+ FROM (SELECT member, roleid, admin_option FROM pg_auth_members
+ -- This UNION could be UNION ALL, but UNION works even if we start
+ -- to allow explicit pg_database_owner membership.
+ UNION
+ SELECT datdba, pg_authid.oid, false
+ FROM pg_database, pg_authid
+ WHERE datname = current_database() AND rolname = 'pg_database_owner'
+ ) m
JOIN pg_authid a ON (m.member = a.oid)
JOIN pg_authid b ON (m.roleid = b.oid)
WHERE pg_has_role(a.oid, 'USAGE');
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index ed243e3d141..e91fa4c78c9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -1497,6 +1497,18 @@ AddRoleMems(const char *rolename, Oid roleid,
}
/*
+ * The charter of pg_database_owner is to have exactly one, implicit,
+ * situation-dependent member. There's no technical need for this
+ * restriction. (One could lift it and take the further step of making
+ * pg_database_ownercheck() equivalent to has_privs_of_role(roleid,
+ * DEFAULT_ROLE_DATABASE_OWNER), in which case explicit,
+ * situation-independent members could act as the owner of any database.)
+ */
+ if (roleid == DEFAULT_ROLE_DATABASE_OWNER)
+ ereport(ERROR,
+ errmsg("role \"%s\" cannot have explicit members", rolename));
+
+ /*
* The role membership grantor of record has little significance at
* present. Nonetheless, inasmuch as users might look to it for a crude
* audit trail, let only superusers impute the grant to a third party.
@@ -1525,6 +1537,30 @@ AddRoleMems(const char *rolename, Oid roleid,
bool new_record_repl[Natts_pg_auth_members];
/*
+ * pg_database_owner is never a role member. Lifting this restriction
+ * would require a policy decision about membership loops. One could
+ * prevent loops, which would include making "ALTER DATABASE x OWNER
+ * TO proposed_datdba" fail if is_member_of_role(pg_database_owner,
+ * proposed_datdba). Hence, gaining a membership could reduce what a
+ * role could do. Alternately, one could allow these memberships to
+ * complete loops. A role could then have actual WITH ADMIN OPTION on
+ * itself, prompting a decision about is_admin_of_role() treatment of
+ * the case.
+ *
+ * Lifting this restriction also has policy implications for ownership
+ * of shared objects (databases and tablespaces). We allow such
+ * ownership, but we might find cause to ban it in the future.
+ * Designing such a ban would more troublesome if the design had to
+ * address pg_database_owner being a member of role FOO that owns a
+ * shared object. (The effect of such ownership is that any owner of
+ * another database can act as the owner of affected shared objects.)
+ */
+ if (memberid == DEFAULT_ROLE_DATABASE_OWNER)
+ ereport(ERROR,
+ errmsg("role \"%s\" cannot be a member of any role",
+ get_rolespec_name(memberRole)));
+
+ /*
* Refuse creation of membership loops, including the trivial case
* where a role is made a member of itself. We do this by checking to
* see if the target role is already a member of the proposed member
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index e6b4bdbd768..9955c7c5c06 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -22,6 +22,7 @@
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_database.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/proclang.h"
@@ -68,6 +69,7 @@ enum RoleRecurseType
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
static List *cached_roles[] = {NIL, NIL};
+static uint32 cached_db_hash;
static const char *getid(const char *s, char *n);
@@ -4665,10 +4667,14 @@ initialize_acl(void)
{
if (!IsBootstrapProcessingMode())
{
+ cached_db_hash =
+ GetSysCacheHashValue1(DATABASEOID,
+ ObjectIdGetDatum(MyDatabaseId));
+
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()) or pg_authid (for
- * has_rolinherit())
+ * of pg_auth_members (for roles_is_member_of()), pg_authid (for
+ * has_rolinherit()), or pg_database (for roles_is_member_of())
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
@@ -4676,6 +4682,9 @@ initialize_acl(void)
CacheRegisterSyscacheCallback(AUTHOID,
RoleMembershipCacheCallback,
(Datum) 0);
+ CacheRegisterSyscacheCallback(DATABASEOID,
+ RoleMembershipCacheCallback,
+ (Datum) 0);
}
}
@@ -4686,6 +4695,13 @@ initialize_acl(void)
static void
RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
{
+ if (cacheid == DATABASEOID &&
+ hashvalue != cached_db_hash &&
+ hashvalue != 0)
+ {
+ return; /* ignore pg_database changes for other DBs */
+ }
+
/* Force membership caches to be recomputed on next use */
cached_role[ROLERECURSE_PRIVS] = InvalidOid;
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
@@ -4728,6 +4744,7 @@ static List *
roles_is_member_of(Oid roleid, enum RoleRecurseType type,
Oid admin_of, bool *is_admin)
{
+ Oid dba;
List *roles_list;
ListCell *l;
List *new_cached_roles;
@@ -4741,6 +4758,24 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
return cached_roles[type];
/*
+ * Role expansion happens in a non-database backend when guc.c checks
+ * DEFAULT_ROLE_READ_ALL_SETTINGS for a physical walsender SHOW command.
+ * In that case, no role gets pg_database_owner.
+ */
+ if (!OidIsValid(MyDatabaseId))
+ dba = InvalidOid;
+ else
+ {
+ HeapTuple dbtup;
+
+ dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
+ if (!HeapTupleIsValid(dbtup))
+ elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
+ dba = ((Form_pg_database) GETSTRUCT(dbtup))->datdba;
+ ReleaseSysCache(dbtup);
+ }
+
+ /*
* Find all the roles that roleid is a member of, including multi-level
* recursion. The role itself will always be the first element of the
* resulting list.
@@ -4787,6 +4822,11 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
roles_list = list_append_unique_oid(roles_list, otherid);
}
ReleaseSysCacheList(memlist);
+
+ /* implement pg_database_owner implicit membership */
+ if (memberid == dba && OidIsValid(dba))
+ roles_list = list_append_unique_oid(roles_list,
+ DEFAULT_ROLE_DATABASE_OWNER);
}
/*
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 55c94458981..4fbdc62d8c7 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1076,8 +1076,9 @@ InitCatCachePhase2(CatCache *cache, bool touch_index)
* criticalRelcachesBuilt), we don't have to worry anymore.
*
* Similarly, during backend startup we have to be able to use the
- * pg_authid and pg_auth_members syscaches for authentication even if
- * we don't yet have relcache entries for those catalogs' indexes.
+ * pg_authid, pg_auth_members and pg_database syscaches for
+ * authentication even if we don't yet have relcache entries for those
+ * catalogs' indexes.
*/
static bool
IndexScanOK(CatCache *cache, ScanKey cur_skey)
@@ -1110,6 +1111,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
case AUTHMEMMEMROLE:
+ case DATABASEOID:
/*
* Protect authentication lookups occurring before relcache has
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c9f7118a5dc..e56cc43e111 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3557,6 +3557,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
printTableAddHeader(&cont, gettext_noop("Role name"), true, align);
printTableAddHeader(&cont, gettext_noop("Attributes"), true, align);
+ /* ignores implicit memberships from superuser & pg_database_owner */
printTableAddHeader(&cont, gettext_noop("Member of"), true, align);
if (verbose && pset.sversion >= 80200)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 943f9aee9fd..474ee2982b8 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202103264
+#define CATALOG_VERSION_NO 202103265
#endif
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 87d917ffc38..4c2bf972ecc 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -24,6 +24,11 @@
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '8778', oid_symbol => 'DEFAULT_ROLE_DATABASE_OWNER',
+ rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
+ rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+ rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
+ rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'DEFAULT_ROLE_MONITOR',
rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 46a69fc0dc9..4903371991f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1719,6 +1719,67 @@ SELECT * FROM pg_largeobject LIMIT 0;
SET SESSION AUTHORIZATION regress_priv_user1;
SELECT * FROM pg_largeobject LIMIT 0; -- to be denied
ERROR: permission denied for table pg_largeobject
+-- test pg_database_owner
+RESET SESSION AUTHORIZATION;
+GRANT pg_database_owner TO regress_priv_user1;
+ERROR: role "pg_database_owner" cannot have explicit members
+GRANT regress_priv_user1 TO pg_database_owner;
+ERROR: role "pg_database_owner" cannot be a member of any role
+CREATE TABLE datdba_only ();
+ALTER TABLE datdba_only OWNER TO pg_database_owner;
+REVOKE DELETE ON datdba_only FROM pg_database_owner;
+SELECT
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv,
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem,
+ pg_has_role('regress_priv_user1', 'pg_database_owner',
+ 'MEMBER WITH ADMIN OPTION') as admin;
+ priv | mem | admin
+------+-----+-------
+ f | f | f
+(1 row)
+
+BEGIN;
+DO $$BEGIN EXECUTE format(
+ 'ALTER DATABASE %I OWNER TO regress_priv_group2', current_catalog); END$$;
+SELECT
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv,
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem,
+ pg_has_role('regress_priv_user1', 'pg_database_owner',
+ 'MEMBER WITH ADMIN OPTION') as admin;
+ priv | mem | admin
+------+-----+-------
+ t | t | f
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+TABLE information_schema.enabled_roles ORDER BY role_name COLLATE "C";
+ role_name
+---------------------
+ pg_database_owner
+ regress_priv_group2
+ regress_priv_user1
+(3 rows)
+
+TABLE information_schema.applicable_roles ORDER BY role_name COLLATE "C";
+ grantee | role_name | is_grantable
+---------------------+---------------------+--------------
+ regress_priv_group2 | pg_database_owner | NO
+ regress_priv_user1 | regress_priv_group2 | NO
+(2 rows)
+
+INSERT INTO datdba_only DEFAULT VALUES;
+SAVEPOINT q; DELETE FROM datdba_only; ROLLBACK TO q;
+ERROR: permission denied for table datdba_only
+SET SESSION AUTHORIZATION regress_priv_user2;
+TABLE information_schema.enabled_roles;
+ role_name
+--------------------
+ regress_priv_user2
+(1 row)
+
+INSERT INTO datdba_only DEFAULT VALUES;
+ERROR: permission denied for table datdba_only
+ROLLBACK;
-- test default ACLs
\c -
CREATE SCHEMA testns;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 6277140cfd3..8dcd2199e0d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1034,6 +1034,37 @@ SELECT * FROM pg_largeobject LIMIT 0;
SET SESSION AUTHORIZATION regress_priv_user1;
SELECT * FROM pg_largeobject LIMIT 0; -- to be denied
+-- test pg_database_owner
+RESET SESSION AUTHORIZATION;
+GRANT pg_database_owner TO regress_priv_user1;
+GRANT regress_priv_user1 TO pg_database_owner;
+CREATE TABLE datdba_only ();
+ALTER TABLE datdba_only OWNER TO pg_database_owner;
+REVOKE DELETE ON datdba_only FROM pg_database_owner;
+SELECT
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv,
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem,
+ pg_has_role('regress_priv_user1', 'pg_database_owner',
+ 'MEMBER WITH ADMIN OPTION') as admin;
+
+BEGIN;
+DO $$BEGIN EXECUTE format(
+ 'ALTER DATABASE %I OWNER TO regress_priv_group2', current_catalog); END$$;
+SELECT
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv,
+ pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem,
+ pg_has_role('regress_priv_user1', 'pg_database_owner',
+ 'MEMBER WITH ADMIN OPTION') as admin;
+SET SESSION AUTHORIZATION regress_priv_user1;
+TABLE information_schema.enabled_roles ORDER BY role_name COLLATE "C";
+TABLE information_schema.applicable_roles ORDER BY role_name COLLATE "C";
+INSERT INTO datdba_only DEFAULT VALUES;
+SAVEPOINT q; DELETE FROM datdba_only; ROLLBACK TO q;
+SET SESSION AUTHORIZATION regress_priv_user2;
+TABLE information_schema.enabled_roles;
+INSERT INTO datdba_only DEFAULT VALUES;
+ROLLBACK;
+
-- test default ACLs
\c -